mtrl 0.1.2 → 0.2.0

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 (220) hide show
  1. package/README.md +70 -22
  2. package/index.ts +33 -0
  3. package/package.json +14 -5
  4. package/src/components/button/{styles.scss → _styles.scss} +2 -2
  5. package/src/components/button/api.ts +89 -0
  6. package/src/components/button/button.ts +50 -0
  7. package/src/components/button/config.ts +75 -0
  8. package/src/components/button/constants.ts +17 -0
  9. package/src/components/button/index.ts +4 -0
  10. package/src/components/button/types.ts +118 -0
  11. package/src/components/card/_styles.scss +359 -0
  12. package/src/components/card/actions.ts +48 -0
  13. package/src/components/card/api.ts +102 -0
  14. package/src/components/card/card.ts +41 -0
  15. package/src/components/card/config.ts +99 -0
  16. package/src/components/card/constants.ts +69 -0
  17. package/src/components/card/content.ts +48 -0
  18. package/src/components/card/features.ts +228 -0
  19. package/src/components/card/header.ts +88 -0
  20. package/src/components/card/index.ts +19 -0
  21. package/src/components/card/media.ts +52 -0
  22. package/src/components/card/types.ts +174 -0
  23. package/src/components/checkbox/api.ts +82 -0
  24. package/src/components/checkbox/checkbox.ts +75 -0
  25. package/src/components/checkbox/config.ts +90 -0
  26. package/src/components/checkbox/index.ts +4 -0
  27. package/src/components/checkbox/types.ts +146 -0
  28. package/src/components/chip/_styles.scss +372 -0
  29. package/src/components/chip/api.ts +115 -0
  30. package/src/components/chip/chip-set.ts +225 -0
  31. package/src/components/chip/chip.ts +82 -0
  32. package/src/components/chip/config.ts +92 -0
  33. package/src/components/chip/constants.ts +38 -0
  34. package/src/components/chip/index.ts +4 -0
  35. package/src/components/chip/types.ts +172 -0
  36. package/src/components/list/api.ts +72 -0
  37. package/src/components/list/config.ts +43 -0
  38. package/src/components/list/{constants.js → constants.ts} +34 -7
  39. package/src/components/list/features.ts +224 -0
  40. package/src/components/list/index.ts +14 -0
  41. package/src/components/list/list-item.ts +120 -0
  42. package/src/components/list/list.ts +37 -0
  43. package/src/components/list/types.ts +179 -0
  44. package/src/components/list/utils.ts +47 -0
  45. package/src/components/menu/api.ts +119 -0
  46. package/src/components/menu/config.ts +54 -0
  47. package/src/components/menu/constants.ts +154 -0
  48. package/src/components/menu/features/items-manager.ts +457 -0
  49. package/src/components/menu/features/keyboard-navigation.ts +133 -0
  50. package/src/components/menu/features/positioning.ts +127 -0
  51. package/src/components/menu/features/{visibility.js → visibility.ts} +66 -64
  52. package/src/components/menu/index.ts +14 -0
  53. package/src/components/menu/menu-item.ts +43 -0
  54. package/src/components/menu/menu.ts +53 -0
  55. package/src/components/menu/types.ts +178 -0
  56. package/src/components/navigation/api.ts +79 -0
  57. package/src/components/navigation/config.ts +61 -0
  58. package/src/components/navigation/{constants.js → constants.ts} +10 -10
  59. package/src/components/navigation/index.ts +14 -0
  60. package/src/components/navigation/nav-item.ts +148 -0
  61. package/src/components/navigation/navigation.ts +50 -0
  62. package/src/components/navigation/types.ts +212 -0
  63. package/src/components/progress/_styles.scss +204 -0
  64. package/src/components/progress/api.ts +179 -0
  65. package/src/components/progress/config.ts +124 -0
  66. package/src/components/progress/constants.ts +43 -0
  67. package/src/components/progress/index.ts +5 -0
  68. package/src/components/progress/progress.ts +163 -0
  69. package/src/components/progress/types.ts +102 -0
  70. package/src/components/snackbar/api.ts +162 -0
  71. package/src/components/snackbar/config.ts +62 -0
  72. package/src/components/snackbar/{constants.js → constants.ts} +21 -4
  73. package/src/components/snackbar/features.ts +76 -0
  74. package/src/components/snackbar/index.ts +4 -0
  75. package/src/components/snackbar/position.ts +71 -0
  76. package/src/components/snackbar/queue.ts +76 -0
  77. package/src/components/snackbar/snackbar.ts +60 -0
  78. package/src/components/snackbar/types.ts +58 -0
  79. package/src/components/switch/api.ts +77 -0
  80. package/src/components/switch/config.ts +74 -0
  81. package/src/components/switch/index.ts +4 -0
  82. package/src/components/switch/switch.ts +52 -0
  83. package/src/components/switch/types.ts +142 -0
  84. package/src/components/textfield/api.ts +72 -0
  85. package/src/components/textfield/config.ts +54 -0
  86. package/src/components/textfield/{constants.js → constants.ts} +38 -5
  87. package/src/components/textfield/index.ts +4 -0
  88. package/src/components/textfield/textfield.ts +50 -0
  89. package/src/components/textfield/types.ts +139 -0
  90. package/src/core/compose/base.ts +43 -0
  91. package/src/core/compose/component.ts +247 -0
  92. package/src/core/compose/features/checkable.ts +155 -0
  93. package/src/core/compose/features/disabled.ts +116 -0
  94. package/src/core/compose/features/events.ts +65 -0
  95. package/src/core/compose/features/icon.ts +67 -0
  96. package/src/core/compose/features/index.ts +35 -0
  97. package/src/core/compose/features/input.ts +174 -0
  98. package/src/core/compose/features/lifecycle.ts +139 -0
  99. package/src/core/compose/features/position.ts +94 -0
  100. package/src/core/compose/features/ripple.ts +55 -0
  101. package/src/core/compose/features/size.ts +29 -0
  102. package/src/core/compose/features/style.ts +31 -0
  103. package/src/core/compose/features/text.ts +44 -0
  104. package/src/core/compose/features/textinput.ts +225 -0
  105. package/src/core/compose/features/textlabel.ts +92 -0
  106. package/src/core/compose/features/track.ts +84 -0
  107. package/src/core/compose/features/variant.ts +29 -0
  108. package/src/core/compose/features/withEvents.ts +137 -0
  109. package/src/core/compose/index.ts +54 -0
  110. package/src/core/compose/{pipe.js → pipe.ts} +16 -11
  111. package/src/core/config/component-config.ts +136 -0
  112. package/src/core/config.ts +211 -0
  113. package/src/core/dom/{attributes.js → attributes.ts} +11 -11
  114. package/src/core/dom/classes.ts +60 -0
  115. package/src/core/dom/create.ts +188 -0
  116. package/src/core/dom/events.ts +209 -0
  117. package/src/core/dom/index.ts +10 -0
  118. package/src/core/dom/utils.ts +97 -0
  119. package/src/core/index.ts +111 -0
  120. package/src/core/state/disabled.ts +81 -0
  121. package/src/core/state/emitter.ts +94 -0
  122. package/src/core/state/events.ts +88 -0
  123. package/src/core/state/index.ts +16 -0
  124. package/src/core/state/lifecycle.ts +131 -0
  125. package/src/core/state/store.ts +197 -0
  126. package/src/core/utils/index.ts +45 -0
  127. package/src/core/utils/{mobile.js → mobile.ts} +48 -24
  128. package/src/core/utils/object.ts +41 -0
  129. package/src/core/utils/validate.ts +234 -0
  130. package/src/{index.js → index.ts} +4 -2
  131. package/index.js +0 -11
  132. package/src/components/button/api.js +0 -54
  133. package/src/components/button/button.js +0 -81
  134. package/src/components/button/config.js +0 -10
  135. package/src/components/button/constants.js +0 -63
  136. package/src/components/button/index.js +0 -2
  137. package/src/components/checkbox/api.js +0 -45
  138. package/src/components/checkbox/checkbox.js +0 -96
  139. package/src/components/checkbox/index.js +0 -2
  140. package/src/components/container/api.js +0 -42
  141. package/src/components/container/container.js +0 -45
  142. package/src/components/container/index.js +0 -2
  143. package/src/components/container/styles.scss +0 -66
  144. package/src/components/list/index.js +0 -2
  145. package/src/components/list/list-item.js +0 -147
  146. package/src/components/list/list.js +0 -267
  147. package/src/components/menu/api.js +0 -117
  148. package/src/components/menu/constants.js +0 -42
  149. package/src/components/menu/features/items-manager.js +0 -375
  150. package/src/components/menu/features/keyboard-navigation.js +0 -129
  151. package/src/components/menu/features/positioning.js +0 -125
  152. package/src/components/menu/index.js +0 -2
  153. package/src/components/menu/menu-item.js +0 -41
  154. package/src/components/menu/menu.js +0 -54
  155. package/src/components/navigation/api.js +0 -43
  156. package/src/components/navigation/index.js +0 -2
  157. package/src/components/navigation/nav-item.js +0 -137
  158. package/src/components/navigation/navigation.js +0 -55
  159. package/src/components/snackbar/api.js +0 -125
  160. package/src/components/snackbar/features.js +0 -69
  161. package/src/components/snackbar/index.js +0 -2
  162. package/src/components/snackbar/position.js +0 -63
  163. package/src/components/snackbar/queue.js +0 -74
  164. package/src/components/snackbar/snackbar.js +0 -70
  165. package/src/components/switch/api.js +0 -44
  166. package/src/components/switch/index.js +0 -2
  167. package/src/components/switch/switch.js +0 -71
  168. package/src/components/textfield/api.js +0 -49
  169. package/src/components/textfield/index.js +0 -2
  170. package/src/components/textfield/textfield.js +0 -68
  171. package/src/core/build/_ripple.scss +0 -79
  172. package/src/core/build/constants.js +0 -51
  173. package/src/core/build/icon.js +0 -78
  174. package/src/core/build/ripple.js +0 -159
  175. package/src/core/build/text.js +0 -54
  176. package/src/core/compose/base.js +0 -8
  177. package/src/core/compose/component.js +0 -225
  178. package/src/core/compose/features/checkable.js +0 -114
  179. package/src/core/compose/features/disabled.js +0 -64
  180. package/src/core/compose/features/events.js +0 -48
  181. package/src/core/compose/features/icon.js +0 -33
  182. package/src/core/compose/features/index.js +0 -20
  183. package/src/core/compose/features/input.js +0 -100
  184. package/src/core/compose/features/lifecycle.js +0 -69
  185. package/src/core/compose/features/position.js +0 -60
  186. package/src/core/compose/features/ripple.js +0 -32
  187. package/src/core/compose/features/size.js +0 -9
  188. package/src/core/compose/features/style.js +0 -12
  189. package/src/core/compose/features/text.js +0 -17
  190. package/src/core/compose/features/textinput.js +0 -114
  191. package/src/core/compose/features/textlabel.js +0 -28
  192. package/src/core/compose/features/track.js +0 -49
  193. package/src/core/compose/features/variant.js +0 -9
  194. package/src/core/compose/features/withEvents.js +0 -67
  195. package/src/core/compose/index.js +0 -16
  196. package/src/core/config.js +0 -140
  197. package/src/core/dom/classes.js +0 -70
  198. package/src/core/dom/create.js +0 -132
  199. package/src/core/dom/events.js +0 -175
  200. package/src/core/dom/index.js +0 -5
  201. package/src/core/dom/utils.js +0 -22
  202. package/src/core/index.js +0 -23
  203. package/src/core/state/disabled.js +0 -51
  204. package/src/core/state/emitter.js +0 -63
  205. package/src/core/state/events.js +0 -29
  206. package/src/core/state/index.js +0 -6
  207. package/src/core/state/lifecycle.js +0 -64
  208. package/src/core/state/store.js +0 -112
  209. package/src/core/utils/index.js +0 -39
  210. package/src/core/utils/object.js +0 -22
  211. package/src/core/utils/validate.js +0 -37
  212. /package/src/components/checkbox/{styles.scss → _styles.scss} +0 -0
  213. /package/src/components/checkbox/{constants.js → constants.ts} +0 -0
  214. /package/src/components/list/{styles.scss → _styles.scss} +0 -0
  215. /package/src/components/menu/{styles.scss → _styles.scss} +0 -0
  216. /package/src/components/navigation/{styles.scss → _styles.scss} +0 -0
  217. /package/src/components/snackbar/{styles.scss → _styles.scss} +0 -0
  218. /package/src/components/switch/{styles.scss → _styles.scss} +0 -0
  219. /package/src/components/switch/{constants.js → constants.ts} +0 -0
  220. /package/src/components/textfield/{styles.scss → _styles.scss} +0 -0
