mtrl 0.2.6 → 0.2.8

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 (226) hide show
  1. package/demo/build.ts +349 -0
  2. package/demo/index.html +110 -0
  3. package/demo/main.js +448 -0
  4. package/demo/styles.css +239 -0
  5. package/index.ts +18 -0
  6. package/package.json +14 -3
  7. package/server.ts +86 -0
  8. package/src/components/badge/api.ts +70 -63
  9. package/src/components/badge/badge.ts +16 -2
  10. package/src/components/badge/config.ts +66 -13
  11. package/src/components/badge/features.ts +51 -42
  12. package/src/components/badge/index.ts +27 -2
  13. package/src/components/badge/types.ts +62 -30
  14. package/src/components/bottom-app-bar/bottom-app-bar.ts +154 -0
  15. package/src/components/bottom-app-bar/config.ts +29 -0
  16. package/src/components/bottom-app-bar/index.ts +17 -0
  17. package/src/components/bottom-app-bar/types.ts +114 -0
  18. package/src/components/button/api.ts +5 -0
  19. package/src/components/button/button.ts +0 -1
  20. package/src/components/button/config.ts +6 -2
  21. package/src/components/button/index.ts +10 -2
  22. package/src/components/button/types.ts +20 -2
  23. package/src/components/card/card.ts +13 -25
  24. package/src/components/card/config.ts +83 -30
  25. package/src/components/card/content.ts +8 -10
  26. package/src/components/card/features.ts +4 -3
  27. package/src/components/card/index.ts +29 -2
  28. package/src/components/card/types.ts +33 -22
  29. package/src/components/checkbox/config.ts +3 -4
  30. package/src/components/checkbox/index.ts +1 -2
  31. package/src/components/checkbox/types.ts +12 -3
  32. package/src/components/chip/api.ts +170 -221
  33. package/src/components/chip/chip.ts +34 -302
  34. package/src/components/chip/config.ts +1 -2
  35. package/src/components/chip/index.ts +10 -2
  36. package/src/components/chip/types.ts +224 -35
  37. package/src/components/datepicker/api.ts +265 -0
  38. package/src/components/datepicker/config.ts +141 -0
  39. package/src/components/datepicker/datepicker.ts +341 -0
  40. package/src/components/datepicker/index.ts +12 -0
  41. package/src/components/datepicker/render.ts +450 -0
  42. package/src/components/datepicker/types.ts +397 -0
  43. package/src/components/datepicker/utils.ts +289 -0
  44. package/src/components/dialog/api.ts +55 -21
  45. package/src/components/dialog/config.ts +12 -9
  46. package/src/components/dialog/dialog.ts +6 -3
  47. package/src/components/dialog/features.ts +345 -151
  48. package/src/components/dialog/index.ts +38 -8
  49. package/src/components/dialog/types.ts +40 -14
  50. package/src/components/divider/config.ts +81 -0
  51. package/src/components/divider/divider.ts +37 -0
  52. package/src/components/divider/features.ts +207 -0
  53. package/src/components/divider/index.ts +9 -0
  54. package/src/components/divider/types.ts +55 -0
  55. package/src/components/extended-fab/api.ts +141 -0
  56. package/src/components/extended-fab/config.ts +112 -0
  57. package/src/components/extended-fab/extended-fab.ts +125 -0
  58. package/src/components/extended-fab/index.ts +9 -0
  59. package/src/components/extended-fab/types.ts +304 -0
  60. package/src/components/fab/api.ts +97 -0
  61. package/src/components/fab/config.ts +93 -0
  62. package/src/components/fab/fab.ts +67 -0
  63. package/src/components/fab/index.ts +9 -0
  64. package/src/components/fab/types.ts +251 -0
  65. package/src/components/list/config.ts +4 -5
  66. package/src/components/list/features.ts +6 -7
  67. package/src/components/list/index.ts +7 -9
  68. package/src/components/list/list-item.ts +12 -13
  69. package/src/components/list/types.ts +50 -5
  70. package/src/components/list/utils.ts +30 -3
  71. package/src/components/menu/features/items-manager.ts +9 -9
  72. package/src/components/menu/features/positioning.ts +7 -7
  73. package/src/components/menu/features/visibility.ts +7 -7
  74. package/src/components/menu/index.ts +7 -9
  75. package/src/components/menu/menu-item.ts +6 -6
  76. package/src/components/menu/menu.ts +22 -0
  77. package/src/components/menu/types.ts +29 -10
  78. package/src/components/menu/utils.ts +67 -0
  79. package/src/components/navigation/api.ts +78 -50
  80. package/src/components/navigation/config.ts +22 -10
  81. package/src/components/navigation/features/items.ts +284 -0
  82. package/src/components/navigation/index.ts +0 -6
  83. package/src/components/navigation/nav-item.ts +70 -33
  84. package/src/components/navigation/navigation.ts +53 -3
  85. package/src/components/navigation/types.ts +117 -70
  86. package/src/components/progress/api.ts +2 -3
  87. package/src/components/progress/config.ts +2 -3
  88. package/src/components/progress/index.ts +0 -1
  89. package/src/components/progress/progress.ts +1 -2
  90. package/src/components/progress/types.ts +186 -33
  91. package/src/components/radios/config.ts +1 -1
  92. package/src/components/radios/index.ts +0 -1
  93. package/src/components/radios/types.ts +0 -7
  94. package/src/components/search/api.ts +203 -0
  95. package/src/components/search/config.ts +86 -0
  96. package/src/components/search/features/index.ts +4 -0
  97. package/src/components/search/features/search.ts +717 -0
  98. package/src/components/search/features/states.ts +169 -0
  99. package/src/components/search/features/structure.ts +197 -0
  100. package/src/components/search/index.ts +7 -0
  101. package/src/components/search/search.ts +52 -0
  102. package/src/components/search/types.ts +175 -0
  103. package/src/components/segmented-button/config.ts +80 -0
  104. package/src/components/segmented-button/index.ts +4 -0
  105. package/src/components/segmented-button/segment.ts +154 -0
  106. package/src/components/segmented-button/segmented-button.ts +249 -0
  107. package/src/components/segmented-button/types.ts +254 -0
  108. package/src/components/slider/accessibility.md +5 -5
  109. package/src/components/slider/api.ts +41 -120
  110. package/src/components/slider/config.ts +51 -47
  111. package/src/components/slider/features/handlers.ts +495 -0
  112. package/src/components/slider/features/index.ts +1 -2
  113. package/src/components/slider/features/slider.ts +66 -84
  114. package/src/components/slider/features/states.ts +195 -0
  115. package/src/components/slider/features/structure.ts +136 -206
  116. package/src/components/slider/features/ui.ts +145 -206
  117. package/src/components/slider/index.ts +2 -11
  118. package/src/components/slider/slider.ts +9 -12
  119. package/src/components/slider/types.ts +67 -26
  120. package/src/components/snackbar/config.ts +2 -3
  121. package/src/components/snackbar/constants.ts +0 -32
  122. package/src/components/snackbar/index.ts +0 -1
  123. package/src/components/snackbar/position.ts +9 -1
  124. package/src/components/snackbar/types.ts +122 -46
  125. package/src/components/switch/config.ts +2 -3
  126. package/src/components/switch/index.ts +0 -1
  127. package/src/components/switch/types.ts +3 -2
  128. package/src/components/tabs/config.ts +3 -4
  129. package/src/components/tabs/features.ts +4 -2
  130. package/src/components/tabs/index.ts +0 -15
  131. package/src/components/tabs/indicator.ts +73 -13
  132. package/src/components/tabs/tab-api.ts +12 -4
  133. package/src/components/tabs/tab.ts +18 -6
  134. package/src/components/tabs/types.ts +23 -5
  135. package/src/components/textfield/config.ts +2 -3
  136. package/src/components/textfield/index.ts +0 -1
  137. package/src/components/textfield/types.ts +17 -3
  138. package/src/components/timepicker/README.md +277 -0
  139. package/src/components/timepicker/api.ts +632 -0
  140. package/src/components/timepicker/clockdial.ts +482 -0
  141. package/src/components/timepicker/config.ts +228 -0
  142. package/src/components/timepicker/index.ts +3 -0
  143. package/src/components/timepicker/render.ts +613 -0
  144. package/src/components/timepicker/timepicker.ts +117 -0
  145. package/src/components/timepicker/types.ts +336 -0
  146. package/src/components/timepicker/utils.ts +241 -0
  147. package/src/components/tooltip/api.ts +1 -1
  148. package/src/components/tooltip/config.ts +27 -6
  149. package/src/components/tooltip/index.ts +0 -1
  150. package/src/components/tooltip/types.ts +13 -3
  151. package/src/components/top-app-bar/config.ts +83 -0
  152. package/src/components/top-app-bar/index.ts +11 -0
  153. package/src/components/top-app-bar/top-app-bar.ts +316 -0
  154. package/src/components/top-app-bar/types.ts +140 -0
  155. package/src/core/build/_ripple.scss +6 -6
  156. package/src/core/build/ripple.ts +72 -95
  157. package/src/core/compose/features/icon.ts +3 -1
  158. package/src/core/compose/features/ripple.ts +4 -1
  159. package/src/core/compose/features/textlabel.ts +23 -2
  160. package/src/core/dom/create.ts +5 -0
  161. package/src/index.ts +9 -0
  162. package/src/styles/abstract/_theme.scss +9 -1
  163. package/src/styles/components/_badge.scss +182 -0
  164. package/src/styles/components/_bottom-app-bar.scss +103 -0
  165. package/src/{components/button/_styles.scss → styles/components/_button.scss} +0 -10
  166. package/src/{components/checkbox/_styles.scss → styles/components/_checkbox.scss} +0 -2
  167. package/src/styles/components/_datepicker.scss +358 -0
  168. package/src/styles/components/_dialog.scss +259 -0
  169. package/src/styles/components/_divider.scss +57 -0
  170. package/src/styles/components/_extended-fab.scss +267 -0
  171. package/src/styles/components/_fab.scss +225 -0
  172. package/src/{components/navigation/_styles.scss → styles/components/_navigation.scss} +1 -0
  173. package/src/styles/components/_search.scss +306 -0
  174. package/src/styles/components/_segmented-button.scss +117 -0
  175. package/src/{components/slider/_styles.scss → styles/components/_slider.scss} +83 -24
  176. package/src/{components/switch/_styles.scss → styles/components/_switch.scss} +0 -2
  177. package/src/{components/tabs/_styles.scss → styles/components/_tabs.scss} +95 -33
  178. package/src/{components/textfield/_styles.scss → styles/components/_textfield.scss} +70 -67
  179. package/src/styles/components/_timepicker.scss +451 -0
  180. package/src/styles/components/_top-app-bar.scss +225 -0
  181. package/src/styles/main.scss +98 -49
  182. package/src/styles/themes/_autumn.scss +21 -0
  183. package/src/styles/themes/_base-theme.scss +61 -0
  184. package/src/styles/themes/_baseline.scss +58 -0
  185. package/src/styles/themes/_bluekhaki.scss +125 -0
  186. package/src/styles/themes/_brownbeige.scss +125 -0
  187. package/src/styles/themes/_browngreen.scss +125 -0
  188. package/src/styles/themes/_forest.scss +6 -0
  189. package/src/styles/themes/_greenbeige.scss +125 -0
  190. package/src/styles/themes/_material.scss +125 -0
  191. package/src/styles/themes/_ocean.scss +6 -0
  192. package/src/styles/themes/_sageivory.scss +125 -0
  193. package/src/styles/themes/_spring.scss +6 -0
  194. package/src/styles/themes/_summer.scss +5 -0
  195. package/src/styles/themes/_sunset.scss +5 -0
  196. package/src/styles/themes/_tealcaramel.scss +125 -0
  197. package/src/styles/themes/_winter.scss +6 -0
  198. package/src/components/badge/_styles.scss +0 -174
  199. package/src/components/badge/constants.ts +0 -30
  200. package/src/components/button/constants.ts +0 -11
  201. package/src/components/card/constants.ts +0 -84
  202. package/src/components/dialog/_styles.scss +0 -213
  203. package/src/components/dialog/constants.ts +0 -32
  204. package/src/components/menu/constants.ts +0 -154
  205. package/src/components/navigation/constants.ts +0 -200
  206. package/src/components/navigation/features/items.js +0 -192
  207. package/src/components/progress/constants.ts +0 -29
  208. package/src/components/slider/features/appearance.ts +0 -94
  209. package/src/components/slider/features/disabled.ts +0 -68
  210. package/src/components/slider/features/events.ts +0 -164
  211. package/src/components/slider/features/interactions.ts +0 -396
  212. package/src/components/slider/features/keyboard.ts +0 -233
  213. package/src/components/switch/constants.ts +0 -80
  214. package/src/components/tabs/constants.ts +0 -89
  215. package/src/core/collection/adapters/mongodb.js +0 -232
  216. /package/src/{components/card/_styles.scss → styles/components/_card.scss} +0 -0
  217. /package/src/{components/carousel/_styles.scss → styles/components/_carousel.scss} +0 -0
  218. /package/src/{components/chip/_styles.scss → styles/components/_chip.scss} +0 -0
  219. /package/src/{components/list/_styles.scss → styles/components/_list.scss} +0 -0
  220. /package/src/{components/menu/_styles.scss → styles/components/_menu.scss} +0 -0
  221. /package/src/{components/progress/_styles.scss → styles/components/_progress.scss} +0 -0
  222. /package/src/{components/radios/_styles.scss → styles/components/_radios.scss} +0 -0
  223. /package/src/{components/sheet/_styles.scss → styles/components/_sheet.scss} +0 -0
  224. /package/src/{components/snackbar/_styles.scss → styles/components/_snackbar.scss} +0 -0
  225. /package/src/{components/tooltip/_styles.scss → styles/components/_tooltip.scss} +0 -0
  226. /package/src/styles/utilities/{_color.scss → _colors.scss} +0 -0
