mtrl 0.2.5 → 0.2.7

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 (196) hide show
  1. package/index.ts +18 -0
  2. package/package.json +1 -1
  3. package/src/components/badge/_styles.scss +123 -115
  4. package/src/components/badge/api.ts +57 -59
  5. package/src/components/badge/badge.ts +16 -2
  6. package/src/components/badge/config.ts +65 -11
  7. package/src/components/badge/constants.ts +22 -12
  8. package/src/components/badge/features.ts +44 -40
  9. package/src/components/badge/types.ts +42 -30
  10. package/src/components/bottom-app-bar/_styles.scss +103 -0
  11. package/src/components/bottom-app-bar/bottom-app-bar.ts +196 -0
  12. package/src/components/bottom-app-bar/config.ts +73 -0
  13. package/src/components/bottom-app-bar/index.ts +11 -0
  14. package/src/components/bottom-app-bar/types.ts +108 -0
  15. package/src/components/button/_styles.scss +0 -66
  16. package/src/components/button/api.ts +5 -0
  17. package/src/components/button/button.ts +0 -2
  18. package/src/components/button/config.ts +5 -0
  19. package/src/components/button/constants.ts +0 -6
  20. package/src/components/button/index.ts +2 -2
  21. package/src/components/button/types.ts +7 -7
  22. package/src/components/card/_styles.scss +67 -25
  23. package/src/components/card/api.ts +54 -3
  24. package/src/components/card/card.ts +25 -6
  25. package/src/components/card/config.ts +189 -22
  26. package/src/components/card/constants.ts +20 -19
  27. package/src/components/card/content.ts +299 -2
  28. package/src/components/card/features.ts +158 -4
  29. package/src/components/card/index.ts +31 -9
  30. package/src/components/card/types.ts +166 -15
  31. package/src/components/checkbox/_styles.scss +0 -2
  32. package/src/components/chip/chip.ts +1 -9
  33. package/src/components/chip/constants.ts +0 -10
  34. package/src/components/chip/index.ts +1 -1
  35. package/src/components/chip/types.ts +1 -4
  36. package/src/components/datepicker/_styles.scss +358 -0
  37. package/src/components/datepicker/api.ts +272 -0
  38. package/src/components/datepicker/config.ts +144 -0
  39. package/src/components/datepicker/constants.ts +98 -0
  40. package/src/components/datepicker/datepicker.ts +346 -0
  41. package/src/components/datepicker/index.ts +9 -0
  42. package/src/components/datepicker/render.ts +452 -0
  43. package/src/components/datepicker/types.ts +268 -0
  44. package/src/components/datepicker/utils.ts +290 -0
  45. package/src/components/dialog/_styles.scss +174 -128
  46. package/src/components/dialog/api.ts +48 -13
  47. package/src/components/dialog/config.ts +9 -5
  48. package/src/components/dialog/dialog.ts +6 -3
  49. package/src/components/dialog/features.ts +290 -130
  50. package/src/components/dialog/types.ts +7 -4
  51. package/src/components/divider/_styles.scss +57 -0
  52. package/src/components/divider/config.ts +81 -0
  53. package/src/components/divider/divider.ts +37 -0
  54. package/src/components/divider/features.ts +207 -0
  55. package/src/components/divider/index.ts +5 -0
  56. package/src/components/divider/types.ts +55 -0
  57. package/src/components/extended-fab/_styles.scss +267 -0
  58. package/src/components/extended-fab/api.ts +141 -0
  59. package/src/components/extended-fab/config.ts +108 -0
  60. package/src/components/extended-fab/constants.ts +36 -0
  61. package/src/components/extended-fab/extended-fab.ts +125 -0
  62. package/src/components/extended-fab/index.ts +4 -0
  63. package/src/components/extended-fab/types.ts +287 -0
  64. package/src/components/fab/_styles.scss +225 -0
  65. package/src/components/fab/api.ts +97 -0
  66. package/src/components/fab/config.ts +94 -0
  67. package/src/components/fab/constants.ts +41 -0
  68. package/src/components/fab/fab.ts +67 -0
  69. package/src/components/fab/index.ts +4 -0
  70. package/src/components/fab/types.ts +234 -0
  71. package/src/components/navigation/_styles.scss +1 -0
  72. package/src/components/navigation/api.ts +78 -50
  73. package/src/components/navigation/features/items.ts +280 -0
  74. package/src/components/navigation/nav-item.ts +72 -23
  75. package/src/components/navigation/navigation.ts +54 -2
  76. package/src/components/navigation/types.ts +210 -188
  77. package/src/components/progress/_styles.scss +0 -65
  78. package/src/components/progress/config.ts +1 -2
  79. package/src/components/progress/constants.ts +0 -14
  80. package/src/components/progress/index.ts +1 -1
  81. package/src/components/progress/progress.ts +1 -4
  82. package/src/components/progress/types.ts +1 -4
  83. package/src/components/radios/_styles.scss +0 -45
  84. package/src/components/radios/api.ts +85 -60
  85. package/src/components/radios/config.ts +1 -2
  86. package/src/components/radios/constants.ts +0 -9
  87. package/src/components/radios/index.ts +1 -1
  88. package/src/components/radios/radio.ts +34 -11
  89. package/src/components/radios/radios.ts +2 -1
  90. package/src/components/radios/types.ts +1 -7
  91. package/src/components/search/_styles.scss +306 -0
  92. package/src/components/search/api.ts +203 -0
  93. package/src/components/search/config.ts +87 -0
  94. package/src/components/search/constants.ts +21 -0
  95. package/src/components/search/features/index.ts +4 -0
  96. package/src/components/search/features/search.ts +718 -0
  97. package/src/components/search/features/states.ts +165 -0
  98. package/src/components/search/features/structure.ts +198 -0
  99. package/src/components/search/index.ts +10 -0
  100. package/src/components/search/search.ts +52 -0
  101. package/src/components/search/types.ts +163 -0
  102. package/src/components/segmented-button/_styles.scss +117 -0
  103. package/src/components/segmented-button/config.ts +67 -0
  104. package/src/components/segmented-button/constants.ts +42 -0
  105. package/src/components/segmented-button/index.ts +4 -0
  106. package/src/components/segmented-button/segment.ts +155 -0
  107. package/src/components/segmented-button/segmented-button.ts +250 -0
  108. package/src/components/segmented-button/types.ts +219 -0
  109. package/src/components/slider/_styles.scss +221 -168
  110. package/src/components/slider/accessibility.md +59 -0
  111. package/src/components/slider/api.ts +41 -120
  112. package/src/components/slider/config.ts +51 -49
  113. package/src/components/slider/features/handlers.ts +495 -0
  114. package/src/components/slider/features/index.ts +1 -2
  115. package/src/components/slider/features/slider.ts +66 -84
  116. package/src/components/slider/features/states.ts +195 -0
  117. package/src/components/slider/features/structure.ts +141 -184
  118. package/src/components/slider/features/ui.ts +150 -201
  119. package/src/components/slider/index.ts +2 -11
  120. package/src/components/slider/slider.ts +9 -12
  121. package/src/components/slider/types.ts +39 -24
  122. package/src/components/switch/_styles.scss +0 -2
  123. package/src/components/tabs/_styles.scss +346 -154
  124. package/src/components/tabs/api.ts +178 -400
  125. package/src/components/tabs/config.ts +46 -52
  126. package/src/components/tabs/constants.ts +85 -8
  127. package/src/components/tabs/features.ts +403 -0
  128. package/src/components/tabs/index.ts +60 -3
  129. package/src/components/tabs/indicator.ts +285 -0
  130. package/src/components/tabs/responsive.ts +144 -0
  131. package/src/components/tabs/scroll-indicators.ts +149 -0
  132. package/src/components/tabs/state.ts +186 -0
  133. package/src/components/tabs/tab-api.ts +258 -0
  134. package/src/components/tabs/tab.ts +255 -0
  135. package/src/components/tabs/tabs.ts +50 -31
  136. package/src/components/tabs/types.ts +332 -128
  137. package/src/components/tabs/utils.ts +107 -0
  138. package/src/components/textfield/_styles.scss +0 -98
  139. package/src/components/textfield/config.ts +2 -3
  140. package/src/components/textfield/constants.ts +0 -14
  141. package/src/components/textfield/index.ts +2 -2
  142. package/src/components/textfield/textfield.ts +0 -2
  143. package/src/components/textfield/types.ts +1 -4
  144. package/src/components/timepicker/README.md +277 -0
  145. package/src/components/timepicker/_styles.scss +451 -0
  146. package/src/components/timepicker/api.ts +632 -0
  147. package/src/components/timepicker/clockdial.ts +482 -0
  148. package/src/components/timepicker/config.ts +130 -0
  149. package/src/components/timepicker/constants.ts +138 -0
  150. package/src/components/timepicker/index.ts +8 -0
  151. package/src/components/timepicker/render.ts +613 -0
  152. package/src/components/timepicker/timepicker.ts +117 -0
  153. package/src/components/timepicker/types.ts +336 -0
  154. package/src/components/timepicker/utils.ts +241 -0
  155. package/src/components/top-app-bar/_styles.scss +225 -0
  156. package/src/components/top-app-bar/config.ts +83 -0
  157. package/src/components/top-app-bar/index.ts +11 -0
  158. package/src/components/top-app-bar/top-app-bar.ts +316 -0
  159. package/src/components/top-app-bar/types.ts +140 -0
  160. package/src/core/build/_ripple.scss +6 -6
  161. package/src/core/build/ripple.ts +72 -95
  162. package/src/core/compose/component.ts +1 -1
  163. package/src/core/compose/features/badge.ts +79 -0
  164. package/src/core/compose/features/icon.ts +3 -1
  165. package/src/core/compose/features/index.ts +3 -1
  166. package/src/core/compose/features/ripple.ts +4 -1
  167. package/src/core/compose/features/textlabel.ts +26 -2
  168. package/src/core/dom/create.ts +5 -0
  169. package/src/index.ts +9 -0
  170. package/src/styles/abstract/_theme.scss +115 -3
  171. package/src/styles/themes/_autumn.scss +21 -0
  172. package/src/styles/themes/_base-theme.scss +61 -0
  173. package/src/styles/themes/_baseline.scss +58 -0
  174. package/src/styles/themes/_bluekhaki.scss +125 -0
  175. package/src/styles/themes/_brownbeige.scss +125 -0
  176. package/src/styles/themes/_browngreen.scss +125 -0
  177. package/src/styles/themes/_forest.scss +6 -0
  178. package/src/styles/themes/_greenbeige.scss +125 -0
  179. package/src/styles/themes/_material.scss +125 -0
  180. package/src/styles/themes/_ocean.scss +6 -0
  181. package/src/styles/themes/_sageivory.scss +125 -0
  182. package/src/styles/themes/_spring.scss +6 -0
  183. package/src/styles/themes/_summer.scss +5 -0
  184. package/src/styles/themes/_sunset.scss +5 -0
  185. package/src/styles/themes/_tealcaramel.scss +125 -0
  186. package/src/styles/themes/_winter.scss +6 -0
  187. package/src/components/card/actions.ts +0 -48
  188. package/src/components/card/header.ts +0 -88
  189. package/src/components/card/media.ts +0 -52
  190. package/src/components/navigation/features/items.js +0 -192
  191. package/src/components/slider/features/appearance.ts +0 -94
  192. package/src/components/slider/features/disabled.ts +0 -43
  193. package/src/components/slider/features/events.ts +0 -164
  194. package/src/components/slider/features/interactions.ts +0 -261
  195. package/src/components/slider/features/keyboard.ts +0 -112
  196. package/src/core/collection/adapters/mongodb.js +0 -232