@@ -0,0 +1,225 @@
1
+ // src/core/compose/features/textinput.ts
2
+
3
+ import { BaseComponent, ElementComponent } from '../component';
4
+
5
+ /**
6
+ * Configuration for text input feature
7
+ */
8
+ export interface TextInputConfig {
9
+ /**
10
+ * Input type (text, password, etc.)
11
+ */
12
+ type?: string;
13
+
14
+ /**
15
+ * Whether to use textarea instead of input
16
+ */
17
+ multiline?: boolean;
18
+
19
+ /**
20
+ * Input name attribute
21
+ */
22
+ name?: string;
23
+
24
+ /**
25
+ * Whether input is required
26
+ */
27
+ required?: boolean;
28
+
29
+ /**
30
+ * Whether input is disabled
31
+ */
32
+ disabled?: boolean;
33
+
34
+ /**
35
+ * Maximum allowed length
36
+ */
37
+ maxLength?: number;
38
+
39
+ /**
40
+ * Input validation pattern
41
+ */
42
+ pattern?: string;
43
+
44
+ /**
45
+ * Autocomplete setting
46
+ */
47
+ autocomplete?: string;
48
+
49
+ /**
50
+ * Initial input value
51
+ */
52
+ value?: string;
53
+
54
+ [key: string]: any;
55
+ }
56
+
57
+ /**
58
+ * Component with text input capabilities
59
+ */
60
+ export interface TextInputComponent extends ElementComponent {
61
+ /**
62
+ * Input element
63
+ */
64
+ input: HTMLInputElement | HTMLTextAreaElement;
65
+
66
+ /**
67
+ * Sets the input value
68
+ * @param value - Value to set
69
+ * @returns Component instance for chaining
70
+ */
71
+ setValue: (value: string) => TextInputComponent;
72
+
73
+ /**
74
+ * Gets the current input value
75
+ * @returns Current value
76
+ */
77
+ getValue: () => string;
78
+
79
+ /**
80
+ * Sets an attribute on the input
81
+ * @param name - Attribute name
82
+ * @param value - Attribute value
83
+ * @returns Component instance for chaining
84
+ */
85
+ setAttribute: (name: string, value: string) => TextInputComponent;
86
+
87
+ /**
88
+ * Gets an attribute from the input
89
+ * @param name - Attribute name
90
+ * @returns Attribute value
91
+ */
92
+ getAttribute: (name: string) => string | null;
93
+
94
+ /**
95
+ * Removes an attribute from the input
96
+ * @param name - Attribute name
97
+ * @returns Component instance for chaining
98
+ */
99
+ removeAttribute: (name: string) => TextInputComponent;
100
+
101
+ /**
102
+ * Event emission method if available
103
+ */
104
+ emit?: (event: string, data: any) => TextInputComponent;
105
+ }
106
+
107
+ /**
108
+ * Enhances a component with text input functionality
109
+ *
110
+ * @param config - Text input configuration
111
+ * @returns Function that enhances a component with text input capabilities
112
+ */
113
+ export const withTextInput = <T extends TextInputConfig>(config: T = {} as T) =>
114
+ <C extends ElementComponent>(component: C): C & TextInputComponent => {
115
+ const input = document.createElement(config.multiline ? 'textarea' : 'input') as
116
+ HTMLInputElement | HTMLTextAreaElement;
117
+
118
+ input.className = `${component.getClass('textfield')}-input`;
119
+
120
+ // Set input attributes
121
+ const attributes: Record<string, string | number | boolean | undefined> = {
122
+ type: config.multiline ? undefined : (config.type || 'text'),
123
+ name: config.name,
124
+ required: config.required,
125
+ disabled: config.disabled,
126
+ maxLength: config.maxLength,
127
+ pattern: config.pattern,
128
+ autocomplete: config.autocomplete,
129
+ value: config.value || ''
130
+ };
131
+
132
+ Object.entries(attributes).forEach(([key, value]) => {
133
+ if (value !== null && value !== undefined) {
134
+ if (typeof value === 'boolean') {
135
+ if (value) {
136
+ input.setAttribute(key, '');
137
+ }
138
+ } else {
139
+ input.setAttribute(key, String(value));
140
+ }
141
+ }
142
+ });
143
+
144
+ // Handle input state changes
145
+ const updateInputState = (): boolean => {
146
+ const isEmpty = !input.value;
147
+ component.element.classList.toggle(`${component.getClass('textfield')}--empty`, isEmpty);
148
+ return isEmpty;
149
+ };
150
+
151
+ // Detect autofill using input events instead of animation
152
+ // This is more compatible with our testing environment
153
+ const handleAutofill = (): void => {
154
+ // Check for webkit autofill background
155
+ const isAutofilled =
156
+ input.matches(':-webkit-autofill') ||
157
+ // For Firefox and other browsers
158
+ (window.getComputedStyle(input).backgroundColor === 'rgb(250, 255, 189)' ||
159
+ window.getComputedStyle(input).backgroundColor === 'rgb(232, 240, 254)');
160
+
161
+ if (isAutofilled) {
162
+ component.element.classList.remove(`${component.getClass('textfield')}--empty`);
163
+ component.emit?.('input', { value: input.value, isEmpty: false, isAutofilled: true });
164
+ }
165
+ };
166
+
167
+ // Event listeners
168
+ input.addEventListener('focus', () => {
169
+ component.element.classList.add(`${component.getClass('textfield')}--focused`);
170
+ component.emit?.('focus', { isEmpty: updateInputState() });
171
+ // Also check for autofill on focus
172
+ setTimeout(handleAutofill, 100);
173
+ });
174
+
175
+ input.addEventListener('blur', () => {
176
+ component.element.classList.remove(`${component.getClass('textfield')}--focused`);
177
+ component.emit?.('blur', { isEmpty: updateInputState() });
178
+ });
179
+
180
+ input.addEventListener('input', () => {
181
+ component.emit?.('input', {
182
+ value: input.value,
183
+ isEmpty: updateInputState(),
184
+ isAutofilled: false
185
+ });
186
+ });
187
+
188
+ // Initial state
189
+ updateInputState();
190
+
191
+ component.element.appendChild(input);
192
+
193
+ // Cleanup
194
+ if ('lifecycle' in component && component.lifecycle?.destroy) {
195
+ const originalDestroy = component.lifecycle.destroy;
196
+ component.lifecycle.destroy = () => {
197
+ input.remove();
198
+ originalDestroy.call(component.lifecycle);
199
+ };
200
+ }
201
+
202
+ return {
203
+ ...component,
204
+ input,
205
+ setValue(value: string) {
206
+ input.value = value || '';
207
+ updateInputState();
208
+ return this;
209
+ },
210
+ getValue() {
211
+ return input.value;
212
+ },
213
+ setAttribute(name: string, value: string) {
214
+ input.setAttribute(name, value);
215
+ return this;
216
+ },
217
+ getAttribute(name: string) {
218
+ return input.getAttribute(name);
219
+ },
220
+ removeAttribute(name: string) {
221
+ input.removeAttribute(name);
222
+ return this;
223
+ }
224
+ };
225
+ };
@@ -0,0 +1,92 @@
1
+ // src/core/compose/features/textlabel.ts
2
+
3
+ import { BaseComponent, ElementComponent } from '../component';
4
+
5
+ /**
6
+ * Configuration for text label feature
7
+ */
8
+ export interface TextLabelConfig {
9
+ /**
10
+ * Label text
11
+ */
12
+ label?: string;
13
+
14
+ /**
15
+ * CSS class prefix
16
+ */
17
+ prefix?: string;
18
+
19
+ /**
20
+ * Component name for class generation
21
+ */
22
+ componentName?: string;
23
+
24
+ [key: string]: any;
25
+ }
26
+
27
+ /**
28
+ * Label manager interface
29
+ */
30
+ export interface LabelManager {
31
+ /**
32
+ * Sets the label text
33
+ * @param text - Text to set
34
+ * @returns LabelManager instance for chaining
35
+ */
36
+ setText: (text: string) => LabelManager;
37
+
38
+ /**
39
+ * Gets the current label text
40
+ * @returns Current text
41
+ */
42
+ getText: () => string;
43
+
44
+ /**
45
+ * Gets the label element
46
+ * @returns Label element
47
+ */
48
+ getElement: () => HTMLElement;
49
+ }
50
+
51
+ /**
52
+ * Component with label capabilities
53
+ */
54
+ export interface LabelComponent extends BaseComponent {
55
+ label: LabelManager;
56
+ }
57
+
58
+ /**
59
+ * Adds a text label to a component
60
+ *
61
+ * @param config - Configuration object containing label information
62
+ * @returns Function that enhances a component with a label
63
+ */
64
+ export const withTextLabel = <T extends TextLabelConfig>(config: T = {} as T) =>
65
+ <C extends ElementComponent>(component: C): C & LabelComponent => {
66
+ if (!config.label) return component as C & LabelComponent;
67
+
68
+ const labelElement = document.createElement('label');
69
+ labelElement.className = `${config.prefix}-${config.componentName}-label`;
70
+ labelElement.textContent = config.label;
71
+
72
+ // Insert label after input for proper z-index stacking
73
+ component.element.appendChild(labelElement);
74
+
75
+ const label: LabelManager = {
76
+ setText(text: string) {
77
+ labelElement.textContent = text;
78
+ return this;
79
+ },
80
+ getText() {
81
+ return labelElement.textContent || '';
82
+ },
83
+ getElement() {
84
+ return labelElement;
85
+ }
86
+ };
87
+
88
+ return {
89
+ ...component,
90
+ label
91
+ };
92
+ };
@@ -0,0 +1,84 @@
1
+ // src/core/compose/features/track.ts
2
+ /**
3
+ * @module core/compose/features
4
+ */
5
+
6
+ import { BaseComponent, ElementComponent } from '../component';
7
+
8
+ /**
9
+ * Configuration for track feature
10
+ */
11
+ export interface TrackConfig {
12
+ /**
13
+ * CSS class prefix
14
+ */
15
+ prefix: string;
16
+
17
+ /**
18
+ * Component name for class generation
19
+ */
20
+ componentName: string;
21
+
22
+ /**
23
+ * Custom icon HTML or 'none'
24
+ */
25
+ icon?: string;
26
+
27
+ [key: string]: any;
28
+ }
29
+
30
+ /**
31
+ * Component with track and thumb elements
32
+ */
33
+ export interface TrackComponent extends BaseComponent {
34
+ /**
35
+ * Track element
36
+ */
37
+ track: HTMLElement;
38
+
39
+ /**
40
+ * Thumb element
41
+ */
42
+ thumb: HTMLElement;
43
+ }
44
+
45
+ /**
46
+ * Default checkmark icon SVG
47
+ * @private
48
+ */
49
+ const DEFAULT_ICON = `
50
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
51
+ <polyline points="20 6 9 17 4 12"></polyline>
52
+ </svg>`;
53
+
54
+ /**
55
+ * Adds track and thumb elements to a component
56
+ *
57
+ * @param config - Track configuration
58
+ * @returns Function that enhances a component with track and thumb elements
59
+ */
60
+ export const withTrack = <T extends TrackConfig>(config: T) =>
61
+ <C extends ElementComponent>(component: C): C & TrackComponent => {
62
+ const track = document.createElement('span');
63
+ track.className = `${config.prefix}-${config.componentName}-track`;
64
+
65
+ const thumb = document.createElement('span');
66
+ thumb.className = `${config.prefix}-${config.componentName}-thumb`;
67
+ track.appendChild(thumb);
68
+
69
+ // Add icon inside thumb if provided or use default
70
+ if (config.icon !== 'none') {
71
+ const icon = document.createElement('span');
72
+ icon.className = `${config.prefix}-${config.componentName}-thumb-icon`;
73
+ icon.innerHTML = config.icon || DEFAULT_ICON;
74
+ thumb.appendChild(icon);
75
+ }
76
+
77
+ component.element.appendChild(track);
78
+
79
+ return {
80
+ ...component,
81
+ track,
82
+ thumb
83
+ };
84
+ };
@@ -0,0 +1,29 @@
1
+ // src/core/compose/features/variant.ts
2
+
3
+ import { BaseComponent, ElementComponent } from '../component';
4
+
5
+ /**
6
+ * Configuration for variant feature
7
+ */
8
+ export interface VariantConfig {
9
+ variant?: string;
10
+ prefix?: string;
11
+ componentName?: string;
12
+ [key: string]: any;
13
+ }
14
+
15
+ /**
16
+ * Adds a variant class to a component
17
+ *
18
+ * @param config - Configuration object containing variant information
19
+ * @returns Function that enhances a component with the variant class
20
+ */
21
+ export const withVariant = <T extends VariantConfig>(config: T) =>
22
+ <C extends ElementComponent>(component: C): C => {
23
+ if (config.variant && component.element) {
24
+ // Use config.componentName since we know it's there
25
+ const className = `${config.prefix}-${config.componentName}--${config.variant}`;
26
+ component.element.classList.add(className);
27
+ }
28
+ return component;
29
+ };
@@ -0,0 +1,137 @@
1
+ // src/core/compose/features/withEvents.ts
2
+
3
+ import { createEventManager } from '../../state/events';
4
+ import { BaseComponent, ElementComponent } from '../component';
5
+
6
+ /**
7
+ * Event manager interface
8
+ */
9
+ export interface EnhancedEventManager {
10
+ /**
11
+ * Add an event listener
12
+ */
13
+ on: (event: string, handler: Function) => EnhancedEventManager;
14
+
15
+ /**
16
+ * Remove an event listener
17
+ */
18
+ off: (event: string, handler: Function) => EnhancedEventManager;
19
+
20
+ /**
21
+ * Add multiple event listeners at once
22
+ */
23
+ addListeners: (listeners: Record<string, Function>) => EnhancedEventManager;
24
+
25
+ /**
26
+ * Remove multiple event listeners at once
27
+ */
28
+ removeListeners: (listeners: Record<string, Function>) => EnhancedEventManager;
29
+
30
+ /**
31
+ * One-time event handler
32
+ */
33
+ once: (event: string, handler: Function) => EnhancedEventManager;
34
+
35
+ /**
36
+ * Clean up all event listeners
37
+ */
38
+ destroy: () => void;
39
+ }
40
+
41
+ /**
42
+ * Component with enhanced event capabilities
43
+ */
44
+ export interface EnhancedEventComponent extends BaseComponent {
45
+ events: EnhancedEventManager;
46
+ on: (event: string, handler: Function) => EnhancedEventComponent;
47
+ off: (event: string, handler: Function) => EnhancedEventComponent;
48
+ }
49
+
50
+ /**
51
+ * Adds enhanced event handling capabilities to a component
52
+ *
53
+ * @param target - Optional custom event target
54
+ * @returns Function that enhances a component with event capabilities
55
+ */
56
+ export const withEvents = (target?: HTMLElement) =>
57
+ <C extends ElementComponent>(component: C): C & EnhancedEventComponent => {
58
+ const events = createEventManager(target || component.element);
59
+
60
+ // Enhanced event methods
61
+ const enhancedEvents: EnhancedEventManager = {
62
+ /**
63
+ * Add multiple event listeners at once
64
+ * @param listeners - Map of event types to handlers
65
+ */
66
+ addListeners(listeners: Record<string, Function>) {
67
+ Object.entries(listeners).forEach(([event, handler]) => {
68
+ events.on(event, handler as any);
69
+ });
70
+ return this;
71
+ },
72
+
73
+ /**
74
+ * Remove multiple event listeners at once
75
+ * @param listeners - Map of event types to handlers
76
+ */
77
+ removeListeners(listeners: Record<string, Function>) {
78
+ Object.entries(listeners).forEach(([event, handler]) => {
79
+ events.off(event, handler as any);
80
+ });
81
+ return this;
82
+ },
83
+
84
+ /**
85
+ * One-time event handler
86
+ * @param event - Event name
87
+ * @param handler - Event handler
88
+ */
89
+ once(event: string, handler: Function) {
90
+ const wrappedHandler = (e: Event) => {
91
+ handler(e);
92
+ events.off(event, wrappedHandler as any);
93
+ };
94
+ events.on(event, wrappedHandler as any);
95
+ return this;
96
+ },
97
+
98
+ /**
99
+ * Add an event listener
100
+ */
101
+ on(event: string, handler: Function) {
102
+ events.on(event, handler as any);
103
+ return this;
104
+ },
105
+
106
+ /**
107
+ * Remove an event listener
108
+ */
109
+ off(event: string, handler: Function) {
110
+ events.off(event, handler as any);
111
+ return this;
112
+ },
113
+
114
+ /**
115
+ * Clean up all event listeners
116
+ */
117
+ destroy() {
118
+ events.destroy();
119
+ }
120
+ };
121
+
122
+ // Add lifecycle integration
123
+ if ('lifecycle' in component && component.lifecycle?.destroy) {
124
+ const originalDestroy = component.lifecycle.destroy;
125
+ component.lifecycle.destroy = () => {
126
+ events.destroy();
127
+ originalDestroy.call(component.lifecycle);
128
+ };
129
+ }
130
+
131
+ return {
132
+ ...component,
133
+ events: enhancedEvents,
134
+ on: enhancedEvents.on.bind(enhancedEvents),
135
+ off: enhancedEvents.off.bind(enhancedEvents)
136
+ };
137
+ };
@@ -0,0 +1,54 @@
1
+ // src/core/compose/index.ts
2
+ /**
3
+ * @module core/compose
4
+ * @description Core composition utilities for creating and combining components
5
+ */
6
+
7
+ export { pipe, compose, transform } from './pipe';
8
+ export { createComponent } from './base';
9
+ export { createBase, withElement } from './component';
10
+ export {
11
+ withEvents,
12
+ withIcon,
13
+ withSize,
14
+ withPosition,
15
+ withText,
16
+ withVariant,
17
+ withDisabled,
18
+ withLifecycle,
19
+ withRipple,
20
+ withInput,
21
+ withCheckable,
22
+ withStyle,
23
+ withTextInput,
24
+ withTextLabel,
25
+ withTrack,
26
+ withEnhancedEvents
27
+ } from './features';
28
+
29
+ // Component feature interfaces
30
+ export type { Component } from './base';
31
+ export type {
32
+ BaseComponent,
33
+ ElementComponent,
34
+ TouchState,
35
+ WithElementOptions
36
+ } from './component';
37
+
38
+ export type {
39
+ EventComponent,
40
+ TextComponent,
41
+ IconComponent,
42
+ LifecycleComponent,
43
+ Lifecycle,
44
+ DisabledComponent,
45
+ DisabledManager,
46
+ RippleComponent,
47
+ InputComponent,
48
+ CheckableComponent,
49
+ CheckableManager,
50
+ TextInputComponent,
51
+ LabelComponent,
52
+ TrackComponent,
53
+ EnhancedEventComponent
54
+ } from './features';
@@ -1,6 +1,4 @@
1
- // @file src/core/compose/pipe.js
2
- // @module core/compose
3
-
1
+ // src/core/compose/pipe.ts
4
2
  /**
5
3
  * @namespace compose
6
4
  * @description Core composition utilities for creating and combining components
@@ -21,7 +19,8 @@
21
19
  * const addOneThenDouble = pipe(addOne, double);
22
20
  * console.log(addOneThenDouble(3)); // Output: 8
23
21
  */