@@ -0,0 +1,154 @@
1
+ // src/components/segmented-button/segment.ts
2
+ import { createElement } from '../../core/dom/create';
3
+ import { createRipple } from '../../core/build/ripple';
4
+ import { SegmentConfig, Segment } from './types';
5
+ import { getSegmentConfig } from './config';
6
+
7
+ /**
8
+ * Creates a segment for the segmented button
9
+ * @param {SegmentConfig} config - Segment configuration
10
+ * @param {HTMLElement} container - Container element
11
+ * @param {string} prefix - Component prefix
12
+ * @param {boolean} groupDisabled - Whether the entire group is disabled
13
+ * @param {Object} options - Additional options
14
+ * @returns {Segment} Segment object
15
+ * @internal
16
+ */
17
+ export const createSegment = (
18
+ config: SegmentConfig,
19
+ container: HTMLElement,
20
+ prefix: string,
21
+ groupDisabled = false,
22
+ options = { ripple: true, rippleConfig: {} }
23
+ ): Segment => {
24
+ const segmentConfig = getSegmentConfig(config, prefix, groupDisabled);
25
+ const element = createElement(segmentConfig);
26
+
27
+ // Add to container
28
+ container.appendChild(element);
29
+
30
+ // Create ripple effect if enabled
31
+ let ripple;
32
+ if (options.ripple) {
33
+ ripple = createRipple(options.rippleConfig);
34
+ ripple.mount(element);
35
+ }
36
+
37
+ // Create text element if provided
38
+ let textElement;
39
+ if (config.text) {
40
+ textElement = createElement({
41
+ tag: 'span',
42
+ className: `${prefix}-segmentedbutton-segment-text`,
43
+ text: config.text,
44
+ container: element
45
+ });
46
+ }
47
+
48
+ // Create icon and checkmark elements
49
+ let iconElement, checkmarkElement;
50
+ if (config.icon) {
51
+ // Create icon element
52
+ iconElement = createElement({
53
+ tag: 'span',
54
+ className: `${prefix}-segmentedbutton-segment-icon`,
55
+ html: config.icon,
56
+ container: element
57
+ });
58
+
59
+ // Create checkmark element (hidden initially)
60
+ checkmarkElement = createElement({
61
+ tag: 'span',
62
+ className: `${prefix}-segmentedbutton-segment-'checkmark'`,
63
+ html: 'icon',
64
+ container: element
65
+ });
66
+
67
+ // Hide checkmark if not selected
68
+ if (!config.selected) {
69
+ checkmarkElement.style.display = 'none';
70
+ }
71
+
72
+ // Hide icon if selected and we have text (icon replaced by checkmark)
73
+ if (config.selected && config.text) {
74
+ iconElement.style.display = 'none';
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Updates the visual state based on selection
80
+ * @param {boolean} selected - Whether the segment is selected
81
+ * @private
82
+ */
83
+ const updateSelectedState = (selected: boolean) => {
84
+ element.classList.toggle(`${prefix}-segmentedbutton-segment--selected`, selected);
85
+ element.setAttribute('aria-pressed', selected ? 'true' : 'false');
86
+
87
+ // Handle icon/checkmark swap if we have both text and icon
88
+ if (iconElement && checkmarkElement && config.text) {
89
+ iconElement.style.display = selected ? 'none' : '';
90
+ checkmarkElement.style.display = selected ? '' : 'none';
91
+ } else if (checkmarkElement) {
92
+ // If we have only icons (no text), show checkmark based on selection
93
+ checkmarkElement.style.display = selected ? '' : 'none';
94
+ }
95
+ };
96
+
97
+ /**
98
+ * Updates the disabled state
99
+ * @param {boolean} disabled - Whether the segment is disabled
100
+ * @private
101
+ */
102
+ const updateDisabledState = (disabled: boolean) => {
103
+ const isDisabled = disabled || groupDisabled;
104
+ element.classList.toggle(`${prefix}-segmentedbutton-segment--disabled`, isDisabled);
105
+
106
+ if (isDisabled) {
107
+ element.setAttribute('disabled', 'true');
108
+ } else {
109
+ element.removeAttribute('disabled');
110
+ }
111
+ };
112
+
113
+ // Value to use for the segment
114
+ const value = config.value || config.text || '';
115
+
116
+ // Initialize state
117
+ let isSelected = config.selected || false;
118
+ let isDisabled = config.disabled || false;
119
+
120
+ return {
121
+ element,
122
+ value,
123
+
124
+ isSelected() {
125
+ return isSelected;
126
+ },
127
+
128
+ setSelected(selected: boolean) {
129
+ isSelected = selected;
130
+ updateSelectedState(selected);
131
+ },
132
+
133
+ isDisabled() {
134
+ return isDisabled || groupDisabled;
135
+ },
136
+
137
+ setDisabled(disabled: boolean) {
138
+ isDisabled = disabled;
139
+ updateDisabledState(disabled);
140
+ },
141
+
142
+ destroy() {
143
+ // Clean up ripple if it exists
144
+ if (ripple) {
145
+ ripple.unmount(element);
146
+ }
147
+
148
+ // Remove from DOM
149
+ if (element.parentNode) {
150
+ element.parentNode.removeChild(element);
151
+ }
152
+ }
153
+ };
154
+ };
@@ -0,0 +1,249 @@
1
+ // src/components/segmented-button/segmented-button.ts
2
+ import { pipe } from '../../core/compose/pipe';
3
+ import { createBase, withElement } from '../../core/compose/component';
4
+ import { withEvents, withLifecycle } from '../../core/compose/features';
5
+ import { createEmitter } from '../../core/state/emitter';
6
+ import { SegmentedButtonConfig, SegmentedButtonComponent, SelectionMode, Segment } from './types';
7
+ import { createBaseConfig, getContainerConfig } from './config';
8
+ import { createSegment } from './segment';
9
+
10
+ /**
11
+ * Creates a new Segmented Button component
12
+ * @param {SegmentedButtonConfig} config - Segmented Button configuration
13
+ * @returns {SegmentedButtonComponent} Segmented Button component instance
14
+ */
15
+ const createSegmentedButton = (config: SegmentedButtonConfig = {}): SegmentedButtonComponent => {
16
+ // Process configuration
17
+ const baseConfig = createBaseConfig(config);
18
+ const mode = baseConfig.mode || SelectionMode.SINGLE;
19
+ const emitter = createEmitter();
20
+
21
+ try {
22
+ // Create the base component
23
+ const component = pipe(
24
+ createBase,
25
+ withEvents(),
26
+ withElement(getContainerConfig(baseConfig)),
27
+ withLifecycle()
28
+ )(baseConfig);
29
+
30
+ // Create segments
31
+ const segments: Segment[] = [];
32
+ if (baseConfig.segments && baseConfig.segments.length) {
33
+ baseConfig.segments.forEach(segmentConfig => {
34
+ const segment = createSegment(
35
+ segmentConfig,
36
+ component.element,
37
+ baseConfig.prefix,
38
+ baseConfig.disabled,
39
+ {
40
+ ripple: baseConfig.ripple,
41
+ rippleConfig: baseConfig.rippleConfig
42
+ }
43
+ );
44
+
45
+ segments.push(segment);
46
+ });
47
+ }
48
+
49
+ // Ensure at least one item is selected in single-select mode
50
+ if (mode === SelectionMode.SINGLE && !segments.some(s => s.isSelected())) {
51
+ // Select the first non-disabled segment by default
52
+ const firstSelectable = segments.find(s => !s.isDisabled());
53
+ if (firstSelectable) {
54
+ firstSelectable.setSelected(true);
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Handles click events on segments
60
+ * @param {Event} event - DOM click event
61
+ * @private
62
+ */
63
+ const handleSegmentClick = (event: Event) => {
64
+ const segmentElement = event.currentTarget as HTMLElement;
65
+ const clickedSegment = segments.find(s => s.element === segmentElement);
66
+
67
+ if (!clickedSegment || clickedSegment.isDisabled()) {
68
+ return;
69
+ }
70
+
71
+ const oldValue = getSelectedValues();
72
+
73
+ // Handle selection based on mode
74
+ if (mode === SelectionMode.SINGLE) {
75
+ // In single-select, deselect all other segments
76
+ segments.forEach(segment => {
77
+ segment.setSelected(segment === clickedSegment);
78
+ });
79
+ } else {
80
+ // In multi-select, toggle the clicked segment
81
+ clickedSegment.setSelected(!clickedSegment.isSelected());
82
+ }
83
+
84
+ // Emit change event
85
+ const newValue = getSelectedValues();
86
+
87
+ // Only emit if values actually changed
88
+ if (
89
+ oldValue.length !== newValue.length ||
90
+ oldValue.some(v => !newValue.includes(v)) ||
91
+ newValue.some(v => !oldValue.includes(v))
92
+ ) {
93
+ emitter.emit('change', {
94
+ selected: getSelected(),
95
+ value: newValue,
96
+ oldValue
97
+ });
98
+ }
99
+ };
100
+
101
+ // Attach click handlers to segments
102
+ segments.forEach(segment => {
103
+ segment.element.addEventListener('click', handleSegmentClick);
104
+ });
105
+
106
+ /**
107
+ * Gets an array of selected segments
108
+ * @returns {Segment[]} Array of selected segments
109
+ * @private
110
+ */
111
+ const getSelected = () => segments.filter(segment => segment.isSelected());
112
+
113
+ /**
114
+ * Gets an array of selected segment values
115
+ * @returns {string[]} Array of selected segment values
116
+ * @private
117
+ */
118
+ const getSelectedValues = () => getSelected().map(segment => segment.value);
119
+
120
+ /**
121
+ * Finds a segment by its value
122
+ * @param {string} value - Segment value to find
123
+ * @returns {Segment|undefined} The found segment or undefined
124
+ * @private
125
+ */
126
+ const findSegmentByValue = (value: string) => segments.find(segment => segment.value === value);
127
+
128
+ // Create the component API
129
+ const segmentedButton: SegmentedButtonComponent = {
130
+ element: component.element,
131
+ segments,
132
+
133
+ getSelected,
134
+
135
+ getValue() {
136
+ return getSelectedValues();
137
+ },
138
+
139
+ select(value) {
140
+ const segment = findSegmentByValue(value);
141
+ if (segment && !segment.isDisabled()) {
142
+ const oldValue = getSelectedValues();
143
+
144
+ if (mode === SelectionMode.SINGLE) {
145
+ // Deselect all other segments
146
+ segments.forEach(s => s.setSelected(s === segment));
147
+ } else {
148
+ // Just select this segment
149
+ segment.setSelected(true);
150
+ }
151
+
152
+ // Emit change event
153
+ const newValue = getSelectedValues();
154
+ if (oldValue.join(',') !== newValue.join(',')) {
155
+ emitter.emit('change', {
156
+ selected: getSelected(),
157
+ value: newValue,
158
+ oldValue
159
+ });
160
+ }
161
+ }
162
+ return this;
163
+ },
164
+
165
+ deselect(value) {
166
+ const segment = findSegmentByValue(value);
167
+ if (segment && !segment.isDisabled()) {
168
+ // In single select mode, only deselect if there's another selected segment
169
+ if (mode === SelectionMode.SINGLE) {
170
+ const selectedSegments = getSelected();
171
+ // Only allow deselection if there's more than one selected or we're selecting a different segment
172
+ if (selectedSegments.length > 1 || !segment.isSelected()) {
173
+ const oldValue = getSelectedValues();
174
+ segment.setSelected(false);
175
+
176
+ // Emit change event
177
+ const newValue = getSelectedValues();
178
+ if (oldValue.join(',') !== newValue.join(',')) {
179
+ emitter.emit('change', {
180
+ selected: getSelected(),
181
+ value: newValue,
182
+ oldValue
183
+ });
184
+ }
185
+ }
186
+ } else {
187
+ // In multi-select, always allow deselection
188
+ const oldValue = getSelectedValues();
189
+ segment.setSelected(false);
190
+
191
+ // Emit change event
192
+ const newValue = getSelectedValues();
193
+ if (oldValue.join(',') !== newValue.join(',')) {
194
+ emitter.emit('change', {
195
+ selected: getSelected(),
196
+ value: newValue,
197
+ oldValue
198
+ });
199
+ }
200
+ }
201
+ }
202
+ return this;
203
+ },
204
+
205
+ enable() {
206
+ // Enable the entire component
207
+ component.element.classList.remove(`${baseConfig.prefix}-segmented-button--disabled`);
208
+ return this;
209
+ },
210
+
211
+ disable() {
212
+ // Disable the entire component
213
+ component.element.classList.add(`${baseConfig.prefix}-segmented-button--disabled`);
214
+ return this;
215
+ },
216
+
217
+ on(event, handler) {
218
+ emitter.on(event, handler);
219
+ return this;
220
+ },
221
+
222
+ off(event, handler) {
223
+ emitter.off(event, handler);
224
+ return this;
225
+ },
226
+
227
+ destroy() {
228
+ // Remove event listeners
229
+ segments.forEach(segment => {
230
+ segment.element.removeEventListener('click', handleSegmentClick);
231
+ segment.destroy();
232
+ });
233
+
234
+ // Clear emitter
235
+ emitter.clear();
236
+
237
+ // Destroy base component
238
+ component.lifecycle.destroy();
239
+ }
240
+ };
241
+
242
+ return segmentedButton;
243
+ } catch (error) {
244
+ console.error('Segmented Button creation error:', error);
245
+ throw new Error(`Failed to create segmented button: ${(error as Error).message}`);
246
+ }
247
+ };
248
+
249
+ export default createSegmentedButton;
@@ -0,0 +1,254 @@
1
+ // src/components/segmented-button/types.ts
2
+
3
+ /**
4
+ * Segmented button selection mode
5
+ * @category Components
6
+ */
7
+ export enum SelectionMode {
8
+ /** Only one segment can be selected at a time */
9
+ SINGLE = 'single',
10
+ /** Multiple segments can be selected */
11
+ MULTI = 'multi'
12
+ }
13
+
14
+ /**
15
+ * Event types for segmented button
16
+ */
17
+ export type SegmentedButtonEventType = 'change';
18
+
19
+ /**
20
+ * Event data for segmented button events
21
+ */
22
+ export interface SegmentedButtonEvent {
23
+ /** The segmented button component that triggered the event */
24
+ segmentedButton: SegmentedButtonComponent;
25
+
26
+ /** The selected segments */
27
+ selected: Segment[];
28
+
29
+ /** Values of the selected segments */
30
+ values: string[];
31
+
32
+ /** Original DOM event if available */
33
+ originalEvent: Event | null;
34
+
35
+ /** Function to prevent default behavior */
36
+ preventDefault: () => void;
37
+
38
+ /** Whether default behavior was prevented */
39
+ defaultPrevented: boolean;
40
+ }
41
+
42
+ /**
43
+ * Configuration for a single segment within a segmented button
44
+ * @category Components
45
+ */
46
+ export interface SegmentConfig {
47
+ /**
48
+ * Text content for the segment
49
+ * @example 'Day'
50
+ */
51
+ text?: string;
52
+
53
+ /**
54
+ * Icon HTML content
55
+ * @example '<svg>...</svg>'
56
+ */
57
+ icon?: string;
58
+
59
+ /**
60
+ * Whether this segment is initially selected
61
+ * @default false
62
+ */
63
+ selected?: boolean;
64
+
65
+ /**
66
+ * Value associated with this segment
67
+ */
68
+ value?: string;
69
+
70
+ /**
71
+ * Whether this segment is disabled
72
+ * @default false
73
+ */
74
+ disabled?: boolean;
75
+
76
+ /**
77
+ * Additional CSS class names for this segment
78
+ */
79
+ class?: string;
80
+ }
81
+
82
+ /**
83
+ * Configuration interface for the Segmented Button component
84
+ * @category Components
85
+ */
86
+ export interface SegmentedButtonConfig {
87
+ /**
88
+ * Selection mode for the segmented button group
89
+ * @default SelectionMode.SINGLE
90
+ */
91
+ mode?: SelectionMode;
92
+
93
+ /**
94
+ * Array of segment configurations
95
+ */
96
+ segments?: SegmentConfig[];
97
+
98
+ /**
99
+ * Component prefix for class names
100
+ * @default 'mtrl'
101
+ */
102
+ prefix?: string;
103
+
104
+ /**
105
+ * Component name used in class generation
106
+ */
107
+ componentName?: string;
108
+
109
+ /**
110
+ * Whether the entire segmented button is initially disabled
111
+ * @default false
112
+ */
113
+ disabled?: boolean;
114
+
115
+ /**
116
+ * Additional CSS class for the segmented button container
117
+ */
118
+ class?: string;
119
+
120
+ /**
121
+ * Whether to enable ripple effect
122
+ * @default true
123
+ */
124
+ ripple?: boolean;
125
+
126
+ /**
127
+ * Ripple effect configuration
128
+ */
129
+ rippleConfig?: {
130
+ /** Duration of the ripple animation in milliseconds */
131
+ duration?: number;
132
+ /** Timing function for the ripple animation */
133
+ timing?: string;
134
+ /** Opacity values for ripple start and end [start, end] */
135
+ opacity?: [string, string];
136
+ };
137
+
138
+ /**
139
+ * Event handlers for segmented button events
140
+ */
141
+ on?: {
142
+ [key in SegmentedButtonEventType]?: (event: SegmentedButtonEvent) => void;
143
+ };
144
+ }
145
+
146
+ /**
147
+ * Interface for a segment within a segmented button
148
+ * @category Components
149
+ */
150
+ export interface Segment {
151
+ /** The segment's DOM element */
152
+ element: HTMLElement;
153
+
154
+ /** The segment's value */
155
+ value: string;
156
+
157
+ /**
158
+ * Gets whether the segment is selected
159
+ */
160
+ isSelected: () => boolean;
161
+
162
+ /**
163
+ * Sets the segment's selected state
164
+ * @param selected - Whether the segment should be selected
165
+ */
166
+ setSelected: (selected: boolean) => void;
167
+
168
+ /**
169
+ * Gets whether the segment is disabled
170
+ */
171
+ isDisabled: () => boolean;
172
+
173
+ /**
174
+ * Sets the segment's disabled state
175
+ * @param disabled - Whether the segment should be disabled
176
+ */
177
+ setDisabled: (disabled: boolean) => void;
178
+
179
+ /**
180
+ * Destroys the segment and cleans up resources
181
+ */
182
+ destroy: () => void;
183
+ }
184
+
185
+ /**
186
+ * Segmented Button component interface
187
+ * @category Components
188
+ */
189
+ export interface SegmentedButtonComponent {
190
+ /** The component's container DOM element */
191
+ element: HTMLElement;
192
+
193
+ /** Array of segment objects */
194
+ segments: Segment[];
195
+
196
+ /**
197
+ * Gets the selected segment(s)
198
+ * @returns An array of selected segments
199
+ */
200
+ getSelected: () => Segment[];
201
+
202
+ /**
203
+ * Gets the values of selected segment(s)
204
+ * @returns An array of selected segment values
205
+ */
206
+ getValue: () => string[];
207
+
208
+ /**
209
+ * Selects a segment by its value
210
+ * @param value - The value of the segment to select
211
+ * @returns The SegmentedButtonComponent for chaining
212
+ */
213
+ select: (value: string) => SegmentedButtonComponent;
214
+
215
+ /**
216
+ * Deselects a segment by its value
217
+ * @param value - The value of the segment to deselect
218
+ * @returns The SegmentedButtonComponent for chaining
219
+ */
220
+ deselect: (value: string) => SegmentedButtonComponent;
221
+
222
+ /**
223
+ * Enables the segmented button
224
+ * @returns The SegmentedButtonComponent for chaining
225
+ */
226
+ enable: () => SegmentedButtonComponent;
227
+
228
+ /**
229
+ * Disables the segmented button
230
+ * @returns The SegmentedButtonComponent for chaining
231
+ */
232
+ disable: () => SegmentedButtonComponent;
233
+
234
+ /**
235
+ * Adds an event listener to the segmented button
236
+ * @param event - Event name ('change', etc.)
237
+ * @param handler - Event handler function
238
+ * @returns The SegmentedButtonComponent for chaining
239
+ */
240
+ on: (event: SegmentedButtonEventType, handler: (event: SegmentedButtonEvent) => void) => SegmentedButtonComponent;
241
+
242
+ /**
243
+ * Removes an event listener from the segmented button
244
+ * @param event - Event name
245
+ * @param handler - Event handler function
246
+ * @returns The SegmentedButtonComponent for chaining
247
+ */
248
+ off: (event: SegmentedButtonEventType, handler: (event: SegmentedButtonEvent) => void) => SegmentedButtonComponent;
249
+
250
+ /**
251
+ * Destroys the component and cleans up resources
252
+ */
253
+ destroy: () => void;
254
+ }
@@ -8,7 +8,7 @@ Based on the provided accessibility requirements, the slider component has been
8
8
 
9
9
  ### Focus and Keyboard Navigation
10
10
 
11
- - **Direct Thumb Focus**: The initial focus now lands directly on the thumb (not the container)
11
+ - **Direct Handle Focus**: The initial focus now lands directly on the handle (not the container)
12
12
  - **Visual Feedback**: Added a clear outline on focus to provide visual cues for keyboard users
13
13
  - **Arrow Key Navigation**:
14
14
  - Left/Right arrows change the value by one step
@@ -19,11 +19,11 @@ Based on the provided accessibility requirements, the slider component has been
19
19
 
20
20
  ### Visual Feedback During Interaction
21
21
 
22
- - **Thumb Shrinking**: The thumb width shrinks slightly during interaction to provide feedback
22
+ - **Handle Shrinking**: The handle width shrinks slightly during interaction to provide feedback
23
23
  - **Value Display**:
24
24
  - Value appears during interaction (touch, drag, mouse click, keyboard navigation)
25
25
  - Value remains visible briefly after interaction ends (1.5 seconds)
26
- - Value position updates to follow the thumb
26
+ - Value position updates to follow the handle
27
27
 
28
28
  ### Visual Anchors for Contrast
29
29
 
@@ -39,13 +39,13 @@ Based on the provided accessibility requirements, the slider component has been
39
39
  - Set appropriate ARIA attributes for screen readers
40
40
 
41
41
  2. **Interaction Feedback**:
42
- - Modified CSS to shrink thumb width during active states
42
+ - Modified CSS to shrink handle width during active states
43
43
  - Enhanced value bubble display timing
44
44
  - Improved touch and mouse event handling
45
45
 
46
46
  3. **Focus Management**:
47
47
  - Set clear focus styles that work cross-browser
48
- - Applied focus directly to interactive thumb elements
48
+ - Applied focus directly to interactive handle elements
49
49
  - Ensured focus outline is visible against various backgrounds
50
50
 
51
51
  ## Keyboard Navigation Map