mtrl 0.1.3 → 0.2.1

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 (225) 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 → _styles.scss} +79 -7
  12. package/src/components/card/{actions.js → actions.ts} +15 -18
  13. package/src/components/card/{api.js → api.ts} +33 -33
  14. package/src/components/card/card.ts +41 -0
  15. package/src/components/card/config.ts +99 -0
  16. package/src/components/card/{constants.js → constants.ts} +11 -10
  17. package/src/components/card/{content.js → content.ts} +15 -18
  18. package/src/components/card/{features.js → features.ts} +104 -94
  19. package/src/components/card/{header.js → header.ts} +21 -25
  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/constants.ts +37 -0
  27. package/src/components/checkbox/index.ts +4 -0
  28. package/src/components/checkbox/types.ts +146 -0
  29. package/src/components/chip/_styles.scss +372 -0
  30. package/src/components/chip/api.ts +115 -0
  31. package/src/components/chip/chip-set.ts +225 -0
  32. package/src/components/chip/chip.ts +82 -0
  33. package/src/components/chip/config.ts +92 -0
  34. package/src/components/chip/constants.ts +38 -0
  35. package/src/components/chip/index.ts +4 -0
  36. package/src/components/chip/types.ts +172 -0
  37. package/src/components/list/api.ts +72 -0
  38. package/src/components/list/config.ts +43 -0
  39. package/src/components/list/{constants.js → constants.ts} +34 -7
  40. package/src/components/list/features.ts +224 -0
  41. package/src/components/list/index.ts +14 -0
  42. package/src/components/list/list-item.ts +120 -0
  43. package/src/components/list/list.ts +37 -0
  44. package/src/components/list/types.ts +179 -0
  45. package/src/components/list/utils.ts +47 -0
  46. package/src/components/menu/api.ts +119 -0
  47. package/src/components/menu/config.ts +54 -0
  48. package/src/components/menu/constants.ts +154 -0
  49. package/src/components/menu/features/items-manager.ts +457 -0
  50. package/src/components/menu/features/keyboard-navigation.ts +133 -0
  51. package/src/components/menu/features/positioning.ts +127 -0
  52. package/src/components/menu/features/{visibility.js → visibility.ts} +66 -64
  53. package/src/components/menu/index.ts +14 -0
  54. package/src/components/menu/menu-item.ts +43 -0
  55. package/src/components/menu/menu.ts +53 -0
  56. package/src/components/menu/types.ts +178 -0
  57. package/src/components/navigation/api.ts +79 -0
  58. package/src/components/navigation/config.ts +61 -0
  59. package/src/components/navigation/{constants.js → constants.ts} +10 -10
  60. package/src/components/navigation/index.ts +14 -0
  61. package/src/components/navigation/nav-item.ts +148 -0
  62. package/src/components/navigation/navigation.ts +50 -0
  63. package/src/components/navigation/types.ts +212 -0
  64. package/src/components/progress/_styles.scss +204 -0
  65. package/src/components/progress/api.ts +179 -0
  66. package/src/components/progress/config.ts +124 -0
  67. package/src/components/progress/constants.ts +43 -0
  68. package/src/components/progress/index.ts +5 -0
  69. package/src/components/progress/progress.ts +163 -0
  70. package/src/components/progress/types.ts +102 -0
  71. package/src/components/snackbar/api.ts +162 -0
  72. package/src/components/snackbar/config.ts +62 -0
  73. package/src/components/snackbar/{constants.js → constants.ts} +21 -4
  74. package/src/components/snackbar/features.ts +76 -0
  75. package/src/components/snackbar/index.ts +4 -0
  76. package/src/components/snackbar/position.ts +71 -0
  77. package/src/components/snackbar/queue.ts +76 -0
  78. package/src/components/snackbar/snackbar.ts +60 -0
  79. package/src/components/snackbar/types.ts +58 -0
  80. package/src/components/switch/api.ts +77 -0
  81. package/src/components/switch/config.ts +74 -0
  82. package/src/components/switch/{constants.js → constants.ts} +5 -5
  83. package/src/components/switch/index.ts +4 -0
  84. package/src/components/switch/switch.ts +52 -0
  85. package/src/components/switch/types.ts +142 -0
  86. package/src/components/textfield/api.ts +72 -0
  87. package/src/components/textfield/config.ts +54 -0
  88. package/src/components/textfield/{constants.js → constants.ts} +38 -5
  89. package/src/components/textfield/index.ts +4 -0
  90. package/src/components/textfield/textfield.ts +50 -0
  91. package/src/components/textfield/types.ts +139 -0
  92. package/src/core/compose/base.ts +43 -0
  93. package/src/core/compose/component.ts +255 -0
  94. package/src/core/compose/features/checkable.ts +155 -0
  95. package/src/core/compose/features/disabled.ts +116 -0
  96. package/src/core/compose/features/events.ts +65 -0
  97. package/src/core/compose/features/icon.ts +67 -0
  98. package/src/core/compose/features/index.ts +35 -0
  99. package/src/core/compose/features/input.ts +174 -0
  100. package/src/core/compose/features/lifecycle.ts +139 -0
  101. package/src/core/compose/features/position.ts +94 -0
  102. package/src/core/compose/features/ripple.ts +55 -0
  103. package/src/core/compose/features/size.ts +29 -0
  104. package/src/core/compose/features/style.ts +31 -0
  105. package/src/core/compose/features/text.ts +44 -0
  106. package/src/core/compose/features/textinput.ts +225 -0
  107. package/src/core/compose/features/textlabel.ts +92 -0
  108. package/src/core/compose/features/track.ts +84 -0
  109. package/src/core/compose/features/variant.ts +29 -0
  110. package/src/core/compose/features/withEvents.ts +137 -0
  111. package/src/core/compose/index.ts +54 -0
  112. package/src/core/compose/{pipe.js → pipe.ts} +16 -11
  113. package/src/core/config/component-config.ts +136 -0
  114. package/src/core/config.ts +211 -0
  115. package/src/core/dom/{attributes.js → attributes.ts} +11 -11
  116. package/src/core/dom/classes.ts +60 -0
  117. package/src/core/dom/create.ts +251 -0
  118. package/src/core/dom/events.ts +209 -0
  119. package/src/core/dom/index.ts +10 -0
  120. package/src/core/dom/utils.ts +97 -0
  121. package/src/core/index.ts +111 -0
  122. package/src/core/state/disabled.ts +81 -0
  123. package/src/core/state/emitter.ts +94 -0
  124. package/src/core/state/events.ts +88 -0
  125. package/src/core/state/index.ts +16 -0
  126. package/src/core/state/lifecycle.ts +131 -0
  127. package/src/core/state/store.ts +197 -0
  128. package/src/core/utils/index.ts +45 -0
  129. package/src/core/utils/{mobile.js → mobile.ts} +48 -24
  130. package/src/core/utils/object.ts +41 -0
  131. package/src/core/utils/validate.ts +234 -0
  132. package/src/{index.js → index.ts} +3 -2
  133. package/index.js +0 -11
  134. package/src/components/button/api.js +0 -54
  135. package/src/components/button/button.js +0 -81
  136. package/src/components/button/config.js +0 -10
  137. package/src/components/button/constants.js +0 -63
  138. package/src/components/button/index.js +0 -2
  139. package/src/components/card/card.js +0 -102
  140. package/src/components/card/config.js +0 -16
  141. package/src/components/card/index.js +0 -7
  142. package/src/components/card/media.js +0 -56
  143. package/src/components/checkbox/api.js +0 -45
  144. package/src/components/checkbox/checkbox.js +0 -96
  145. package/src/components/checkbox/constants.js +0 -88
  146. package/src/components/checkbox/index.js +0 -2
  147. package/src/components/container/api.js +0 -42
  148. package/src/components/container/container.js +0 -45
  149. package/src/components/container/index.js +0 -2
  150. package/src/components/container/styles.scss +0 -66
  151. package/src/components/list/index.js +0 -2
  152. package/src/components/list/list-item.js +0 -147
  153. package/src/components/list/list.js +0 -267
  154. package/src/components/menu/api.js +0 -117
  155. package/src/components/menu/constants.js +0 -42
  156. package/src/components/menu/features/items-manager.js +0 -375
  157. package/src/components/menu/features/keyboard-navigation.js +0 -129
  158. package/src/components/menu/features/positioning.js +0 -125
  159. package/src/components/menu/index.js +0 -2
  160. package/src/components/menu/menu-item.js +0 -41
  161. package/src/components/menu/menu.js +0 -54
  162. package/src/components/navigation/api.js +0 -43
  163. package/src/components/navigation/index.js +0 -2
  164. package/src/components/navigation/nav-item.js +0 -137
  165. package/src/components/navigation/navigation.js +0 -55
  166. package/src/components/snackbar/api.js +0 -125
  167. package/src/components/snackbar/features.js +0 -69
  168. package/src/components/snackbar/index.js +0 -2
  169. package/src/components/snackbar/position.js +0 -63
  170. package/src/components/snackbar/queue.js +0 -74
  171. package/src/components/snackbar/snackbar.js +0 -70
  172. package/src/components/switch/api.js +0 -44
  173. package/src/components/switch/index.js +0 -2
  174. package/src/components/switch/switch.js +0 -71
  175. package/src/components/textfield/api.js +0 -49
  176. package/src/components/textfield/index.js +0 -2
  177. package/src/components/textfield/textfield.js +0 -68
  178. package/src/core/build/_ripple.scss +0 -79
  179. package/src/core/build/constants.js +0 -51
  180. package/src/core/build/icon.js +0 -78
  181. package/src/core/build/ripple.js +0 -159
  182. package/src/core/build/text.js +0 -54
  183. package/src/core/compose/base.js +0 -8
  184. package/src/core/compose/component.js +0 -225
  185. package/src/core/compose/features/checkable.js +0 -114
  186. package/src/core/compose/features/disabled.js +0 -64
  187. package/src/core/compose/features/events.js +0 -48
  188. package/src/core/compose/features/icon.js +0 -33
  189. package/src/core/compose/features/index.js +0 -20
  190. package/src/core/compose/features/input.js +0 -100
  191. package/src/core/compose/features/lifecycle.js +0 -69
  192. package/src/core/compose/features/position.js +0 -60
  193. package/src/core/compose/features/ripple.js +0 -32
  194. package/src/core/compose/features/size.js +0 -9
  195. package/src/core/compose/features/style.js +0 -12
  196. package/src/core/compose/features/text.js +0 -17
  197. package/src/core/compose/features/textinput.js +0 -114
  198. package/src/core/compose/features/textlabel.js +0 -28
  199. package/src/core/compose/features/track.js +0 -49
  200. package/src/core/compose/features/variant.js +0 -9
  201. package/src/core/compose/features/withEvents.js +0 -67
  202. package/src/core/compose/index.js +0 -16
  203. package/src/core/config.js +0 -140
  204. package/src/core/dom/classes.js +0 -70
  205. package/src/core/dom/create.js +0 -132
  206. package/src/core/dom/events.js +0 -175
  207. package/src/core/dom/index.js +0 -5
  208. package/src/core/dom/utils.js +0 -22
  209. package/src/core/index.js +0 -23
  210. package/src/core/state/disabled.js +0 -51
  211. package/src/core/state/emitter.js +0 -63
  212. package/src/core/state/events.js +0 -29
  213. package/src/core/state/index.js +0 -6
  214. package/src/core/state/lifecycle.js +0 -64
  215. package/src/core/state/store.js +0 -112
  216. package/src/core/utils/index.js +0 -39
  217. package/src/core/utils/object.js +0 -22
  218. package/src/core/utils/validate.js +0 -37
  219. /package/src/components/checkbox/{styles.scss → _styles.scss} +0 -0
  220. /package/src/components/list/{styles.scss → _styles.scss} +0 -0
  221. /package/src/components/menu/{styles.scss → _styles.scss} +0 -0
  222. /package/src/components/navigation/{styles.scss → _styles.scss} +0 -0
  223. /package/src/components/snackbar/{styles.scss → _styles.scss} +0 -0
  224. /package/src/components/switch/{styles.scss → _styles.scss} +0 -0
  225. /package/src/components/textfield/{styles.scss → _styles.scss} +0 -0