24
- export const pipe = (...fns) => (x) => fns.reduce((v, f) => f(v), x)
22
+ export const pipe = <T>(...fns: Array<(arg: any) => any>) =>
23
+ (x: T): any => fns.reduce((v, f) => f(v), x);
25
24
 
26
25
  /**
27
26
  * Performs right-to-left function composition.
@@ -38,7 +37,8 @@ export const pipe = (...fns) => (x) => fns.reduce((v, f) => f(v), x)
38
37
  * const doubleTheAddOne = compose(addOne, double);
39
38
  * console.log(doubleTheAddOne(3)); // Output: 7
40
39
  */
41
- export const compose = (...fns) => (x) => fns.reduceRight((v, f) => f(v), x)
40
+ export const compose = <T>(...fns: Array<(arg: any) => any>) =>
41
+ (x: T): any => fns.reduceRight((v, f) => f(v), x);
42
42
 
43
43
  /**
44
44
  * Creates a function that applies transformations to an object with shared context.
@@ -57,13 +57,18 @@ export const compose = (...fns) => (x) => fns.reduceRight((v, f) => f(v), x)
57
57
  * const person = createPerson({}, { name: 'John', age: 30 });
58
58
  * // Result: { name: 'John', age: 30 }
59
59
  */
60
- export const transform = (...transformers) => (obj, context = {}) =>
61
- transformers.reduce((acc, transformer) => ({
62
- ...acc,
63
- ...transformer(acc, context)
64
- }), obj)
60
+ export const transform = <T, C = Record<string, any>>(
61
+ ...transformers: Array<(obj: T, context: C) => Partial<T>>
62
+ ) => (obj: T, context: C = {} as C): T =>
63
+ transformers.reduce(
64
+ (acc, transformer) => ({
65
+ ...acc,
66
+ ...transformer(acc, context)
67
+ }),
68
+ obj
69
+ );
65
70
 
66
71
  /**
67
72
  * @typedef {Object} TransformContext
68
73
  * @property {any} [key] - Any contextual data needed by transformers
69
- */
74
+ */