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,247 @@
1
+ // src/core/compose/component.ts
2
+ /**
3
+ * @module core/compose/component
4
+ * @description Core utilities for component composition and creation with built-in mobile support
5
+ */
6
+
7
+ import { createElement, CreateElementOptions } from '../dom/create';
8
+ import {
9
+ normalizeEvent,
10
+ hasTouchSupport,
11
+ TOUCH_CONFIG,
12
+ PASSIVE_EVENTS
13
+ } from '../utils/mobile';
14
+
15
+ /**
16
+ * Touch state interface to track touch interactions
17
+ */
18
+ export interface TouchState {
19
+ startTime: number;
20
+ startPosition: { x: number; y: number };
21
+ isTouching: boolean;
22
+ activeTarget: EventTarget | null;
23
+ }
24
+
25
+ /**
26
+ * Base component interface with prefix utilities
27
+ */
28
+ export interface BaseComponent {
29
+ config: Record<string, any>;
30
+ componentName?: string;
31
+ getClass: (name: string) => string;
32
+ getModifierClass: (base: string, modifier: string) => string;
33
+ getElementClass: (base: string, element: string) => string;
34
+ touchState: TouchState;
35
+ updateTouchState: (event: Event, status: 'start' | 'end') => void;
36
+ }
37
+
38
+ /**
39
+ * Element component extends base with element
40
+ */
41
+ export interface ElementComponent extends BaseComponent {
42
+ element: HTMLElement;
43
+ addClass: (...classes: string[]) => ElementComponent;
44
+ destroy: () => void;
45
+ }
46
+
47
+ /**
48
+ * Options for withElement enhancer
49
+ */
50
+ export interface WithElementOptions {
51
+ tag?: string;
52
+ componentName?: string;
53
+ attrs?: Record<string, any>;
54
+ className?: string | string[];
55
+ forwardEvents?: Record<string, boolean | ((component: any, event: Event) => boolean)>;
56
+ interactive?: boolean;
57
+ }
58
+
59
+ /**
60
+ * Creates helper functions for managing CSS class names with a prefix
61
+ * @param {string} prefix - Prefix to apply to class names
62
+ * @returns {Object} Class name utilities
63
+ */
64
+ const withPrefix = (prefix: string) => ({
65
+ /**
66
+ * Gets a prefixed class name
67
+ * @param {string} name - Base class name
68
+ * @returns {string} Prefixed class name
69
+ */
70
+ getClass: (name: string): string => `${prefix}-${name}`,
71
+
72
+ /**
73
+ * Gets a prefixed modifier class name
74
+ * @param {string} base - Base class name
75
+ * @param {string} modifier - Modifier name
76
+ * @returns {string} Prefixed modifier class
77
+ */
78
+ getModifierClass: (base: string, modifier: string): string => `${base}--${modifier}`,
79
+
80
+ /**
81
+ * Gets a prefixed element class name
82
+ * @param {string} base - Base class name
83
+ * @param {string} element - Element name
84
+ * @returns {string} Prefixed element class
85
+ */
86
+ getElementClass: (base: string, element: string): string => `${base}-${element}`
87
+ });
88
+
89
+ /**
90
+ * Creates a base component with configuration and prefix utilities.
91
+ * This forms the foundation for all components in the system.
92
+ *
93
+ * @param {Object} config - Component configuration
94
+ * @returns {BaseComponent} Base component with prefix utilities
95
+ */
96
+ export const createBase = (config: Record<string, any> = {}): BaseComponent => ({
97
+ config,
98
+ componentName: config.componentName,
99
+ ...withPrefix(config.prefix || 'mtrl'),
100
+
101
+ /**
102
+ * Manages the touch interaction state for the component.
103
+ */
104
+ touchState: {
105
+ startTime: 0,
106
+ startPosition: { x: 0, y: 0 },
107
+ isTouching: false,
108
+ activeTarget: null
109
+ },
110
+
111
+ /**
112
+ * Updates the component's touch state based on user interactions.
113
+ * Tracks touch position and timing for gesture recognition.
114
+ */
115
+ updateTouchState(event: Event, status: 'start' | 'end'): void {
116
+ const normalized = normalizeEvent(event);
117
+
118
+ if (status === 'start') {
119
+ this.touchState = {
120
+ startTime: Date.now(),
121
+ startPosition: {
122
+ x: normalized.clientX,
123
+ y: normalized.clientY
124
+ },
125
+ isTouching: true,
126
+ activeTarget: normalized.target
127
+ };
128
+ } else if (status === 'end') {
129
+ this.touchState.isTouching = false;
130
+ this.touchState.activeTarget = null;
131
+ }
132
+ }
133
+ });
134
+
135
+ /**
136
+ * Higher-order function that adds a DOM element to a component
137
+ * @param {WithElementOptions} options - Element creation options
138
+ * @returns {Function} Component enhancer
139
+ */
140
+ export const withElement = (options: WithElementOptions = {}) =>
141
+ (base: BaseComponent): ElementComponent => {
142
+ /**
143
+ * Handles the start of a touch interaction.
144
+ */
145
+ const handleTouchStart = (event: Event): void => {
146
+ base.updateTouchState(event, 'start');
147
+ element.classList.add(`${base.getClass('touch-active')}`);
148
+
149
+ if (options.forwardEvents?.touchstart && base.config?.emit) {
150
+ base.config.emit('touchstart', normalizeEvent(event));
151
+ }
152
+ };
153
+
154
+ /**
155
+ * Handles the end of a touch interaction.
156
+ */
157
+ const handleTouchEnd = (event: Event): void => {
158
+ if (!base.touchState.isTouching) return;
159
+
160
+ const touchDuration = Date.now() - base.touchState.startTime;
161
+ element.classList.remove(`${base.getClass('touch-active')}`);
162
+ base.updateTouchState(event, 'end');
163
+
164
+ // Emit tap event for short touches
165
+ if (touchDuration < TOUCH_CONFIG.TAP_THRESHOLD && base.config?.emit) {
166
+ base.config.emit('tap', normalizeEvent(event));
167
+ }
168
+
169
+ if (options.forwardEvents?.touchend && base.config?.emit) {
170
+ base.config.emit('touchend', normalizeEvent(event));
171
+ }
172
+ };
173
+
174
+ /**
175
+ * Handles touch movement.
176
+ */
177
+ const handleTouchMove = (event: Event): void => {
178
+ if (!base.touchState.isTouching) return;
179
+
180
+ const normalized = normalizeEvent(event);
181
+ const deltaX = normalized.clientX - base.touchState.startPosition.x;
182
+ const deltaY = normalized.clientY - base.touchState.startPosition.y;
183
+
184
+ // Detect and emit swipe gestures
185
+ if (Math.abs(deltaX) > TOUCH_CONFIG.SWIPE_THRESHOLD && base.config?.emit) {
186
+ base.config.emit('swipe', {
187
+ direction: deltaX > 0 ? 'right' : 'left',
188
+ deltaX,
189
+ deltaY
190
+ });
191
+ }
192
+
193
+ if (options.forwardEvents?.touchmove && base.config?.emit) {
194
+ base.config.emit('touchmove', { ...normalized, deltaX, deltaY });
195
+ }
196
+ };
197
+
198
+ // Create element options from component options
199
+ const elementOptions: CreateElementOptions = {
200
+ tag: options.tag || 'div',
201
+ className: [
202
+ base.getClass(options.componentName || base.componentName || 'component'),
203
+ hasTouchSupport() && options.interactive ? base.getClass('interactive') : null,
204
+ options.className
205
+ ].filter(Boolean),
206
+ attrs: options.attrs || {},
207
+ context: base
208
+ };
209
+
210
+ // Create the element with appropriate classes
211
+ const element = createElement(elementOptions);
212
+
213
+ // Add event listeners only if touch is supported and the component is interactive
214
+ if (hasTouchSupport() && options.interactive) {
215
+ element.addEventListener('touchstart', handleTouchStart, PASSIVE_EVENTS);
216
+ element.addEventListener('touchend', handleTouchEnd);
217
+ element.addEventListener('touchmove', handleTouchMove, PASSIVE_EVENTS);
218
+ }
219
+
220
+ return {
221
+ ...base,
222
+ element,
223
+
224
+ /**
225
+ * Adds CSS classes to the element
226
+ * @param {...string} classes - CSS classes to add
227
+ * @returns {ElementComponent} Component instance for chaining
228
+ */
229
+ addClass(...classes: string[]): ElementComponent {
230
+ element.classList.add(...classes.filter(Boolean));
231
+ return this;
232
+ },
233
+
234
+ /**
235
+ * Removes the element and cleans up event listeners.
236
+ * Ensures proper resource cleanup when the component is destroyed.
237
+ */
238
+ destroy(): void {
239
+ if (hasTouchSupport() && options.interactive) {
240
+ element.removeEventListener('touchstart', handleTouchStart);
241
+ element.removeEventListener('touchend', handleTouchEnd);
242
+ element.removeEventListener('touchmove', handleTouchMove);
243
+ }
244
+ element.remove();
245
+ }
246
+ };
247
+ };
@@ -0,0 +1,155 @@
1
+ // src/core/compose/features/checkable.ts
2
+
3
+ import { BaseComponent } from '../component';
4
+
5
+ /**
6
+ * Configuration for checkable feature
7
+ */
8
+ export interface CheckableConfig {
9
+ checked?: boolean;
10
+ [key: string]: any;
11
+ }
12
+
13
+ /**
14
+ * Component with input element
15
+ */
16
+ export interface InputComponent extends BaseComponent {
17
+ input: HTMLInputElement;
18
+ emit?: (event: string, data: any) => InputComponent;
19
+ }
20
+
21
+ /**
22
+ * Checkable state manager interface
23
+ */
24
+ export interface CheckableManager {
25
+ /**
26
+ * Sets the checked state to true
27
+ * Emits change event if state changes
28
+ * @returns CheckableManager instance for chaining
29
+ */
30
+ check: () => CheckableManager;
31
+
32
+ /**
33
+ * Sets the checked state to false
34
+ * Emits change event if state changes
35
+ * @returns CheckableManager instance for chaining
36
+ */
37
+ uncheck: () => CheckableManager;
38
+
39
+ /**
40
+ * Toggles the current checked state
41
+ * Always emits change event
42
+ * @returns CheckableManager instance for chaining
43
+ */
44
+ toggle: () => CheckableManager;
45
+
46
+ /**
47
+ * Gets the current checked state
48
+ * @returns Whether component is checked
49
+ */
50
+ isChecked: () => boolean;
51
+ }
52
+
53
+ /**
54
+ * Component with checkable capabilities
55
+ */
56
+ export interface CheckableComponent extends BaseComponent {
57
+ checkable: CheckableManager;
58
+ }
59
+
60
+ /**
61
+ * Adds checked state management to a component with an input
62
+ * Manages visual state and event emission for checked changes
63
+ *
64
+ * @param config - Checkable configuration
65
+ * @returns Function that enhances a component with checkable functionality
66
+ */
67
+ export const withCheckable = <T extends CheckableConfig>(config: T = {} as T) =>
68
+ <C extends InputComponent>(component: C): C & CheckableComponent => {
69
+ if (!component.input) return component as C & CheckableComponent;
70
+
71
+ /**
72
+ * Updates component classes to reflect checked state
73
+ */
74
+ const updateStateClasses = (): void => {
75
+ component.element.classList.toggle(
76
+ `${component.getClass('switch')}--checked`,
77
+ component.input.checked
78
+ );
79
+ };
80
+
81
+ // Set initial state
82
+ if (config.checked) {
83
+ component.input.checked = true;
84
+ updateStateClasses();
85
+ }
86
+
87
+ // Update classes whenever checked state changes
88
+ if (component.emit) {
89
+ component.on?.('change', updateStateClasses);
90
+ }
91
+
92
+ const checkable: CheckableManager = {
93
+ /**
94
+ * Sets the checked state to true
95
+ * Emits change event if state changes
96
+ * @returns CheckableManager instance for chaining
97
+ */
98
+ check() {
99
+ if (!component.input.checked) {
100
+ component.input.checked = true;
101
+ updateStateClasses();
102
+ component.emit?.('change', {
103
+ checked: true,
104
+ value: component.input.value
105
+ });
106
+ }
107
+ return this;
108
+ },
109
+
110
+ /**
111
+ * Sets the checked state to false
112
+ * Emits change event if state changes
113
+ * @returns CheckableManager instance for chaining
114
+ */
115
+ uncheck() {
116
+ if (component.input.checked) {
117
+ component.input.checked = false;
118
+ updateStateClasses();
119
+ component.emit?.('change', {
120
+ checked: false,
121
+ value: component.input.value
122
+ });
123
+ }
124
+ return this;
125
+ },
126
+
127
+ /**
128
+ * Toggles the current checked state
129
+ * Always emits change event
130
+ * @returns CheckableManager instance for chaining
131
+ */
132
+ toggle() {
133
+ component.input.checked = !component.input.checked;
134
+ updateStateClasses();
135
+ component.emit?.('change', {
136
+ checked: component.input.checked,
137
+ value: component.input.value
138
+ });
139
+ return this;
140
+ },
141
+
142
+ /**
143
+ * Gets the current checked state
144
+ * @returns Whether component is checked
145
+ */
146
+ isChecked() {
147
+ return component.input.checked;
148
+ }
149
+ };
150
+
151
+ return {
152
+ ...component,
153
+ checkable
154
+ };
155
+ };
@@ -0,0 +1,116 @@
1
+ // src/core/compose/features/disabled.ts
2
+
3
+ import { BaseComponent, ElementComponent } from '../component';
4
+
5
+ /**
6
+ * Configuration for disabled feature
7
+ */
8
+ export interface DisabledConfig {
9
+ disabled?: boolean;
10
+ componentName?: string;
11
+ [key: string]: any;
12
+ }
13
+
14
+ /**
15
+ * Disabled state manager interface
16
+ */
17
+ export interface DisabledManager {
18
+ /**
19
+ * Enables the component
20
+ * @returns DisabledManager instance for chaining
21
+ */
22
+ enable(): DisabledManager;
23
+
24
+ /**
25
+ * Disables the component
26
+ * @returns DisabledManager instance for chaining
27
+ */
28
+ disable(): DisabledManager;
29
+
30
+ /**
31
+ * Toggles the disabled state
32
+ * @returns DisabledManager instance for chaining
33
+ */
34
+ toggle(): DisabledManager;
35
+
36
+ /**
37
+ * Checks if the component is disabled
38
+ * @returns true if disabled
39
+ */
40
+ isDisabled(): boolean;
41
+ }
42
+
43
+ /**
44
+ * Component with disabled state capabilities
45
+ */
46
+ export interface DisabledComponent extends BaseComponent {
47
+ disabled: DisabledManager;
48
+ }
49
+
50
+ /**
51
+ * Adds disabled state management to a component
52
+ *
53
+ * @param config - Configuration object
54
+ * @returns Function that enhances a component with disabled state management
55
+ */
56
+ export const withDisabled = <T extends DisabledConfig>(config: T) =>
57
+ <C extends ElementComponent>(component: C): C & DisabledComponent => {
58
+ // Get the disabled class based on component name
59
+ const disabledClass = `${component.getClass(config.componentName || component.componentName || 'component')}--disabled`;
60
+
61
+ // Directly implement disabled functionality
62
+ const disabled: DisabledManager = {
63
+ enable() {
64
+ component.element.classList.remove(disabledClass);
65
+ if ('input' in component && component.input instanceof HTMLElement) {
66
+ component.input.disabled = false;
67
+ component.input.removeAttribute('disabled');
68
+ } else {
69
+ (component.element as HTMLButtonElement).disabled = false;
70
+ component.element.removeAttribute('disabled');
71
+ }
72
+ return this;
73
+ },
74
+
75
+ disable() {
76
+ component.element.classList.add(disabledClass);
77
+ if ('input' in component && component.input instanceof HTMLElement) {
78
+ component.input.disabled = true;
79
+ component.input.setAttribute('disabled', 'true');
80
+ } else {
81
+ (component.element as HTMLButtonElement).disabled = true;
82
+ component.element.setAttribute('disabled', 'true');
83
+ }
84
+ return this;
85
+ },
86
+
87
+ toggle() {
88
+ if (this.isDisabled()) {
89
+ this.enable();
90
+ } else {
91
+ this.disable();
92
+ }
93
+ return this;
94
+ },
95
+
96
+ isDisabled() {
97
+ if ('input' in component && component.input instanceof HTMLElement) {
98
+ return component.input.disabled;
99
+ }
100
+ return (component.element as HTMLButtonElement).disabled;
101
+ }
102
+ };
103
+
104
+ // Initialize disabled state if configured
105
+ if (config.disabled) {
106
+ // Use requestAnimationFrame to ensure DOM is ready
107
+ requestAnimationFrame(() => {
108
+ disabled.disable();
109
+ });
110
+ }
111
+
112
+ return {
113
+ ...component,
114
+ disabled
115
+ };
116
+ };
@@ -0,0 +1,65 @@
1
+ // src/core/compose/features/events.ts
2
+ /**
3
+ * @module core/compose/features
4
+ */
5
+
6
+ import { createEmitter, Emitter } from '../../state/emitter';
7
+ import { BaseComponent } from '../component';
8
+
9
+ /**
10
+ * Component with event capabilities
11
+ */
12
+ export interface EventComponent extends BaseComponent {
13
+ /**
14
+ * Subscribe to an event
15
+ * @param event - Event name
16
+ * @param handler - Event handler
17
+ * @returns Component instance for chaining
18
+ */
19
+ on(event: string, handler: (...args: any[]) => void): EventComponent;
20
+
21
+ /**
22
+ * Unsubscribe from an event
23
+ * @param event - Event name
24
+ * @param handler - Event handler
25
+ * @returns Component instance for chaining
26
+ */
27
+ off(event: string, handler: (...args: any[]) => void): EventComponent;
28
+
29
+ /**
30
+ * Emit an event
31
+ * @param event - Event name
32
+ * @param data - Event data
33
+ * @returns Component instance for chaining
34
+ */
35
+ emit(event: string, data?: any): EventComponent;
36
+ }
37
+
38
+ /**
39
+ * Adds event handling capabilities to a component
40
+ * Returns event system ready to use immediately
41
+ *
42
+ * @returns Function that enhances a component with event capabilities
43
+ */
44
+ export const withEvents = () =>
45
+ <T extends BaseComponent>(component: T): T & EventComponent => {
46
+ const emitter: Emitter = createEmitter();
47
+
48
+ return {
49
+ ...component,
50
+ on(event: string, handler: (...args: any[]) => void) {
51
+ emitter.on(event, handler);
52
+ return this;
53
+ },
54
+
55
+ off(event: string, handler: (...args: any[]) => void) {
56
+ emitter.off(event, handler);
57
+ return this;
58
+ },
59
+
60
+ emit(event: string, data?: any) {
61
+ emitter.emit(event, data);
62
+ return this;
63
+ }
64
+ };
65
+ };
@@ -0,0 +1,67 @@
1
+ // src/core/compose/features/icon.ts
2
+
3
+ import { createIcon, IconManager } from '../../build/icon';
4
+ import { BaseComponent, ElementComponent } from '../component';
5
+
6
+ /**
7
+ * Configuration for icon feature
8
+ */
9
+ export interface IconConfig {
10
+ icon?: string;
11
+ iconPosition?: 'start' | 'end';
12
+ iconSize?: string;
13
+ prefix?: string;
14
+ componentName?: string;
15
+ text?: string;
16
+ [key: string]: any;
17
+ }
18
+
19
+ /**
20
+ * Component with icon capabilities
21
+ */
22
+ export interface IconComponent extends BaseComponent {
23
+ icon: IconManager;
24
+ }
25
+
26
+ /**
27
+ * Updates the component's circular style based on content
28
+ * Adds circular class if there's an icon but no text
29
+ */
30
+ const updateCircularStyle = (component: ElementComponent, config: IconConfig): void => {
31
+ const hasText = config.text;
32
+ const hasIcon = config.icon;
33
+
34
+ const circularClass = `${component.getClass('button')}--circular`;
35
+ if (!hasText && hasIcon) {
36
+ component.element.classList.add(circularClass);
37
+ } else {
38
+ component.element.classList.remove(circularClass);
39
+ }
40
+ };
41
+
42
+ /**
43
+ * Adds icon management to a component
44
+ *
45
+ * @param config - Configuration object containing icon information
46
+ * @returns Function that enhances a component with icon capabilities
47
+ */
48
+ export const withIcon = <T extends IconConfig>(config: T) =>
49
+ <C extends ElementComponent>(component: C): C & IconComponent => {
50
+ const icon = createIcon(component.element, {
51
+ prefix: config.prefix,
52
+ type: config.componentName || 'component',
53
+ position: config.iconPosition,
54
+ iconSize: config.iconSize
55
+ });
56
+
57
+ if (config.icon) {
58
+ icon.setIcon(config.icon);
59
+ }
60
+
61
+ updateCircularStyle(component, config);
62
+
63
+ return {
64
+ ...component,
65
+ icon
66
+ };
67
+ };
@@ -0,0 +1,35 @@
1
+ // src/core/compose/features/index.ts
2
+
3
+ // Core features
4
+ export { withEvents } from './events';
5
+ export { withText } from './text';
6
+ export { withIcon } from './icon';
7
+ export { withVariant } from './variant';
8
+ export { withSize } from './size';
9
+ export { withPosition } from './position';
10
+ export { withRipple } from './ripple';
11
+ export { withInput } from './input';
12
+ export { withCheckable } from './checkable';
13
+ export { withStyle } from './style';
14
+ export { withTextInput } from './textinput';
15
+ export { withTextLabel } from './textlabel';
16
+ export { withTrack } from './track';
17
+ export { withEvents as withEnhancedEvents } from './withEvents';
18
+
19
+ // State management features
20
+ export { withDisabled } from './disabled';
21
+ export { withLifecycle } from './lifecycle';
22
+
23
+ // Re-export interfaces for better developer experience
24
+ export type { EventComponent } from './events';
25
+ export type { TextComponent } from './text';
26
+ export type { IconComponent } from './icon';
27
+ export type { LifecycleComponent, Lifecycle } from './lifecycle';
28
+ export type { DisabledComponent, DisabledManager } from './disabled';
29
+ export type { RippleComponent } from './ripple';
30
+ export type { InputComponent } from './input';
31
+ export type { CheckableComponent, CheckableManager } from './checkable';
32
+ export type { TextInputComponent } from './textinput';
33
+ export type { LabelComponent, LabelManager } from './textlabel';
34
+ export type { TrackComponent } from './track';
35
+ export type { EnhancedEventComponent } from './withEvents';