@@ -0,0 +1,136 @@
1
+ // src/core/config/component-config.ts
2
+ import { PREFIX } from '../config';
3
+
4
+ /**
5
+ * Base component configuration interface
6
+ * Common configuration properties shared by all components
7
+ */
8
+ export interface BaseComponentConfig {
9
+ componentName?: string;
10
+ prefix?: string;
11
+ class?: string;
12
+ [key: string]: any;
13
+ }
14
+
15
+ /**
16
+ * Creates a base configuration for any component
17
+ *
18
+ * @param {BaseComponentConfig} defaults - Default configuration for the component
19
+ * @param {BaseComponentConfig} userConfig - User provided configuration
20
+ * @param {string} componentName - The name of the component
21
+ * @returns {BaseComponentConfig} Complete configuration with defaults applied
22
+ *
23
+ * @example
24
+ * // In button/config.ts
25
+ * export const createBaseConfig = (config: ButtonConfig = {}) =>
26
+ * createComponentConfig(defaultConfig, config, 'button');
27
+ */
28
+ export const createComponentConfig = (
29
+ defaults: BaseComponentConfig,
30
+ userConfig: BaseComponentConfig = {},
31
+ componentName: string
32
+ ): BaseComponentConfig => {
33
+ // Create a new object with defaults and user config
34
+ const config = {
35
+ ...defaults,
36
+ ...userConfig,
37
+ // Force these values to ensure consistency
38
+ componentName,
39
+ prefix: PREFIX
40
+ };
41
+
42
+ return config;
43
+ };
44
+
45
+ /**
46
+ * Creates a class name with proper prefixing
47
+ *
48
+ * @param {string} componentName - The name of the component
49
+ * @param {string} [element] - Optional element name for BEM notation
50
+ * @param {string} [modifier] - Optional modifier name for BEM notation
51
+ * @returns {string} Properly formatted class name
52
+ *
53
+ * @example
54
+ * // Returns 'mtrl-button'
55
+ * createClassName('button');
56
+ *
57
+ * // Returns 'mtrl-button__icon'
58
+ * createClassName('button', 'icon');
59
+ *
60
+ * // Returns 'mtrl-button--primary'
61
+ * createClassName('button', null, 'primary');
62
+ *
63
+ * // Returns 'mtrl-button__icon--small'
64
+ * createClassName('button', 'icon', 'small');
65
+ */
66
+ export const createClassName = (
67
+ componentName: string,
68
+ element?: string | null,
69
+ modifier?: string | null
70
+ ): string => {
71
+ let className = `${PREFIX}-${componentName}`;
72
+
73
+ if (element) {
74
+ className += `__${element}`;
75
+ }
76
+
77
+ if (modifier) {
78
+ className += `--${modifier}`;
79
+ }
80
+
81
+ return className;
82
+ };
83
+
84
+ /**
85
+ * Processes class names for an element, handling arrays, nulls and conditional classes
86
+ *
87
+ * @param {string | string[] | null} classNames - Class names to process
88
+ * @returns {string} Space-separated class names as a string
89
+ *
90
+ * @example
91
+ * // Returns 'mtrl-card mtrl-card--elevated custom-class'
92
+ * processClassNames(['mtrl-card', 'mtrl-card--elevated', 'custom-class']);
93
+ *
94
+ * // Returns 'mtrl-card'
95
+ * processClassNames(['mtrl-card', null, undefined]);
96
+ */
97
+ export const processClassNames = (classNames: string | string[] | null): string => {
98
+ if (!classNames) return '';
99
+
100
+ if (typeof classNames === 'string') return classNames;
101
+
102
+ return classNames
103
+ .filter(Boolean) // Remove null, undefined, empty strings
104
+ .join(' ');
105
+ };
106
+
107
+ /**
108
+ * Creates a configuration object for withElement HOC
109
+ *
110
+ * @param {BaseComponentConfig} config - Component configuration
111
+ * @param {Object} options - Element options
112
+ * @returns {Object} Configuration object for withElement
113
+ */
114
+ export const createElementConfig = (
115
+ config: BaseComponentConfig,
116
+ options: {
117
+ tag: string;
118
+ attrs?: Record<string, any>;
119
+ className?: string | string[] | null;
120
+ html?: string;
121
+ text?: string;
122
+ forwardEvents?: Record<string, boolean | Function>;
123
+ interactive?: boolean;
124
+ }
125
+ ) => {
126
+ return {
127
+ tag: options.tag,
128
+ componentName: config.componentName,
129
+ attrs: options.attrs || {},
130
+ className: options.className,
131
+ html: options.html,
132
+ text: options.text,
133
+ forwardEvents: options.forwardEvents || {},
134
+ interactive: options.interactive
135
+ };
136
+ };
@@ -0,0 +1,211 @@
1
+ // src/core/config.ts
2
+
3
+ /**
4
+ * Library prefix used for all components
5
+ */
6
+ export const PREFIX = 'mtrl';
7
+
8
+ /**
9
+ * Gets a prefixed component class name
10
+ * @param type - Component type
11
+ * @returns Prefixed class name
12
+ */
13
+ export const getComponentClass = (type: string): string => `${PREFIX}-${type}`;
14
+
15
+ /**
16
+ * Gets a BEM modifier class name
17
+ * @param baseClass - Base class name
18
+ * @param modifier - Modifier name
19
+ * @returns Modifier class name
20
+ */
21
+ export const getModifierClass = (baseClass: string, modifier: string): string => `${baseClass}--${modifier}`;
22
+
23
+ /**
24
+ * Gets a BEM element class name
25
+ * @param baseClass - Base class name
26
+ * @param element - Element name
27
+ * @returns Element class name
28
+ */
29
+ export const getElementClass = (baseClass: string, element: string): string => `${baseClass}-${element}`;
30
+
31
+ /**
32
+ * Component type identifiers
33
+ */
34
+ export enum COMPONENTS {
35
+ BUTTON = 'button',
36
+ TEXTFIELD = 'textfield',
37
+ CONTAINER = 'container',
38
+ SNACKBAR = 'snackbar',
39
+ SWITCH = 'switch'
40
+ }
41
+
42
+ /**
43
+ * Theme configuration interface
44
+ */
45
+ export interface ThemeConfig {
46
+ /**
47
+ * Theme name
48
+ */
49
+ name: string;
50
+
51
+ /**
52
+ * Theme CSS variables
53
+ */
54
+ variables: Record<string, string>;
55
+
56
+ /**
57
+ * Theme variants
58
+ */
59
+ variants: Record<string, any>;
60
+ }
61
+
62
+ /**
63
+ * Component configuration interface
64
+ */
65
+ export interface ComponentConfig {
66
+ prefix: string;
67
+ type: string;
68
+ baseClass: string;
69
+ getClass: () => string;
70
+ getModifierClass: (modifier: string) => string;
71
+ getElementClass: (element: string) => string;
72
+ withTheme: (theme: string) => ThemedComponentConfig;
73
+ withVariants: (...variants: string[]) => VariantComponentConfig;
74
+ withStates: (...states: string[]) => StateComponentConfig;
75
+ }
76
+
77
+ /**
78
+ * Themed component configuration interface
79
+ */
80
+ export interface ThemedComponentConfig extends ComponentConfig {
81
+ theme: string;
82
+ getThemeClass: (variant: string) => string;
83
+ }
84
+
85
+ /**
86
+ * Variant component configuration interface
87
+ */
88
+ export interface VariantComponentConfig extends ComponentConfig {
89
+ variants: string[];
90
+ hasVariant: (variant: string) => boolean;
91
+ getVariantClass: (variant: string) => string | null;
92
+ }
93
+
94
+ /**
95
+ * State component configuration interface
96
+ */
97
+ export interface StateComponentConfig extends ComponentConfig {
98
+ states: string[];
99
+ getStateClass: (state: string) => string | null;
100
+ }
101
+
102
+ /**
103
+ * Creates a component configuration object
104
+ * @param type - Component type
105
+ * @returns Component configuration interface
106
+ */
107
+ export const createComponentConfig = (type: string): ComponentConfig => {
108
+ const baseClass = `${PREFIX}-${type}`;
109
+
110
+ // Create the base config object
111
+ const config: ComponentConfig = {
112
+ prefix: PREFIX,
113
+ type,
114
+ baseClass,
115
+
116
+ // Class name generators
117
+ getClass: () => baseClass,
118
+ getModifierClass: (modifier) => `${baseClass}--${modifier}`,
119
+ getElementClass: (element) => `${baseClass}-${element}`,
120
+
121
+ // Theme support
122
+ withTheme: (theme) => ({
123
+ ...config,
124
+ theme,
125
+ getThemeClass: (variant) => `${baseClass}--theme-${theme}-${variant}`
126
+ }),
127
+
128
+ // Variant support
129
+ withVariants: (...variants) => ({
130
+ ...config,
131
+ variants,
132
+ hasVariant: (variant) => variants.includes(variant),
133
+ getVariantClass: (variant) =>
134
+ variants.includes(variant) ? `${baseClass}--${variant}` : null
135
+ }),
136
+
137
+ // State support
138
+ withStates: (...states) => ({
139
+ ...config,
140
+ states,
141
+ getStateClass: (state) =>
142
+ states.includes(state) ? `${baseClass}--state-${state}` : null
143
+ })
144
+ };
145
+
146
+ return config;
147
+ };
148
+
149
+ /**
150
+ * Common component states
151
+ */
152
+ export enum STATES {
153
+ DISABLED = 'disabled',
154
+ FOCUSED = 'focused',
155
+ ACTIVE = 'active',
156
+ LOADING = 'loading',
157
+ ERROR = 'error'
158
+ }
159
+
160
+ /**
161
+ * CSS class generation utilities
162
+ */
163
+ export const classNames = {
164
+ /**
165
+ * Creates a BEM-style class name
166
+ * @param block - Block name
167
+ * @param element - Element name
168
+ * @param modifier - Modifier name
169
+ * @returns BEM class name
170
+ */
171
+ bem: (block: string, element?: string, modifier?: string): string => {
172
+ let className = block;
173
+ if (element) className += `-${element}`;
174
+ if (modifier) className += `--${modifier}`;
175
+ return className;
176
+ },
177
+
178
+ /**
179
+ * Joins class names, filtering out falsy values
180
+ * @param classes - Class names to join
181
+ * @returns Joined class names
182
+ */
183
+ join: (...classes: (string | undefined | null | false)[]): string =>
184
+ classes.filter(Boolean).join(' ')
185
+ };
186
+
187
+ /**
188
+ * Creates a themed component configuration
189
+ * @param type - Component type
190
+ * @param theme - Theme configuration
191
+ * @returns Themed component configuration
192
+ */
193
+ export const createThemedComponent = (type: string, theme: ThemeConfig): ThemedComponentConfig => {
194
+ const config = createComponentConfig(type);
195
+
196
+ return {
197
+ ...config,
198
+ theme: theme.name,
199
+
200
+ // Theme-specific class generators
201
+ getThemeClass: (variant) =>
202
+ `${config.getClass()}--theme-${theme.name}${variant ? `-${variant}` : ''}`,
203
+
204
+ // Theme CSS variables
205
+ getCssVariables: () =>
206
+ Object.entries(theme.variables).reduce((acc, [key, value]) => ({
207
+ ...acc,
208
+ [`--${PREFIX}-${type}-${key}`]: value
209
+ }), {} as Record<string, string>)
210
+ };
211
+ };
@@ -1,4 +1,4 @@
1
- // src/core/dom/attributes.js
1
+ // src/core/dom/attributes.ts
2
2
  /**
3
3
  * @module core/dom
4
4
  * @description DOM manipulation utilities
@@ -9,15 +9,15 @@
9
9
  * @memberof module:core/dom
10
10
  * @function setAttributes
11
11
  * @param {HTMLElement} element - Target element
12
- * @param {Object} attrs - Attributes to set
12
+ * @param {Record<string, any>} attrs - Attributes to set
13
13
  * @returns {HTMLElement} Modified element
14
14
  */
