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,120 @@
1
+ // src/components/list/list-item.ts
2
+ import { PREFIX } from '../../core/config';
3
+ import { pipe } from '../../core/compose';
4
+ import { createBase, withElement } from '../../core/compose/component';
5
+ import { withEvents, withDisabled } from '../../core/compose/features';
6
+ import { ListItemConfig } from './types';
7
+ import { LIST_ITEM_LAYOUTS, LIST_CLASSES } from './constants';
8
+ import { createElement } from './utils';
9
+
10
+ /**
11
+ * Creates list item content based on configuration
12
+ * @param {BaseComponent} component - Component to enhance
13
+ * @param {ListItemConfig} config - List item configuration
14
+ * @returns {BaseComponent} Enhanced component
15
+ */
16
+ const withItemContent = (config: ListItemConfig) => (component: any): any => {
17
+ const { element } = component;
18
+ const prefix = config.prefix || PREFIX;
19
+ const isVertical = config.layout === LIST_ITEM_LAYOUTS.VERTICAL;
20
+
21
+ // Create content container
22
+ const content = createElement('div', `${prefix}-${LIST_CLASSES.ITEM_CONTENT}`);
23
+
24
+ // Add leading content (icon/avatar)
25
+ if (config.leading) {
26
+ const leading = createElement('div', `${prefix}-${LIST_CLASSES.ITEM_LEADING}`);
27
+ if (typeof config.leading === 'string') {
28
+ leading.innerHTML = config.leading;
29
+ } else {
30
+ leading.appendChild(config.leading);
31
+ }
32
+ element.appendChild(leading);
33
+ }
34
+
35
+ // Text wrapper for proper alignment
36
+ const textWrapper = createElement('div', `${prefix}-${LIST_CLASSES.ITEM_TEXT}`);
37
+
38
+ // Add overline text (vertical only)
39
+ if (isVertical && config.overline) {
40
+ const overline = createElement('div', `${prefix}-${LIST_CLASSES.ITEM_OVERLINE}`, config.overline);
41
+ textWrapper.appendChild(overline);
42
+ }
43
+
44
+ // Add headline (primary text)
45
+ if (config.headline) {
46
+ const headline = createElement('div', `${prefix}-${LIST_CLASSES.ITEM_HEADLINE}`, config.headline);
47
+ textWrapper.appendChild(headline);
48
+ }
49
+
50
+ // Add supporting text (secondary text)
51
+ if (config.supportingText) {
52
+ const supporting = createElement('div', `${prefix}-${LIST_CLASSES.ITEM_SUPPORTING}`, config.supportingText);
53
+ textWrapper.appendChild(supporting);
54
+ }
55
+
56
+ content.appendChild(textWrapper);
57
+
58
+ // Add meta information (vertical only)
59
+ if (isVertical && config.meta) {
60
+ const meta = createElement('div', `${prefix}-${LIST_CLASSES.ITEM_META}`);
61
+ if (typeof config.meta === 'string') {
62
+ meta.textContent = config.meta;
63
+ } else {
64
+ meta.appendChild(config.meta);
65
+ }
66
+ content.appendChild(meta);
67
+ }
68
+
69
+ element.appendChild(content);
70
+
71
+ // Add trailing content (icon/meta)
72
+ if (config.trailing) {
73
+ const trailing = createElement('div', `${prefix}-${LIST_CLASSES.ITEM_TRAILING}`);
74
+ if (typeof config.trailing === 'string') {
75
+ trailing.innerHTML = config.trailing;
76
+ } else {
77
+ trailing.appendChild(config.trailing);
78
+ }
79
+ element.appendChild(trailing);
80
+ }
81
+
82
+ // Handle selected state
83
+ if (config.selected) {
84
+ element.setAttribute('aria-selected', 'true');
85
+ element.classList.add(`${prefix}-${LIST_CLASSES.ITEM}--selected`);
86
+ }
87
+
88
+ return component;
89
+ };
90
+
91
+ /**
92
+ * Creates a list item component
93
+ * @param {ListItemConfig} config - List item configuration
94
+ * @returns {Object} List item component instance
95
+ */
96
+ const createListItem = (config: ListItemConfig): any => {
97
+ const baseConfig = {
98
+ ...config,
99
+ componentName: 'list-item',
100
+ prefix: PREFIX
101
+ };
102
+
103
+ const layoutClass = config.layout === LIST_ITEM_LAYOUTS.VERTICAL ? 'vertical' : '';
104
+ const combinedClass = `${layoutClass} ${config.class || ''}`.trim();
105
+
106
+ return pipe(
107
+ createBase,
108
+ withEvents(),
109
+ withElement({
110
+ tag: 'div',
111
+ role: config.role || 'listitem',
112
+ componentName: 'list-item',
113
+ className: combinedClass
114
+ }),
115
+ withDisabled(baseConfig),
116
+ withItemContent(baseConfig)
117
+ )(baseConfig);
118
+ };
119
+
120
+ export default createListItem;
@@ -0,0 +1,37 @@
1
+ // src/components/list/list.ts
2
+ import { pipe } from '../../core/compose';
3
+ import { createBase, withElement } from '../../core/compose/component';
4
+ import { withEvents, withDisabled, withLifecycle } from '../../core/compose/features';
5
+ import { withAPI } from './api';
6
+ import { withListContent } from './features';
7
+ import { ListConfig } from './types';
8
+ import { createBaseConfig, getElementConfig } from './config';
9
+
10
+ /**
11
+ * Creates a List component
12
+ * @param {ListConfig} config - List configuration
13
+ * @returns {Object} List component instance
14
+ */
15
+ const createList = (config: ListConfig = {}): any => {
16
+ const baseConfig = createBaseConfig(config);
17
+
18
+ try {
19
+ return pipe(
20
+ createBase,
21
+ withEvents(),
22
+ withElement(getElementConfig(baseConfig)),
23
+ withDisabled(baseConfig),
24
+ withLifecycle(),
25
+ withListContent(baseConfig),
26
+ comp => withAPI({
27
+ disabled: comp.disabled,
28
+ lifecycle: comp.lifecycle
29
+ })(comp)
30
+ )(baseConfig);
31
+ } catch (error) {
32
+ console.error('List creation error:', error instanceof Error ? error.message : String(error));
33
+ throw new Error(`Failed to create list: ${error instanceof Error ? error.message : String(error)}`);
34
+ }
35
+ };
36
+
37
+ export default createList;
@@ -0,0 +1,179 @@
1
+ // src/components/list/types.ts
2
+ import { LIST_TYPES, LIST_LAYOUTS } from './constants';
3
+
4
+ /**
5
+ * List item configuration
6
+ */
7
+ export interface ListItemConfig {
8
+ /** Unique identifier for the item */
9
+ id: string;
10
+
11
+ /** Item layout (horizontal/vertical) */
12
+ layout?: keyof typeof LIST_LAYOUTS | string;
13
+
14
+ /** Leading content (icon/avatar) */
15
+ leading?: string | HTMLElement;
16
+
17
+ /** Primary text */
18
+ headline?: string;
19
+
20
+ /** Secondary text */
21
+ supportingText?: string;
22
+
23
+ /** Trailing content (icon/meta) */
24
+ trailing?: string | HTMLElement;
25
+
26
+ /** Text above headline (vertical only) */
27
+ overline?: string;
28
+
29
+ /** Meta information (vertical only) */
30
+ meta?: string | HTMLElement;
31
+
32
+ /** Whether the item is disabled */
33
+ disabled?: boolean;
34
+
35
+ /** Whether the item is selected */
36
+ selected?: boolean;
37
+
38
+ /** Additional CSS classes */
39
+ class?: string;
40
+
41
+ /** ARIA role */
42
+ role?: string;
43
+
44
+ /** Whether this is a divider instead of an item */
45
+ divider?: boolean;
46
+ }
47
+
48
+ /**
49
+ * List section configuration
50
+ */
51
+ export interface ListSectionConfig {
52
+ /** Unique identifier for the section */
53
+ id: string;
54
+
55
+ /** Section title text */
56
+ title: string;
57
+
58
+ /** Items in this section */
59
+ items: ListItemConfig[];
60
+ }
61
+
62
+ /**
63
+ * Selection change event data
64
+ */
65
+ export interface SelectionChangeEvent {
66
+ /** Array of selected item IDs */
67
+ selected: string[];
68
+
69
+ /** The item that was clicked */
70
+ item?: ListItemData;
71
+
72
+ /** List selection type */
73
+ type: keyof typeof LIST_TYPES | string;
74
+ }
75
+
76
+ /**
77
+ * List item data
78
+ */
79
+ export interface ListItemData {
80
+ /** The item's DOM element */
81
+ element: HTMLElement;
82
+
83
+ /** Whether the item is disabled */
84
+ disabled?: boolean;
85
+
86
+ /** Internal properties */
87
+ [key: string]: any;
88
+ }
89
+
90
+ /**
91
+ * Configuration interface for the List component
92
+ */
93
+ export interface ListConfig {
94
+ /** List selection type */
95
+ type?: keyof typeof LIST_TYPES | string;
96
+
97
+ /** List layout */
98
+ layout?: keyof typeof LIST_LAYOUTS | string;
99
+
100
+ /** List items */
101
+ items?: ListItemConfig[];
102
+
103
+ /** List sections */
104
+ sections?: ListSectionConfig[];
105
+
106
+ /** Whether the list is disabled */
107
+ disabled?: boolean;
108
+
109
+ /** Additional CSS classes */
110
+ class?: string;
111
+
112
+ /** Prefix for class names */
113
+ prefix?: string;
114
+
115
+ /** Component name */
116
+ componentName?: string;
117
+ }
118
+
119
+ /**
120
+ * List component interface
121
+ */
122
+ export interface ListComponent {
123
+ /** The root element of the list */
124
+ element: HTMLElement;
125
+
126
+ /** Map of list items */
127
+ items: Map<string, ListItemData>;
128
+
129
+ /** Set of selected item IDs */
130
+ selectedItems: Set<string>;
131
+
132
+ /** Gets the currently selected items */
133
+ getSelected: () => string[];
134
+
135
+ /** Sets the selected items */
136
+ setSelected: (ids: string[]) => void;
137
+
138
+ /** Adds a new item to the list */
139
+ addItem: (itemConfig: ListItemConfig) => void;
140
+
141
+ /** Removes an item from the list */
142
+ removeItem: (id: string) => void;
143
+
144
+ /** Adds event listener */
145
+ on: (event: string, handler: Function) => ListComponent;
146
+
147
+ /** Removes event listener */
148
+ off: (event: string, handler: Function) => ListComponent;
149
+
150
+ /** Enables the list */
151
+ enable: () => ListComponent;
152
+
153
+ /** Disables the list */
154
+ disable: () => ListComponent;
155
+
156
+ /** Destroys the list component and cleans up resources */
157
+ destroy: () => void;
158
+ }
159
+
160
+ /**
161
+ * Base component interface
162
+ */
163
+ export interface BaseComponent {
164
+ element: HTMLElement;
165
+ prefix?: string;
166
+ items?: Map<string, ListItemData>;
167
+ selectedItems?: Set<string>;
168
+ emit?: (event: string, data: any) => void;
169
+ on?: (event: string, handler: Function) => any;
170
+ off?: (event: string, handler: Function) => any;
171
+ lifecycle?: {
172
+ destroy: () => void;
173
+ };
174
+ disabled?: {
175
+ enable: () => any;
176
+ disable: () => any;
177
+ };
178
+ [key: string]: any;
179
+ }
@@ -0,0 +1,47 @@
1
+ // src/components/list/utils.ts
2
+ import { LIST_CLASSES } from './constants';
3
+
4
+ /**
5
+ * Creates a divider element
6
+ * @param {string} prefix - CSS class prefix
7
+ * @returns {HTMLElement} Divider element
8
+ */
9
+ export const createDivider = (prefix: string): HTMLElement => {
10
+ const divider = document.createElement('div');
11
+ divider.className = `${prefix}-${LIST_CLASSES.DIVIDER}`;
12
+ divider.setAttribute('role', 'separator');
13
+ return divider;
14
+ };
15
+
16
+ /**
17
+ * Creates a section title element
18
+ * @param {string} title - Section title text
19
+ * @param {string} prefix - CSS class prefix
20
+ * @returns {HTMLElement} Section title element
21
+ */
22
+ export const createSectionTitle = (title: string, prefix: string): HTMLElement => {
23
+ const titleEl = document.createElement('div');
24
+ titleEl.className = `${prefix}-${LIST_CLASSES.SECTION_TITLE}`;
25
+ titleEl.textContent = title;
26
+ return titleEl;
27
+ };
28
+
29
+ /**
30
+ * Creates a DOM element with optional class and content
31
+ * @param {string} tag - HTML tag name
32
+ * @param {string} className - CSS class name
33
+ * @param {string|HTMLElement} [content] - Element content or child element
34
+ * @returns {HTMLElement} Created element
35
+ */
36
+ export const createElement = (tag: string, className: string, content?: string | HTMLElement): HTMLElement => {
37
+ const element = document.createElement(tag);
38
+ element.className = className;
39
+ if (content) {
40
+ if (typeof content === 'string') {
41
+ element.textContent = content;
42
+ } else {
43
+ element.appendChild(content);
44
+ }
45
+ }
46
+ return element;
47
+ };
@@ -0,0 +1,119 @@
1
+ // src/components/menu/api.ts
2
+ import { ApiOptions, BaseComponent, MenuComponent, MenuItemConfig, MenuPositionConfig } from './types';
3
+
4
+ /**
5
+ * Enhances menu 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 = ({ lifecycle }: ApiOptions) =>
10
+ (component: BaseComponent): MenuComponent => ({
11
+ ...component as any,
12
+ element: component.element,
13
+
14
+ /**
15
+ * Shows the menu
16
+ * @returns {MenuComponent} Component instance
17
+ */
18
+ show(): MenuComponent {
19
+ component.show?.();
20
+ return this;
21
+ },
22
+
23
+ /**
24
+ * Hides the menu
25
+ * @returns {MenuComponent} Component instance
26
+ */
27
+ hide(): MenuComponent {
28
+ component.hide?.();
29
+ return this;
30
+ },
31
+
32
+ /**
33
+ * Positions the menu relative to a target
34
+ * @param {HTMLElement} target - Target element
35
+ * @param {MenuPositionConfig} options - Position options
36
+ * @returns {MenuComponent} Component instance
37
+ */
38
+ position(target: HTMLElement, options?: MenuPositionConfig): MenuComponent {
39
+ component.position?.(target, options);
40
+ return this;
41
+ },
42
+
43
+ /**
44
+ * Adds an item to the menu
45
+ * @param {MenuItemConfig} config - Item configuration
46
+ * @returns {MenuComponent} Component instance
47
+ */
48
+ addItem(config: MenuItemConfig): MenuComponent {
49
+ component.addItem?.(config);
50
+ return this;
51
+ },
52
+
53
+ /**
54
+ * Removes an item by name
55
+ * @param {string} name - Item name to remove
56
+ * @returns {MenuComponent} Component instance
57
+ */
58
+ removeItem(name: string): MenuComponent {
59
+ component.removeItem?.(name);
60
+ return this;
61
+ },
62
+
63
+ /**
64
+ * Gets all registered items
65
+ * @returns {Map<string, any>} Map of item names to configurations
66
+ */
67
+ getItems() {
68
+ return component.getItems?.() || new Map();
69
+ },
70
+
71
+ /**
72
+ * Checks if the menu is currently visible
73
+ * @returns {boolean} Whether the menu is visible
74
+ */
75
+ isVisible(): boolean {
76
+ return component.isVisible?.() || false;
77
+ },
78
+
79
+ /**
80
+ * Registers an event handler
81
+ * @param {string} event - Event name
82
+ * @param {Function} handler - Event handler
83
+ * @returns {MenuComponent} Component instance
84
+ */
85
+ on(event: string, handler: Function): MenuComponent {
86
+ component.on?.(event, handler);
87
+ return this;
88
+ },
89
+
90
+ /**
91
+ * Unregisters an event handler
92
+ * @param {string} event - Event name
93
+ * @param {Function} handler - Event handler
94
+ * @returns {MenuComponent} Component instance
95
+ */
96
+ off(event: string, handler: Function): MenuComponent {
97
+ component.off?.(event, handler);
98
+ return this;
99
+ },
100
+
101
+ /**
102
+ * Destroys the menu component and cleans up resources
103
+ * @returns {MenuComponent} Component instance
104
+ */
105
+ destroy(): MenuComponent {
106
+ // First close any open submenus
107
+ component.hide?.();
108
+
109
+ // Then destroy the component
110
+ lifecycle.destroy?.();
111
+
112
+ // Final cleanup - forcibly remove from DOM if still attached
113
+ if (component.element && component.element.parentNode) {
114
+ component.element.remove();
115
+ }
116
+
117
+ return this;
118
+ }
119
+ });
@@ -0,0 +1,54 @@
1
+ // src/components/menu/config.ts
2
+ import { PREFIX } from '../../core/config';
3
+ import {
4
+ createComponentConfig,
5
+ createElementConfig,
6
+ BaseComponentConfig
7
+ } from '../../core/config/component-config';
8
+ import { MenuConfig, BaseComponent, ApiOptions } from './types';
9
+
10
+ /**
11
+ * Default configuration for the Menu component
12
+ */
13
+ export const defaultConfig: MenuConfig = {
14
+ items: [],
15
+ stayOpenOnSelect: false
16
+ };
17
+
18
+ /**
19
+ * Creates the base configuration for Menu component
20
+ * @param {MenuConfig} config - User provided configuration
21
+ * @returns {MenuConfig} Complete configuration with defaults applied
22
+ */
23
+ export const createBaseConfig = (config: MenuConfig = {}): MenuConfig =>
24
+ createComponentConfig(defaultConfig, config, 'menu') as MenuConfig;
25
+
26
+ /**
27
+ * Generates element configuration for the Menu component
28
+ * @param {MenuConfig} config - Menu configuration
29
+ * @returns {Object} Element configuration object for withElement
30
+ */
31
+ export const getElementConfig = (config: MenuConfig) =>
32
+ createElementConfig(config, {
33
+ tag: 'div',
34
+ componentName: 'menu',
35
+ attrs: {
36
+ role: 'menu',
37
+ tabindex: '-1',
38
+ 'aria-hidden': 'true'
39
+ },
40
+ className: config.class
41
+ });
42
+
43
+ /**
44
+ * Creates API configuration for the Menu component
45
+ * @param {BaseComponent} comp - Component with lifecycle feature
46
+ * @returns {ApiOptions} API configuration object
47
+ */
48
+ export const getApiConfig = (comp: BaseComponent): ApiOptions => ({
49
+ lifecycle: {
50
+ destroy: comp.lifecycle?.destroy || (() => {})
51
+ }
52
+ });
53
+
54
+ export default defaultConfig;
@@ -0,0 +1,154 @@
1
+ // src/components/menu/constants.ts
2
+
3
+ /**
4
+ * Menu alignment options
5
+ */
6
+ export const MENU_ALIGN = {
7
+ LEFT: 'left',
8
+ RIGHT: 'right',
9
+ CENTER: 'center'
10
+ } as const;
11
+
12
+ /**
13
+ * Menu vertical alignment options
14
+ */
15
+ export const MENU_VERTICAL_ALIGN = {
16
+ TOP: 'top',
17
+ BOTTOM: 'bottom',
18
+ MIDDLE: 'middle'
19
+ } as const;
20
+
21
+ /**
22
+ * Menu item types
23
+ */
24
+ export const MENU_ITEM_TYPES = {
25
+ ITEM: 'item',
26
+ DIVIDER: 'divider'
27
+ } as const;
28
+
29
+ /**
30
+ * Menu events
31
+ */
32
+ export const MENU_EVENTS = {
33
+ SELECT: 'select',
34
+ OPEN: 'open',
35
+ CLOSE: 'close',
36
+ SUBMENU_OPEN: 'submenuOpen',
37
+ SUBMENU_CLOSE: 'submenuClose'
38
+ } as const;
39
+
40
+ /**
41
+ * Menu states
42
+ */
43
+ export const MENU_STATES = {
44
+ VISIBLE: 'visible',
45
+ HIDDEN: 'hidden',
46
+ DISABLED: 'disabled'
47
+ } as const;
48
+
49
+ /**
50
+ * Menu element classes
51
+ */
52
+ export const MENU_CLASSES = {
53
+ ROOT: 'menu',
54
+ ITEM: 'menu-item',
55
+ ITEM_CONTAINER: 'menu-item-container',
56
+ LIST: 'menu-list',
57
+ DIVIDER: 'menu-divider',
58
+ SUBMENU: 'menu--submenu'
59
+ } as const;
60
+
61
+ /**
62
+ * Menu positioning schema
63
+ * Used for validation in component creation
64
+ */
65
+ export const MENU_POSITION_SCHEMA = {
66
+ type: 'object',
67
+ properties: {
68
+ align: {
69
+ type: 'string',
70
+ enum: Object.values(MENU_ALIGN),
71
+ optional: true,
72
+ default: MENU_ALIGN.LEFT
73
+ },
74
+ vAlign: {
75
+ type: 'string',
76
+ enum: Object.values(MENU_VERTICAL_ALIGN),
77
+ optional: true,
78
+ default: MENU_VERTICAL_ALIGN.BOTTOM
79
+ },
80
+ offsetX: {
81
+ type: 'number',
82
+ optional: true,
83
+ default: 0
84
+ },
85
+ offsetY: {
86
+ type: 'number',
87
+ optional: true,
88
+ default: 0
89
+ }
90
+ }
91
+ } as const;
92
+
93
+ /**
94
+ * Validation schema for menu configuration
95
+ */
96
+ export const MENU_SCHEMA = {
97
+ type: 'object',
98
+ properties: {
99
+ items: {
100
+ type: 'array',
101
+ optional: true,
102
+ default: []
103
+ },
104
+ class: {
105
+ type: 'string',
106
+ optional: true
107
+ },
108
+ stayOpenOnSelect: {
109
+ type: 'boolean',
110
+ optional: true,
111
+ default: false
112
+ },
113
+ openingButton: {
114
+ optional: true
115
+ }
116
+ }
117
+ } as const;
118
+
119
+ /**
120
+ * Menu item schema
121
+ */
122
+ export const MENU_ITEM_SCHEMA = {
123
+ type: 'object',
124
+ properties: {
125
+ name: {
126
+ type: 'string',
127
+ required: true
128
+ },
129
+ text: {
130
+ type: 'string',
131
+ required: true
132
+ },
133
+ type: {
134
+ type: 'string',
135
+ enum: Object.values(MENU_ITEM_TYPES),
136
+ optional: true,
137
+ default: MENU_ITEM_TYPES.ITEM
138
+ },
139
+ disabled: {
140
+ type: 'boolean',
141
+ optional: true,
142
+ default: false
143
+ },
144
+ class: {
145
+ type: 'string',
146
+ optional: true
147
+ },
148
+ items: {
149
+ type: 'array',
150
+ optional: true,
151
+ description: 'Submenu items'
152
+ }
153
+ }
154
+ } as const;