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,48 @@
1
+ // src/components/card/content.ts
2
+ import { PREFIX } from '../../core/config';
3
+ import { pipe } from '../../core/compose';
4
+ import { createBase, withElement } from '../../core/compose/component';
5
+ import { CardContentConfig } from './types';
6
+
7
+ /**
8
+ * Creates a card content component
9
+ * @param {CardContentConfig} config - Content configuration
10
+ * @returns {HTMLElement} Card content element
11
+ */
12
+ export const createCardContent = (config: CardContentConfig = {}): HTMLElement => {
13
+ const baseConfig = {
14
+ ...config,
15
+ componentName: 'card-content',
16
+ prefix: PREFIX
17
+ };
18
+
19
+ try {
20
+ const content = pipe(
21
+ createBase,
22
+ withElement({
23
+ tag: 'div',
24
+ componentName: 'card-content',
25
+ className: [
26
+ config.class,
27
+ config.padding === false ? `${PREFIX}-card-content--no-padding` : null
28
+ ],
29
+ html: config.html,
30
+ text: config.text
31
+ })
32
+ )(baseConfig);
33
+
34
+ // Add children if provided
35
+ if (Array.isArray(config.children)) {
36
+ config.children.forEach(child => {
37
+ if (child instanceof HTMLElement) {
38
+ content.element.appendChild(child);
39
+ }
40
+ });
41
+ }
42
+
43
+ return content.element;
44
+ } catch (error) {
45
+ console.error('Card content creation error:', error instanceof Error ? error.message : String(error));
46
+ throw new Error(`Failed to create card content: ${error instanceof Error ? error.message : String(error)}`);
47
+ }
48
+ };
@@ -0,0 +1,228 @@
1
+ // src/components/card/features.ts
2
+ import { PREFIX } from '../../core/config';
3
+ import { createElement } from '../../core/dom/create';
4
+ import { BaseComponent, CardComponent, LoadingFeature, ExpandableFeature, SwipeableFeature } from './types';
5
+
6
+ interface LoadingConfig {
7
+ initialState?: boolean;
8
+ }
9
+
10
+ interface ExpandableConfig {
11
+ initialExpanded?: boolean;
12
+ expandableContent?: HTMLElement;
13
+ }
14
+
15
+ interface SwipeableConfig {
16
+ onSwipeLeft?: (component: CardComponent) => void;
17
+ onSwipeRight?: (component: CardComponent) => void;
18
+ threshold?: number;
19
+ }
20
+
21
+ /**
22
+ * Higher-order function to add loading state to a card
23
+ * @param {LoadingConfig} config - Loading state configuration
24
+ * @returns {Function} Card component enhancer
25
+ */
26
+ export const withLoading = (config: LoadingConfig = {}) => (component: BaseComponent): BaseComponent & { loading: LoadingFeature } => {
27
+ const initialState = config.initialState || false;
28
+ let loadingElement: HTMLElement | null = null;
29
+ let isLoading = initialState;
30
+
31
+ function setLoading(loading: boolean): void {
32
+ isLoading = loading;
33
+
34
+ if (loading && !loadingElement) {
35
+ // Create and add loading overlay
36
+ loadingElement = createElement({
37
+ tag: 'div',
38
+ className: `${PREFIX}-card-loading-overlay`,
39
+ container: component.element
40
+ });
41
+
42
+ // Add spinner
43
+ createElement({
44
+ tag: 'div',
45
+ className: `${PREFIX}-card-loading-spinner`,
46
+ container: loadingElement
47
+ });
48
+
49
+ component.element.classList.add(`${PREFIX}-card--state-loading`);
50
+ } else if (!loading && loadingElement) {
51
+ // Remove loading overlay
52
+ loadingElement.remove();
53
+ loadingElement = null;
54
+ component.element.classList.remove(`${PREFIX}-card--state-loading`);
55
+ }
56
+ }
57
+
58
+ if (initialState) {
59
+ setLoading(true);
60
+ }
61
+
62
+ return {
63
+ ...component,
64
+ loading: {
65
+ isLoading: () => isLoading,
66
+ setLoading
67
+ }
68
+ };
69
+ };
70
+
71
+ /**
72
+ * Higher-order function to add expandable behavior to a card
73
+ * @param {ExpandableConfig} config - Expandable configuration
74
+ * @returns {Function} Card component enhancer
75
+ */
76
+ export const withExpandable = (config: ExpandableConfig = {}) => (component: BaseComponent): BaseComponent & { expandable: ExpandableFeature } => {
77
+ const initialExpanded = config.initialExpanded || false;
78
+ let isExpanded = initialExpanded;
79
+ const expandableContent = config.expandableContent;
80
+ let expandButton: HTMLButtonElement;
81
+
82
+ // Create expand/collapse button
83
+ expandButton = createElement({
84
+ tag: 'button',
85
+ className: `${PREFIX}-card-expand-button`,
86
+ attrs: {
87
+ 'aria-expanded': isExpanded ? 'true' : 'false',
88
+ 'aria-label': isExpanded ? 'Collapse' : 'Expand'
89
+ }
90
+ }) as HTMLButtonElement;
91
+
92
+ // Add to card as action if not already present
93
+ const actionsContainer = component.element.querySelector(`.${PREFIX}-card-actions`);
94
+ if (actionsContainer) {
95
+ actionsContainer.appendChild(expandButton);
96
+ } else {
97
+ // Create actions container if not present
98
+ const newActionsContainer = createElement({
99
+ tag: 'div',
100
+ className: `${PREFIX}-card-actions`,
101
+ container: component.element
102
+ });
103
+ newActionsContainer.appendChild(expandButton);
104
+ }
105
+
106
+ // Set initial state
107
+ if (expandableContent) {
108
+ expandableContent.classList.add(`${PREFIX}-card-expandable-content`);
109
+ if (!initialExpanded) {
110
+ expandableContent.style.display = 'none';
111
+ }
112
+ component.element.appendChild(expandableContent);
113
+ }
114
+
115
+ // Set expanded state
116
+ function setExpanded(expanded: boolean): void {
117
+ isExpanded = expanded;
118
+
119
+ if (expandableContent) {
120
+ expandableContent.style.display = expanded ? 'block' : 'none';
121
+ }
122
+
123
+ expandButton.setAttribute('aria-expanded', expanded ? 'true' : 'false');
124
+ expandButton.setAttribute('aria-label', expanded ? 'Collapse' : 'Expand');
125
+
126
+ if (expanded) {
127
+ component.element.classList.add(`${PREFIX}-card--expanded`);
128
+ } else {
129
+ component.element.classList.remove(`${PREFIX}-card--expanded`);
130
+ }
131
+
132
+ component.emit?.('expandedChanged', { expanded });
133
+ }
134
+
135
+ // Toggle expanded state
136
+ function toggleExpanded(): void {
137
+ setExpanded(!isExpanded);
138
+ }
139
+
140
+ // Add click handler to toggle button
141
+ expandButton.addEventListener('click', (e) => {
142
+ e.stopPropagation();
143
+ toggleExpanded();
144
+ });
145
+
146
+ return {
147
+ ...component,
148
+ expandable: {
149
+ isExpanded: () => isExpanded,
150
+ setExpanded,
151
+ toggleExpanded
152
+ }
153
+ };
154
+ };
155
+
156
+ /**
157
+ * Higher-order function to add swipeable behavior to a card
158
+ * @param {SwipeableConfig} config - Swipeable configuration
159
+ * @returns {Function} Card component enhancer
160
+ */
161
+ export const withSwipeable = (config: SwipeableConfig = {}) => (component: BaseComponent): BaseComponent & { swipeable: SwipeableFeature } => {
162
+ const threshold = config.threshold || 100;
163
+ let startX = 0;
164
+ let currentX = 0;
165
+
166
+ function handleTouchStart(e: TouchEvent): void {
167
+ startX = e.touches[0].clientX;
168
+ component.element.style.transition = 'none';
169
+ }
170
+
171
+ function handleTouchMove(e: TouchEvent): void {
172
+ if (!startX) return;
173
+
174
+ currentX = e.touches[0].clientX;
175
+ const diffX = currentX - startX;
176
+
177
+ // Apply transform to move card
178
+ component.element.style.transform = `translateX(${diffX}px)`;
179
+ }
180
+
181
+ function handleTouchEnd(): void {
182
+ if (!startX) return;
183
+
184
+ component.element.style.transition = 'transform 0.3s ease';
185
+ const diffX = currentX - startX;
186
+
187
+ if (Math.abs(diffX) >= threshold) {
188
+ // Swipe threshold reached
189
+ if (diffX > 0 && config.onSwipeRight) {
190
+ // Swipe right
191
+ component.element.style.transform = 'translateX(100%)';
192
+ config.onSwipeRight(component as CardComponent);
193
+ } else if (diffX < 0 && config.onSwipeLeft) {
194
+ // Swipe left
195
+ component.element.style.transform = 'translateX(-100%)';
196
+ config.onSwipeLeft(component as CardComponent);
197
+ } else {
198
+ // Reset if no handler
199
+ component.element.style.transform = 'translateX(0)';
200
+ }
201
+ } else {
202
+ // Reset if below threshold
203
+ component.element.style.transform = 'translateX(0)';
204
+ }
205
+
206
+ startX = 0;
207
+ currentX = 0;
208
+ }
209
+
210
+ // Add event listeners
211
+ component.element.addEventListener('touchstart', handleTouchStart as EventListener);
212
+ component.element.addEventListener('touchmove', handleTouchMove as EventListener);
213
+ component.element.addEventListener('touchend', handleTouchEnd);
214
+
215
+ // Add swipeable class
216
+ component.element.classList.add(`${PREFIX}-card--swipeable`);
217
+
218
+ // Return enhanced component
219
+ return {
220
+ ...component,
221
+ swipeable: {
222
+ reset: () => {
223
+ component.element.style.transition = 'transform 0.3s ease';
224
+ component.element.style.transform = 'translateX(0)';
225
+ }
226
+ }
227
+ };
228
+ };
@@ -0,0 +1,88 @@
1
+ // src/components/card/header.ts
2
+ import { PREFIX } from '../../core/config';
3
+ import { pipe } from '../../core/compose';
4
+ import { createBase, withElement } from '../../core/compose/component';
5
+ import { createElement } from '../../core/dom/create';
6
+ import { CardHeaderConfig } from './types';
7
+
8
+ /**
9
+ * Creates a card header component
10
+ * @param {CardHeaderConfig} config - Header configuration
11
+ * @returns {HTMLElement} Card header element
12
+ */
13
+ export const createCardHeader = (config: CardHeaderConfig = {}): HTMLElement => {
14
+ const baseConfig = {
15
+ ...config,
16
+ componentName: 'card-header',
17
+ prefix: PREFIX
18
+ };
19
+
20
+ try {
21
+ const header = pipe(
22
+ createBase,
23
+ withElement({
24
+ tag: 'div',
25
+ componentName: 'card-header',
26
+ className: config.class
27
+ })
28
+ )(baseConfig);
29
+
30
+ // Create text container
31
+ const textContainer = createElement({
32
+ tag: 'div',
33
+ className: `${PREFIX}-card-header-text`,
34
+ container: header.element
35
+ });
36
+
37
+ // Add title if provided
38
+ if (config.title) {
39
+ createElement({
40
+ tag: 'h3',
41
+ className: `${PREFIX}-card-header-title`,
42
+ text: config.title,
43
+ container: textContainer
44
+ });
45
+ }
46
+
47
+ // Add subtitle if provided
48
+ if (config.subtitle) {
49
+ createElement({
50
+ tag: 'h4',
51
+ className: `${PREFIX}-card-header-subtitle`,
52
+ text: config.subtitle,
53
+ container: textContainer
54
+ });
55
+ }
56
+
57
+ // Add avatar if provided
58
+ if (config.avatar) {
59
+ const avatarElement = typeof config.avatar === 'string'
60
+ ? createElement({
61
+ tag: 'div',
62
+ className: `${PREFIX}-card-header-avatar`,
63
+ html: config.avatar
64
+ })
65
+ : config.avatar;
66
+
67
+ header.element.insertBefore(avatarElement, header.element.firstChild);
68
+ }
69
+
70
+ // Add action if provided
71
+ if (config.action) {
72
+ const actionElement = typeof config.action === 'string'
73
+ ? createElement({
74
+ tag: 'div',
75
+ className: `${PREFIX}-card-header-action`,
76
+ html: config.action
77
+ })
78
+ : config.action;
79
+
80
+ header.element.appendChild(actionElement);
81
+ }
82
+
83
+ return header.element;
84
+ } catch (error) {
85
+ console.error('Card header creation error:', error instanceof Error ? error.message : String(error));
86
+ throw new Error(`Failed to create card header: ${error instanceof Error ? error.message : String(error)}`);
87
+ }
88
+ };
@@ -0,0 +1,19 @@
1
+ // src/components/card/index.ts
2
+ export { default } from './card'
3
+ export { createCardContent } from './content'
4
+ export { createCardHeader } from './header'
5
+ export { createCardActions } from './actions'
6
+ export { createCardMedia } from './media'
7
+ export {
8
+ CardVariant,
9
+ CardElevation,
10
+ CardSchema,
11
+ CardHeaderConfig,
12
+ CardContentConfig,
13
+ CardActionsConfig,
14
+ CardMediaConfig,
15
+ CardComponent
16
+ } from './types'
17
+
18
+ // Export constants for backward compatibility
19
+ export { CARD_VARIANTS, CARD_ELEVATIONS, CARD_SCHEMA } from './constants'
@@ -0,0 +1,52 @@
1
+ // src/components/card/media.ts
2
+ import { PREFIX } from '../../core/config';
3
+ import { pipe } from '../../core/compose';
4
+ import { createBase, withElement } from '../../core/compose/component';
5
+ import { CardMediaConfig } from './types';
6
+
7
+ /**
8
+ * Creates a card media component
9
+ * @param {CardMediaConfig} config - Media configuration
10
+ * @returns {HTMLElement} Card media element
11
+ */
12
+ export const createCardMedia = (config: CardMediaConfig = {}): HTMLElement => {
13
+ const baseConfig = {
14
+ ...config,
15
+ componentName: 'card-media',
16
+ prefix: PREFIX
17
+ };
18
+
19
+ try {
20
+ const media = pipe(
21
+ createBase,
22
+ withElement({
23
+ tag: 'div',
24
+ componentName: 'card-media',
25
+ className: [
26
+ config.class,
27
+ config.aspectRatio ? `${PREFIX}-card-media--${config.aspectRatio.replace(':', '-')}` : null,
28
+ config.contain ? `${PREFIX}-card-media--contain` : null
29
+ ]
30
+ })
31
+ )(baseConfig);
32
+
33
+ // If custom element is provided, use it
34
+ if (config.element instanceof HTMLElement) {
35
+ media.element.appendChild(config.element);
36
+ }
37
+
38
+ // Otherwise create an image if src is provided
39
+ else if (config.src) {
40
+ const img = document.createElement('img');
41
+ img.src = config.src;
42
+ if (config.alt) img.alt = config.alt;
43
+ img.className = `${PREFIX}-card-media-img`;
44
+ media.element.appendChild(img);
45
+ }
46
+
47
+ return media.element;
48
+ } catch (error) {
49
+ console.error('Card media creation error:', error instanceof Error ? error.message : String(error));
50
+ throw new Error(`Failed to create card media: ${error instanceof Error ? error.message : String(error)}`);
51
+ }
52
+ };
@@ -0,0 +1,174 @@
1
+ // src/components/card/types.ts
2
+
3
+ /**
4
+ * Card variant types following Material Design 3
5
+ */
6
+ export enum CardVariant {
7
+ ELEVATED = 'elevated',
8
+ FILLED = 'filled',
9
+ OUTLINED = 'outlined'
10
+ }
11
+
12
+ /**
13
+ * Card elevation levels
14
+ */
15
+ export enum CardElevation {
16
+ RESTING = 1,
17
+ HOVERED = 2,
18
+ DRAGGED = 4
19
+ }
20
+
21
+ /**
22
+ * Interface for card configuration
23
+ */
24
+ export interface CardSchema {
25
+ variant?: CardVariant;
26
+ interactive?: boolean;
27
+ fullWidth?: boolean;
28
+ clickable?: boolean;
29
+ draggable?: boolean;
30
+ class?: string;
31
+ headerConfig?: CardHeaderConfig;
32
+ contentConfig?: CardContentConfig;
33
+ actionsConfig?: CardActionsConfig;
34
+ mediaConfig?: CardMediaConfig;
35
+ }
36
+
37
+ /**
38
+ * Interface for card header configuration
39
+ */
40
+ export interface CardHeaderConfig {
41
+ title?: string;
42
+ subtitle?: string;
43
+ avatar?: HTMLElement | string;
44
+ action?: HTMLElement | string;
45
+ class?: string;
46
+ }
47
+
48
+ /**
49
+ * Interface for card content configuration
50
+ */
51
+ export interface CardContentConfig {
52
+ text?: string;
53
+ html?: string;
54
+ children?: HTMLElement[];
55
+ padding?: boolean;
56
+ class?: string;
57
+ }
58
+
59
+ /**
60
+ * Interface for card actions configuration
61
+ */
62
+ export interface CardActionsConfig {
63
+ actions?: HTMLElement[];
64
+ fullBleed?: boolean;
65
+ vertical?: boolean;
66
+ align?: 'start' | 'center' | 'end' | 'space-between';
67
+ class?: string;
68
+ }
69
+
70
+ /**
71
+ * Interface for card media configuration
72
+ */
73
+ export interface CardMediaConfig {
74
+ src?: string;
75
+ alt?: string;
76
+ element?: HTMLElement;
77
+ aspectRatio?: '16:9' | '4:3' | '1:1' | string;
78
+ contain?: boolean;
79
+ class?: string;
80
+ }
81
+
82
+ /**
83
+ * Base component interface
84
+ */
85
+ export interface BaseComponent {
86
+ element: HTMLElement;
87
+ getClass: (name?: string) => string;
88
+ getModifierClass: (base: string, modifier: string) => string;
89
+ getElementClass: (base: string, element: string) => string;
90
+ addClass: (...classes: string[]) => BaseComponent;
91
+ emit?: (event: string, data?: any) => void;
92
+ config: CardComponentConfig;
93
+ touchState?: TouchState;
94
+ updateTouchState?: (event: TouchEvent | MouseEvent, status: 'start' | 'end') => void;
95
+ lifecycle?: ComponentLifecycle;
96
+ }
97
+
98
+ /**
99
+ * Touch state interface
100
+ */
101
+ export interface TouchState {
102
+ startTime: number;
103
+ startPosition: { x: number; y: number };
104
+ isTouching: boolean;
105
+ activeTarget: EventTarget | null;
106
+ }
107
+
108
+ /**
109
+ * Component lifecycle interface
110
+ */
111
+ export interface ComponentLifecycle {
112
+ mount?: () => void;
113
+ update?: () => void;
114
+ destroy: () => void;
115
+ }
116
+
117
+ /**
118
+ * Card component configuration
119
+ */
120
+ export interface CardComponentConfig extends CardSchema {
121
+ componentName: string;
122
+ prefix: string;
123
+ }
124
+
125
+ /**
126
+ * Card component interface
127
+ */
128
+ export interface CardComponent extends BaseComponent {
129
+ // Card-specific methods
130
+ addContent: (contentElement: HTMLElement) => CardComponent;
131
+ setHeader: (headerElement: HTMLElement) => CardComponent;
132
+ addMedia: (mediaElement: HTMLElement, position?: 'top' | 'bottom') => CardComponent;
133
+ setActions: (actionsElement: HTMLElement) => CardComponent;
134
+ makeDraggable: (dragStartCallback?: (event: DragEvent) => void) => CardComponent;
135
+ destroy: () => void;
136
+
137
+ // Optional feature interfaces
138
+ loading?: LoadingFeature;
139
+ expandable?: ExpandableFeature;
140
+ swipeable?: SwipeableFeature;
141
+ }
142
+
143
+ /**
144
+ * Loading feature interface
145
+ */
146
+ export interface LoadingFeature {
147
+ isLoading: () => boolean;
148
+ setLoading: (loading: boolean) => void;
149
+ }
150
+
151
+ /**
152
+ * Expandable feature interface
153
+ */
154
+ export interface ExpandableFeature {
155
+ isExpanded: () => boolean;
156
+ setExpanded: (expanded: boolean) => void;
157
+ toggleExpanded: () => void;
158
+ }
159
+
160
+ /**
161
+ * Swipeable feature interface
162
+ */
163
+ export interface SwipeableFeature {
164
+ reset: () => void;
165
+ }
166
+
167
+ /**
168
+ * API options interface
169
+ */
170
+ export interface ApiOptions {
171
+ lifecycle: {
172
+ destroy: () => void;
173
+ };
174
+ }
@@ -0,0 +1,82 @@
1
+ // src/components/checkbox/api.ts
2
+ import { BaseComponent, CheckboxComponent, ApiOptions } from './types';
3
+
4
+ /**
5
+ * Enhances checkbox component with API methods
6
+ * @param {ApiOptions} options - API configuration
7
+ * @returns {Function} Higher-order function that adds API methods to component
8
+ */
9
+ export const withAPI = ({ disabled, lifecycle, checkable }: ApiOptions) =>
10
+ (component: BaseComponent): CheckboxComponent => ({
11
+ ...component as any,
12
+ element: component.element,
13
+ input: component.input as HTMLInputElement,
14
+
15
+ // Value management
16
+ getValue: component.getValue || (() => ''),
17
+ setValue(value: string): CheckboxComponent {
18
+ component.setValue?.(value);
19
+ return this;
20
+ },
21
+
22
+ // State management
23
+ check(): CheckboxComponent {
24
+ checkable.check();
25
+ return this;
26
+ },
27
+
28
+ uncheck(): CheckboxComponent {
29
+ checkable.uncheck();
30
+ return this;
31
+ },
32
+
33
+ toggle(): CheckboxComponent {
34
+ checkable.toggle();
35
+ return this;
36
+ },
37
+
38
+ isChecked(): boolean {
39
+ return checkable.isChecked();
40
+ },
41
+
42
+ setIndeterminate(state: boolean): CheckboxComponent {
43
+ component.setIndeterminate?.(state);
44
+ return this;
45
+ },
46
+
47
+ // Label management
48
+ setLabel(text: string): CheckboxComponent {
49
+ component.text?.setText(text);
50
+ return this;
51
+ },
52
+
53
+ getLabel(): string {
54
+ return component.text?.getText() || '';
55
+ },
56
+
57
+ // Event handling
58
+ on(event: string, handler: Function): CheckboxComponent {
59
+ component.on?.(event, handler);
60
+ return this;
61
+ },
62
+
63
+ off(event: string, handler: Function): CheckboxComponent {
64
+ component.off?.(event, handler);
65
+ return this;
66
+ },
67
+
68
+ // State management
69
+ enable(): CheckboxComponent {
70
+ disabled.enable();
71
+ return this;
72
+ },
73
+
74
+ disable(): CheckboxComponent {
75
+ disabled.disable();
76
+ return this;
77
+ },
78
+
79
+ destroy(): void {
80
+ lifecycle.destroy();
81
+ }
82
+ });