15
- export const setAttributes = (element, attrs = {}) => {
15
+ export const setAttributes = (element: HTMLElement, attrs: Record<string, any> = {}): HTMLElement => {
16
16
  Object.entries(attrs).forEach(([key, value]) => {
17
- if (value != null) element.setAttribute(key, value)
18
- })
19
- return element
20
- }
17
+ if (value != null) element.setAttribute(key, value.toString());
18
+ });
19
+ return element;
20
+ };
21
21
 
22
22
  /**
23
23
  * Removes multiple attributes from an element
@@ -27,7 +27,7 @@ export const setAttributes = (element, attrs = {}) => {
27
27
  * @param {string[]} attrs - Attributes to remove
28
28
  * @returns {HTMLElement} Modified element
29
29
  */
30
- export const removeAttributes = (element, attrs = []) => {
31
- attrs.forEach(attr => element.removeAttribute(attr))
32
- return element
33
- }
30
+ export const removeAttributes = (element: HTMLElement, attrs: string[] = []): HTMLElement => {
31
+ attrs.forEach(attr => element.removeAttribute(attr));
32
+ return element;
33
+ };
@@ -0,0 +1,60 @@
1
+ // src/core/dom/classes.ts
2
+ /**
3
+ * @module core/dom
4
+ * @description DOM manipulation utilities
5
+ */
6
+
7
+ import { normalizeClasses } from '../utils';
8
+
9
+ /**
10
+ * Adds multiple classes to an element
11
+ * @param {HTMLElement} element - Target element
12
+ * @param {...(string | string[])} classes - Classes to add
13
+ * @returns {HTMLElement} Modified element
14
+ */
15
+ export const addClass = (element: HTMLElement, ...classes: (string | string[])[]): HTMLElement => {
16
+ const normalizedClasses = normalizeClasses(...classes);
17
+ if (normalizedClasses.length) {
18
+ element.classList.add(...normalizedClasses);
19
+ }
20
+ return element;
21
+ };
22
+
23
+ /**
24
+ * Removes multiple classes from an element
25
+ * @param {HTMLElement} element - Target element
26
+ * @param {...(string | string[])} classes - Classes to remove
27
+ * @returns {HTMLElement} Modified element
28
+ */
29
+ export const removeClass = (element: HTMLElement, ...classes: (string | string[])[]): HTMLElement => {
30
+ const normalizedClasses = normalizeClasses(...classes);
31
+ if (normalizedClasses.length) {
32
+ element.classList.remove(...normalizedClasses);
33
+ }
34
+ return element;
35
+ };
36
+
37
+ /**
38
+ * Toggles multiple classes on an element
39
+ * @param {HTMLElement} element - Target element
40
+ * @param {...(string | string[])} classes - Classes to toggle
41
+ * @returns {HTMLElement} Modified element
42
+ */
43
+ export const toggleClass = (element: HTMLElement, ...classes: (string | string[])[]): HTMLElement => {
44
+ const normalizedClasses = normalizeClasses(...classes);
45
+ normalizedClasses.forEach(cls => {
46
+ element.classList.toggle(cls);
47
+ });
48
+ return element;
49
+ };
50
+
51
+ /**
52
+ * Checks if an element has all specified classes
53
+ * @param {HTMLElement} element - Target element
54
+ * @param {...(string | string[])} classes - Classes to check
55
+ * @returns {boolean} True if element has all specified classes
56
+ */
57
+ export const hasClass = (element: HTMLElement, ...classes: (string | string[])[]): boolean => {
58
+ const normalizedClasses = normalizeClasses(...classes);
59
+ return normalizedClasses.every(cls => element.classList.contains(cls));
60
+ };
@@ -0,0 +1,251 @@
1
+ // src/core/dom/create.ts
2
+ /**
3
+ * @module core/dom
4
+ * @description DOM manipulation utilities
5
+ */
6
+
7
+ import { setAttributes } from './attributes';
8
+ import { normalizeClasses } from '../utils';
9
+
10
+ /**
11
+ * Options for element creation
12
+ */
13
+ export interface CreateElementOptions {
14
+ /**
15
+ * HTML tag name
16
+ */
17
+ tag?: string;
18
+
19
+ /**
20
+ * Container to append element to
21
+ */
22
+ container?: HTMLElement | null;
23
+
24
+ /**
25
+ * Inner HTML content
26
+ */
27
+ html?: string;
28
+
29
+ /**
30
+ * Text content
31
+ */
32
+ text?: string;
33
+
34
+ /**
35
+ * Element ID
36
+ */
37
+ id?: string;
38
+
39
+ /**
40
+ * Dataset attributes
41
+ */
42
+ data?: Record<string, string>;
43
+
44
+ /**
45
+ * CSS classes
46
+ */
47
+ className?: string | string[] | null;
48
+
49
+ /**
50
+ * HTML attributes
51
+ */
52
+ attrs?: Record<string, any>;
53
+
54
+ /**
55
+ * Events to forward when component has emit method
56
+ */
57
+ forwardEvents?: Record<string, boolean | ((context: any, event: Event) => boolean)>;
58
+
59
+ /**
60
+ * Callback after element creation
61
+ */
62
+ onCreate?: (element: HTMLElement, context?: any) => void;
63
+
64
+ /**
65
+ * Component context
66
+ */
67
+ context?: any;
68
+
69
+ /**
70
+ * Additional attributes
71
+ */
72
+ [key: string]: any;
73
+ }
74
+
75
+ /**
76
+ * Event handler storage to facilitate cleanup
77
+ */
78
+ export interface EventHandlerStorage {
79
+ [eventName: string]: EventListener;
80
+ }
81
+
82
+ /**
83
+ * Creates a DOM element with the specified options
84
+ *
85
+ * @param {CreateElementOptions} options - Element creation options
86
+ * @returns {HTMLElement} Created element
87
+ */
88
+ export const createElement = (options: CreateElementOptions = {}): HTMLElement => {
89
+ const {
90
+ tag = 'div',
91
+ container = null,
92
+ html = '',
93
+ text = '',
94
+ id = '',
95
+ data = {},
96
+ className,
97
+ attrs = {},
98
+ forwardEvents = {},
99
+ onCreate,
100
+ context,
101
+ ...rest
102
+ } = options;
103
+
104
+ const element = document.createElement(tag);
105
+
106
+ // Handle content
107
+ if (html) element.innerHTML = html;
108
+ if (text) element.textContent = text;
109
+ if (id) element.id = id;
110
+
111
+ // Handle classes
112
+ if (className) {
113
+ const normalizedClasses = normalizeClasses(className);
114
+ if (normalizedClasses.length) {
115
+ element.classList.add(...normalizedClasses);
116
+ }
117
+ }
118
+
119
+ // Handle data attributes
120
+ Object.entries(data).forEach(([key, value]) => {
121
+ element.dataset[key] = value;
122
+ });
123
+
124
+ // Handle all other attributes
125
+ const allAttrs = { ...attrs, ...rest };
126
+ Object.entries(allAttrs).forEach(([key, value]) => {
127
+ if (value != null) element.setAttribute(key, String(value));
128
+ });
129
+
130
+ // Initialize event handler storage if not present
131
+ if (!element.__eventHandlers) {
132
+ element.__eventHandlers = {};
133
+ }
134
+
135
+ // Handle event forwarding if context has emit method or is a component with on method
136
+ if (forwardEvents && (context?.emit || context?.on)) {
137
+ Object.entries(forwardEvents).forEach(([nativeEvent, eventConfig]) => {
138
+ // Create a wrapper handler function to evaluate condition and forward event
139
+ const handler = (event: Event) => {
140
+ // Determine if the event should be forwarded
141
+ let shouldForward = true;
142
+
143
+ if (typeof eventConfig === 'function') {
144
+ try {
145
+ // If it's a function, call with component context and event
146
+ shouldForward = eventConfig({ ...context, element }, event);
147
+ } catch (error) {
148
+ console.warn(`Error in event condition for ${nativeEvent}:`, error);
149
+ shouldForward = false;
150
+ }
151
+ } else {
152
+ // If it's a boolean, use directly
153
+ shouldForward = Boolean(eventConfig);
154
+ }
155
+
156
+ // Forward the event if condition passes
157
+ if (shouldForward) {
158
+ if (context.emit) {
159
+ context.emit(nativeEvent, { event, element, originalEvent: event });
160
+ } else if (context.on) {
161
+ // This is a component with on method but no emit method
162
+ // Dispatch a custom event that can be listened to
163
+ const customEvent = new CustomEvent(nativeEvent, {
164
+ detail: { event, element, originalEvent: event },
165
+ bubbles: true,
166
+ cancelable: true
167
+ });
168
+ element.dispatchEvent(customEvent);
169
+ }
170
+ }
171
+ };
172
+
173
+ // Store the handler for future removal
174
+ element.__eventHandlers[nativeEvent] = handler;
175
+
176
+ // Add the actual event listener
177
+ element.addEventListener(nativeEvent, handler);
178
+ });
179
+ }
180
+
181
+ // Append to container if provided
182
+ if (container) {
183
+ container.appendChild(element);
184
+ }
185
+
186
+ if (typeof onCreate === 'function') {
187
+ onCreate(element, context);
188
+ }
189
+
190
+ return element;
191
+ };
192
+
193
+ /**
194
+ * Removes event handlers from an element
195
+ * @param element - Element to cleanup
196
+ */
197
+ export const removeEventHandlers = (element: HTMLElement): void => {
198
+ if (element.__eventHandlers) {
199
+ Object.entries(element.__eventHandlers).forEach(([eventName, handler]) => {
200
+ element.removeEventListener(eventName, handler);
201
+ });
202
+ delete element.__eventHandlers;
203
+ }
204
+ };
205
+
206
+ /**
207
+ * Higher-order function to add attributes to an element
208
+ * @param {Record<string, any>} attrs - Attributes to add
209
+ * @returns {(element: HTMLElement) => HTMLElement} Element transformer
210
+ */
211
+ export const withAttributes = (attrs: Record<string, any>) =>
212
+ (element: HTMLElement): HTMLElement => {
213
+ setAttributes(element, attrs);
214
+ return element;
215
+ };
216
+
217
+ /**
218
+ * Higher-order function to add classes to an element
219
+ * @param {...(string | string[])} classes - Classes to add
220
+ * @returns {(element: HTMLElement) => HTMLElement} Element transformer
221
+ */
222
+ export const withClasses = (...classes: (string | string[])[]) =>
223
+ (element: HTMLElement): HTMLElement => {
224
+ const normalizedClasses = normalizeClasses(...classes);
225
+ if (normalizedClasses.length) {
226
+ element.classList.add(...normalizedClasses);
227
+ }
228
+ return element;
229
+ };
230
+
231
+ /**
232
+ * Higher-order function to add content to an element
233
+ * @param {Node|string} content - Content to add
234
+ * @returns {(element: HTMLElement) => HTMLElement} Element transformer
235
+ */
236
+ export const withContent = (content: Node | string) =>
237
+ (element: HTMLElement): HTMLElement => {
238
+ if (content instanceof Node) {
239
+ element.appendChild(content);
240
+ } else {
241
+ element.textContent = content;
242
+ }
243
+ return element;
244
+ };
245
+
246
+ // Extend HTMLElement interface to add eventHandlers property
247
+ declare global {
248
+ interface HTMLElement {
249
+ __eventHandlers?: EventHandlerStorage;
250
+ }
251
+ }