@@ -0,0 +1,67 @@
1
+ // src/components/segmented-button/config.ts
2
+ import { createComponentConfig } from '../../core/config/component-config';
3
+ import { SegmentedButtonConfig, SelectionMode } from './types';
4
+ import { DEFAULT_CONFIG, CLASSES } from './constants';
5
+
6
+ /**
7
+ * Creates the base configuration for Segmented Button component
8
+ * @param {SegmentedButtonConfig} config - User provided configuration
9
+ * @returns {SegmentedButtonConfig} Complete configuration with defaults applied
10
+ * @internal
11
+ */
12
+ export const createBaseConfig = (config: SegmentedButtonConfig = {}): SegmentedButtonConfig =>
13
+ createComponentConfig(DEFAULT_CONFIG, config, CLASSES.CONTAINER) as SegmentedButtonConfig;
14
+
15
+ /**
16
+ * Generates element configuration for the Segmented Button container
17
+ * @param {SegmentedButtonConfig} config - Segmented Button configuration
18
+ * @returns {Object} Element configuration object for withElement
19
+ * @internal
20
+ */
21
+ export const getContainerConfig = (config: SegmentedButtonConfig) => ({
22
+ tag: 'div',
23
+ componentName: CLASSES.CONTAINER,
24
+ attrs: {
25
+ role: 'group',
26
+ 'aria-label': 'Segmented button',
27
+ 'data-mode': config.mode || SelectionMode.SINGLE
28
+ },
29
+ className: [
30
+ config.class,
31
+ config.disabled ? `${config.prefix}-${CLASSES.CONTAINER}--disabled` : null
32
+ ],
33
+ interactive: true
34
+ });
35
+
36
+ /**
37
+ * Generates configuration for a segment element
38
+ * @param {Object} segment - Segment configuration
39
+ * @param {string} prefix - Component prefix
40
+ * @param {boolean} groupDisabled - Whether the entire group is disabled
41
+ * @returns {Object} Element configuration for the segment
42
+ * @internal
43
+ */
44
+ export const getSegmentConfig = (segment, prefix, groupDisabled = false) => {
45
+ const isDisabled = groupDisabled || segment.disabled;
46
+
47
+ return {
48
+ tag: 'button',
49
+ attrs: {
50
+ type: 'button',
51
+ role: 'button',
52
+ disabled: isDisabled ? true : undefined,
53
+ 'aria-pressed': segment.selected ? 'true' : 'false',
54
+ value: segment.value
55
+ },
56
+ className: [
57
+ `${prefix}-${CLASSES.SEGMENT}`,
58
+ segment.selected ? `${prefix}-${CLASSES.SEGMENT}--${CLASSES.SELECTED}` : null,
59
+ isDisabled ? `${prefix}-${CLASSES.SEGMENT}--${CLASSES.DISABLED}` : null,
60
+ segment.class
61
+ ],
62
+ forwardEvents: {
63
+ click: (component) => !isDisabled
64
+ },
65
+ interactive: !isDisabled
66
+ };
67
+ };
@@ -0,0 +1,42 @@
1
+ // src/components/segmented-button/constants.ts
2
+ import { SelectionMode } from './types';
3
+
4
+ /**
5
+ * Default checkbox icon SVG used for selected state
6
+ * @internal
7
+ */
8
+ export const DEFAULT_CHECKMARK_ICON = `
9
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
10
+ <polyline points="20 6 9 17 4 12"></polyline>
11
+ </svg>`;
12
+
13
+ /**
14
+ * Default configuration values for segmented buttons
15
+ * @internal
16
+ */
17
+ export const DEFAULT_CONFIG = {
18
+ mode: SelectionMode.SINGLE,
19
+ ripple: true
20
+ };
21
+
22
+ /**
23
+ * Event names used by the segmented button component
24
+ * @internal
25
+ */
26
+ export const EVENTS = {
27
+ CHANGE: 'change'
28
+ };
29
+
30
+ /**
31
+ * CSS classes used by the segmented button component
32
+ * @internal
33
+ */
34
+ export const CLASSES = {
35
+ CONTAINER: 'segmented-button',
36
+ SEGMENT: 'segmented-button-segment',
37
+ SELECTED: 'selected',
38
+ DISABLED: 'disabled',
39
+ ICON: 'icon',
40
+ CHECKMARK: 'checkmark',
41
+ TEXT: 'text'
42
+ };
@@ -0,0 +1,4 @@
1
+ // src/components/segmented-button/index.ts
2
+ export { default, default as createSegmentedButton } from './segmented-button';
3
+ export { SelectionMode } from './types';
4
+ export type { SegmentedButtonConfig, SegmentedButtonComponent, SegmentConfig, Segment } from './types';
@@ -0,0 +1,155 @@
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 { DEFAULT_CHECKMARK_ICON, CLASSES } from './constants';
6
+ import { getSegmentConfig } from './config';
7
+
8
+ /**
9
+ * Creates a segment for the segmented button
10
+ * @param {SegmentConfig} config - Segment configuration
11
+ * @param {HTMLElement} container - Container element
12
+ * @param {string} prefix - Component prefix
13
+ * @param {boolean} groupDisabled - Whether the entire group is disabled
14
+ * @param {Object} options - Additional options
15
+ * @returns {Segment} Segment object
16
+ * @internal
17
+ */
18
+ export const createSegment = (
19
+ config: SegmentConfig,
20
+ container: HTMLElement,
21
+ prefix: string,
22
+ groupDisabled = false,
23
+ options = { ripple: true, rippleConfig: {} }
24
+ ): Segment => {
25
+ const segmentConfig = getSegmentConfig(config, prefix, groupDisabled);
26
+ const element = createElement(segmentConfig);
27
+
28
+ // Add to container
29
+ container.appendChild(element);
30
+
31
+ // Create ripple effect if enabled
32
+ let ripple;
33
+ if (options.ripple) {
34
+ ripple = createRipple(options.rippleConfig);
35
+ ripple.mount(element);
36
+ }
37
+
38
+ // Create text element if provided
39
+ let textElement;
40
+ if (config.text) {
41
+ textElement = createElement({
42
+ tag: 'span',
43
+ className: `${prefix}-${CLASSES.SEGMENT}-${CLASSES.TEXT}`,
44
+ text: config.text,
45
+ container: element
46
+ });
47
+ }
48
+
49
+ // Create icon and checkmark elements
50
+ let iconElement, checkmarkElement;
51
+ if (config.icon) {
52
+ // Create icon element
53
+ iconElement = createElement({
54
+ tag: 'span',
55
+ className: `${prefix}-${CLASSES.SEGMENT}-${CLASSES.ICON}`,
56
+ html: config.icon,
57
+ container: element
58
+ });
59
+
60
+ // Create checkmark element (hidden initially)
61
+ checkmarkElement = createElement({
62
+ tag: 'span',
63
+ className: `${prefix}-${CLASSES.SEGMENT}-${CLASSES.CHECKMARK}`,
64
+ html: DEFAULT_CHECKMARK_ICON,
65
+ container: element
66
+ });
67
+
68
+ // Hide checkmark if not selected
69
+ if (!config.selected) {
70
+ checkmarkElement.style.display = 'none';
71
+ }
72
+
73
+ // Hide icon if selected and we have text (icon replaced by checkmark)
74
+ if (config.selected && config.text) {
75
+ iconElement.style.display = 'none';
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Updates the visual state based on selection
81
+ * @param {boolean} selected - Whether the segment is selected
82
+ * @private
83
+ */
84
+ const updateSelectedState = (selected: boolean) => {
85
+ element.classList.toggle(`${prefix}-${CLASSES.SEGMENT}--${CLASSES.SELECTED}`, selected);
86
+ element.setAttribute('aria-pressed', selected ? 'true' : 'false');
87
+
88
+ // Handle icon/checkmark swap if we have both text and icon
89
+ if (iconElement && checkmarkElement && config.text) {
90
+ iconElement.style.display = selected ? 'none' : '';
91
+ checkmarkElement.style.display = selected ? '' : 'none';
92
+ } else if (checkmarkElement) {
93
+ // If we have only icons (no text), show checkmark based on selection
94
+ checkmarkElement.style.display = selected ? '' : 'none';
95
+ }
96
+ };
97
+
98
+ /**
99
+ * Updates the disabled state
100
+ * @param {boolean} disabled - Whether the segment is disabled
101
+ * @private
102
+ */
103
+ const updateDisabledState = (disabled: boolean) => {
104
+ const isDisabled = disabled || groupDisabled;
105
+ element.classList.toggle(`${prefix}-${CLASSES.SEGMENT}--${CLASSES.DISABLED}`, isDisabled);
106
+
107
+ if (isDisabled) {
108
+ element.setAttribute('disabled', 'true');
109
+ } else {
110
+ element.removeAttribute('disabled');
111
+ }
112
+ };
113
+
114
+ // Value to use for the segment
115
+ const value = config.value || config.text || '';
116
+
117
+ // Initialize state
118
+ let isSelected = config.selected || false;
119
+ let isDisabled = config.disabled || false;
120
+
121
+ return {
122
+ element,
123
+ value,
124
+
125
+ isSelected() {
126
+ return isSelected;
127
+ },
128
+
129
+ setSelected(selected: boolean) {
130
+ isSelected = selected;
131
+ updateSelectedState(selected);
132
+ },
133
+
134
+ isDisabled() {
135
+ return isDisabled || groupDisabled;
136
+ },
137
+
138
+ setDisabled(disabled: boolean) {
139
+ isDisabled = disabled;
140
+ updateDisabledState(disabled);
141
+ },
142
+
143
+ destroy() {
144
+ // Clean up ripple if it exists
145
+ if (ripple) {
146
+ ripple.unmount(element);
147
+ }
148
+
149
+ // Remove from DOM
150
+ if (element.parentNode) {
151
+ element.parentNode.removeChild(element);
152
+ }
153
+ }
154
+ };
155
+ };
@@ -0,0 +1,250 @@
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
+ import { EVENTS } from './constants';
10
+
11
+ /**
12
+ * Creates a new Segmented Button component
13
+ * @param {SegmentedButtonConfig} config - Segmented Button configuration
14
+ * @returns {SegmentedButtonComponent} Segmented Button component instance
15
+ */
16
+ const createSegmentedButton = (config: SegmentedButtonConfig = {}): SegmentedButtonComponent => {
17
+ // Process configuration
18
+ const baseConfig = createBaseConfig(config);
19
+ const mode = baseConfig.mode || SelectionMode.SINGLE;
20
+ const emitter = createEmitter();
21
+
22
+ try {
23
+ // Create the base component
24
+ const component = pipe(
25
+ createBase,
26
+ withEvents(),
27
+ withElement(getContainerConfig(baseConfig)),
28
+ withLifecycle()
29
+ )(baseConfig);
30
+
31
+ // Create segments
32
+ const segments: Segment[] = [];
33
+ if (baseConfig.segments && baseConfig.segments.length) {
34
+ baseConfig.segments.forEach(segmentConfig => {
35
+ const segment = createSegment(
36
+ segmentConfig,
37
+ component.element,
38
+ baseConfig.prefix,
39
+ baseConfig.disabled,
40
+ {
41
+ ripple: baseConfig.ripple,
42
+ rippleConfig: baseConfig.rippleConfig
43
+ }
44
+ );
45
+
46
+ segments.push(segment);
47
+ });
48
+ }
49
+
50
+ // Ensure at least one item is selected in single-select mode
51
+ if (mode === SelectionMode.SINGLE && !segments.some(s => s.isSelected())) {
52
+ // Select the first non-disabled segment by default
53
+ const firstSelectable = segments.find(s => !s.isDisabled());
54
+ if (firstSelectable) {
55
+ firstSelectable.setSelected(true);
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Handles click events on segments
61
+ * @param {Event} event - DOM click event
62
+ * @private
63
+ */
64
+ const handleSegmentClick = (event: Event) => {
65
+ const segmentElement = event.currentTarget as HTMLElement;
66
+ const clickedSegment = segments.find(s => s.element === segmentElement);
67
+
68
+ if (!clickedSegment || clickedSegment.isDisabled()) {
69
+ return;
70
+ }
71
+
72
+ const oldValue = getSelectedValues();
73
+
74
+ // Handle selection based on mode
75
+ if (mode === SelectionMode.SINGLE) {
76
+ // In single-select, deselect all other segments
77
+ segments.forEach(segment => {
78
+ segment.setSelected(segment === clickedSegment);
79
+ });
80
+ } else {
81
+ // In multi-select, toggle the clicked segment
82
+ clickedSegment.setSelected(!clickedSegment.isSelected());
83
+ }
84
+
85
+ // Emit change event
86
+ const newValue = getSelectedValues();
87
+
88
+ // Only emit if values actually changed
89
+ if (
90
+ oldValue.length !== newValue.length ||
91
+ oldValue.some(v => !newValue.includes(v)) ||
92
+ newValue.some(v => !oldValue.includes(v))
93
+ ) {
94
+ emitter.emit(EVENTS.CHANGE, {
95
+ selected: getSelected(),
96
+ value: newValue,
97
+ oldValue
98
+ });
99
+ }
100
+ };
101
+
102
+ // Attach click handlers to segments
103
+ segments.forEach(segment => {
104
+ segment.element.addEventListener('click', handleSegmentClick);
105
+ });
106
+
107
+ /**
108
+ * Gets an array of selected segments
109
+ * @returns {Segment[]} Array of selected segments
110
+ * @private
111
+ */
112
+ const getSelected = () => segments.filter(segment => segment.isSelected());
113
+
114
+ /**
115
+ * Gets an array of selected segment values
116
+ * @returns {string[]} Array of selected segment values
117
+ * @private
118
+ */
119
+ const getSelectedValues = () => getSelected().map(segment => segment.value);
120
+
121
+ /**
122
+ * Finds a segment by its value
123
+ * @param {string} value - Segment value to find
124
+ * @returns {Segment|undefined} The found segment or undefined
125
+ * @private
126
+ */
127
+ const findSegmentByValue = (value: string) => segments.find(segment => segment.value === value);
128
+
129
+ // Create the component API
130
+ const segmentedButton: SegmentedButtonComponent = {
131
+ element: component.element,
132
+ segments,
133
+
134
+ getSelected,
135
+
136
+ getValue() {
137
+ return getSelectedValues();
138
+ },
139
+
140
+ select(value) {
141
+ const segment = findSegmentByValue(value);
142
+ if (segment && !segment.isDisabled()) {
143
+ const oldValue = getSelectedValues();
144
+
145
+ if (mode === SelectionMode.SINGLE) {
146
+ // Deselect all other segments
147
+ segments.forEach(s => s.setSelected(s === segment));
148
+ } else {
149
+ // Just select this segment
150
+ segment.setSelected(true);
151
+ }
152
+
153
+ // Emit change event
154
+ const newValue = getSelectedValues();
155
+ if (oldValue.join(',') !== newValue.join(',')) {
156
+ emitter.emit(EVENTS.CHANGE, {
157
+ selected: getSelected(),
158
+ value: newValue,
159
+ oldValue
160
+ });
161
+ }
162
+ }
163
+ return this;
164
+ },
165
+
166
+ deselect(value) {
167
+ const segment = findSegmentByValue(value);
168
+ if (segment && !segment.isDisabled()) {
169
+ // In single select mode, only deselect if there's another selected segment
170
+ if (mode === SelectionMode.SINGLE) {
171
+ const selectedSegments = getSelected();
172
+ // Only allow deselection if there's more than one selected or we're selecting a different segment
173
+ if (selectedSegments.length > 1 || !segment.isSelected()) {
174
+ const oldValue = getSelectedValues();
175
+ segment.setSelected(false);
176
+
177
+ // Emit change event
178
+ const newValue = getSelectedValues();
179
+ if (oldValue.join(',') !== newValue.join(',')) {
180
+ emitter.emit(EVENTS.CHANGE, {
181
+ selected: getSelected(),
182
+ value: newValue,
183
+ oldValue
184
+ });
185
+ }
186
+ }
187
+ } else {
188
+ // In multi-select, always allow deselection
189
+ const oldValue = getSelectedValues();
190
+ segment.setSelected(false);
191
+
192
+ // Emit change event
193
+ const newValue = getSelectedValues();
194
+ if (oldValue.join(',') !== newValue.join(',')) {
195
+ emitter.emit(EVENTS.CHANGE, {
196
+ selected: getSelected(),
197
+ value: newValue,
198
+ oldValue
199
+ });
200
+ }
201
+ }
202
+ }
203
+ return this;
204
+ },
205
+
206
+ enable() {
207
+ // Enable the entire component
208
+ component.element.classList.remove(`${baseConfig.prefix}-segmented-button--disabled`);
209
+ return this;
210
+ },
211
+
212
+ disable() {
213
+ // Disable the entire component
214
+ component.element.classList.add(`${baseConfig.prefix}-segmented-button--disabled`);
215
+ return this;
216
+ },
217
+
218
+ on(event, handler) {
219
+ emitter.on(event, handler);
220
+ return this;
221
+ },
222
+
223
+ off(event, handler) {
224
+ emitter.off(event, handler);
225
+ return this;
226
+ },
227
+
228
+ destroy() {
229
+ // Remove event listeners
230
+ segments.forEach(segment => {
231
+ segment.element.removeEventListener('click', handleSegmentClick);
232
+ segment.destroy();
233
+ });
234
+
235
+ // Clear emitter
236
+ emitter.clear();
237
+
238
+ // Destroy base component
239
+ component.lifecycle.destroy();
240
+ }
241
+ };
242
+
243
+ return segmentedButton;
244
+ } catch (error) {
245
+ console.error('Segmented Button creation error:', error);
246
+ throw new Error(`Failed to create segmented button: ${(error as Error).message}`);
247
+ }
248
+ };
249
+
250
+ export default createSegmentedButton;