mtrl 0.1.3 → 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 (224) 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/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} +3 -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/card/card.js +0 -102
  138. package/src/components/card/config.js +0 -16
  139. package/src/components/card/index.js +0 -7
  140. package/src/components/card/media.js +0 -56
  141. package/src/components/checkbox/api.js +0 -45
  142. package/src/components/checkbox/checkbox.js +0 -96
  143. package/src/components/checkbox/index.js +0 -2
  144. package/src/components/container/api.js +0 -42
  145. package/src/components/container/container.js +0 -45
  146. package/src/components/container/index.js +0 -2
  147. package/src/components/container/styles.scss +0 -66
  148. package/src/components/list/index.js +0 -2
  149. package/src/components/list/list-item.js +0 -147
  150. package/src/components/list/list.js +0 -267
  151. package/src/components/menu/api.js +0 -117
  152. package/src/components/menu/constants.js +0 -42
  153. package/src/components/menu/features/items-manager.js +0 -375
  154. package/src/components/menu/features/keyboard-navigation.js +0 -129
  155. package/src/components/menu/features/positioning.js +0 -125
  156. package/src/components/menu/index.js +0 -2
  157. package/src/components/menu/menu-item.js +0 -41
  158. package/src/components/menu/menu.js +0 -54
  159. package/src/components/navigation/api.js +0 -43
  160. package/src/components/navigation/index.js +0 -2
  161. package/src/components/navigation/nav-item.js +0 -137
  162. package/src/components/navigation/navigation.js +0 -55
  163. package/src/components/snackbar/api.js +0 -125
  164. package/src/components/snackbar/features.js +0 -69
  165. package/src/components/snackbar/index.js +0 -2
  166. package/src/components/snackbar/position.js +0 -63
  167. package/src/components/snackbar/queue.js +0 -74
  168. package/src/components/snackbar/snackbar.js +0 -70
  169. package/src/components/switch/api.js +0 -44
  170. package/src/components/switch/index.js +0 -2
  171. package/src/components/switch/switch.js +0 -71
  172. package/src/components/textfield/api.js +0 -49
  173. package/src/components/textfield/index.js +0 -2
  174. package/src/components/textfield/textfield.js +0 -68
  175. package/src/core/build/_ripple.scss +0 -79
  176. package/src/core/build/constants.js +0 -51
  177. package/src/core/build/icon.js +0 -78
  178. package/src/core/build/ripple.js +0 -159
  179. package/src/core/build/text.js +0 -54
  180. package/src/core/compose/base.js +0 -8
  181. package/src/core/compose/component.js +0 -225
  182. package/src/core/compose/features/checkable.js +0 -114
  183. package/src/core/compose/features/disabled.js +0 -64
  184. package/src/core/compose/features/events.js +0 -48
  185. package/src/core/compose/features/icon.js +0 -33
  186. package/src/core/compose/features/index.js +0 -20
  187. package/src/core/compose/features/input.js +0 -100
  188. package/src/core/compose/features/lifecycle.js +0 -69
  189. package/src/core/compose/features/position.js +0 -60
  190. package/src/core/compose/features/ripple.js +0 -32
  191. package/src/core/compose/features/size.js +0 -9
  192. package/src/core/compose/features/style.js +0 -12
  193. package/src/core/compose/features/text.js +0 -17
  194. package/src/core/compose/features/textinput.js +0 -114
  195. package/src/core/compose/features/textlabel.js +0 -28
  196. package/src/core/compose/features/track.js +0 -49
  197. package/src/core/compose/features/variant.js +0 -9
  198. package/src/core/compose/features/withEvents.js +0 -67
  199. package/src/core/compose/index.js +0 -16
  200. package/src/core/config.js +0 -140
  201. package/src/core/dom/classes.js +0 -70
  202. package/src/core/dom/create.js +0 -132
  203. package/src/core/dom/events.js +0 -175
  204. package/src/core/dom/index.js +0 -5
  205. package/src/core/dom/utils.js +0 -22
  206. package/src/core/index.js +0 -23
  207. package/src/core/state/disabled.js +0 -51
  208. package/src/core/state/emitter.js +0 -63
  209. package/src/core/state/events.js +0 -29
  210. package/src/core/state/index.js +0 -6
  211. package/src/core/state/lifecycle.js +0 -64
  212. package/src/core/state/store.js +0 -112
  213. package/src/core/utils/index.js +0 -39
  214. package/src/core/utils/object.js +0 -22
  215. package/src/core/utils/validate.js +0 -37
  216. /package/src/components/checkbox/{styles.scss → _styles.scss} +0 -0
  217. /package/src/components/checkbox/{constants.js → constants.ts} +0 -0
  218. /package/src/components/list/{styles.scss → _styles.scss} +0 -0
  219. /package/src/components/menu/{styles.scss → _styles.scss} +0 -0
  220. /package/src/components/navigation/{styles.scss → _styles.scss} +0 -0
  221. /package/src/components/snackbar/{styles.scss → _styles.scss} +0 -0
  222. /package/src/components/switch/{styles.scss → _styles.scss} +0 -0
  223. /package/src/components/switch/{constants.js → constants.ts} +0 -0
  224. /package/src/components/textfield/{styles.scss → _styles.scss} +0 -0
@@ -0,0 +1,224 @@
1
+ // src/components/list/features.ts
2
+ import {
3
+ ListConfig,
4
+ ListItemConfig,
5
+ ListItemData,
6
+ BaseComponent,
7
+ SelectionChangeEvent
8
+ } from './types';
9
+ import { LIST_TYPES, LIST_LAYOUTS } from './constants';
10
+ import { createDivider, createSectionTitle } from './utils';
11
+ import createListItem from './list-item';
12
+
13
+ /**
14
+ * Enhances component with list content and functionality
15
+ * @param {ListConfig} config - List configuration
16
+ * @returns {Function} Higher-order function that adds list features to component
17
+ */
18
+ export const withListContent = (config: ListConfig) =>
19
+ (component: BaseComponent): BaseComponent => {
20
+ const { element } = component;
21
+ const prefix = component.prefix || config.prefix || '';
22
+ const items = new Map<string, ListItemData>();
23
+ const selectedItems = new Set<string>();
24
+
25
+ // Set list type
26
+ element.setAttribute('data-type', config.type || LIST_TYPES.DEFAULT);
27
+
28
+ // Handle keyboard navigation
29
+ const handleKeyDown = (event: KeyboardEvent): void => {
30
+ const focusedItem = document.activeElement as HTMLElement;
31
+ if (!focusedItem?.classList.contains(`${prefix}-list-item`)) return;
32
+
33
+ const listItems = Array.from(element.querySelectorAll(`.${prefix}-list-item`)) as HTMLElement[];
34
+ const currentIndex = listItems.indexOf(focusedItem);
35
+
36
+ switch (event.key) {
37
+ case 'ArrowDown':
38
+ case 'ArrowRight':
39
+ event.preventDefault();
40
+ const nextItem = listItems[currentIndex + 1];
41
+ if (nextItem) nextItem.focus();
42
+ break;
43
+ case 'ArrowUp':
44
+ case 'ArrowLeft':
45
+ event.preventDefault();
46
+ const prevItem = listItems[currentIndex - 1];
47
+ if (prevItem) prevItem.focus();
48
+ break;
49
+ case 'Home':
50
+ event.preventDefault();
51
+ listItems[0]?.focus();
52
+ break;
53
+ case 'End':
54
+ event.preventDefault();
55
+ listItems[listItems.length - 1]?.focus();
56
+ break;
57
+ case ' ':
58
+ case 'Enter':
59
+ event.preventDefault();
60
+ handleItemClick(focusedItem);
61
+ break;
62
+ }
63
+ };
64
+
65
+ // Handle item selection
66
+ const handleItemClick = (itemElement: HTMLElement): void => {
67
+ const id = itemElement.dataset.id;
68
+ if (!id) return;
69
+
70
+ const itemData = items.get(id);
71
+ if (!itemData || itemData.disabled) return;
72
+
73
+ switch (config.type) {
74
+ case LIST_TYPES.SINGLE_SELECT:
75
+ // Deselect previously selected item
76
+ selectedItems.forEach(selectedId => {
77
+ const selected = items.get(selectedId);
78
+ if (selected) {
79
+ selected.element.classList.remove(`${prefix}-list-item--selected`);
80
+ selected.element.setAttribute('aria-selected', 'false');
81
+ }
82
+ });
83
+ selectedItems.clear();
84
+
85
+ // Select new item
86
+ itemElement.classList.add(`${prefix}-list-item--selected`);
87
+ itemElement.setAttribute('aria-selected', 'true');
88
+ selectedItems.add(id);
89
+ break;
90
+
91
+ case LIST_TYPES.MULTI_SELECT:
92
+ const isSelected = selectedItems.has(id);
93
+ if (isSelected) {
94
+ itemElement.classList.remove(`${prefix}-list-item--selected`);
95
+ itemElement.setAttribute('aria-selected', 'false');
96
+ selectedItems.delete(id);
97
+ } else {
98
+ itemElement.classList.add(`${prefix}-list-item--selected`);
99
+ itemElement.setAttribute('aria-selected', 'true');
100
+ selectedItems.add(id);
101
+ }
102
+ break;
103
+ }
104
+
105
+ component.emit?.('selectionChange', {
106
+ selected: Array.from(selectedItems),
107
+ item: itemData,
108
+ type: config.type
109
+ } as SelectionChangeEvent);
110
+ };
111
+
112
+ // Create items from configuration
113
+ const createItems = (itemsConfig: ListItemConfig[] = [], container: HTMLElement = element): void => {
114
+ itemsConfig.forEach((itemConfig, index) => {
115
+ if (itemConfig.divider) {
116
+ container.appendChild(createDivider(prefix));
117
+ return;
118
+ }
119
+
120
+ const item = createListItem({
121
+ ...itemConfig,
122
+ layout: config.layout || LIST_LAYOUTS.HORIZONTAL,
123
+ role: config.type === LIST_TYPES.RADIO ? 'radio' : 'option'
124
+ });
125
+
126
+ item.element.dataset.id = itemConfig.id;
127
+ item.element.tabIndex = index === 0 ? 0 : -1;
128
+ items.set(itemConfig.id, item);
129
+
130
+ if (itemConfig.selected) {
131
+ selectedItems.add(itemConfig.id);
132
+ item.element.classList.add(`${prefix}-list-item--selected`);
133
+ item.element.setAttribute('aria-selected', 'true');
134
+ }
135
+
136
+ container.appendChild(item.element);
137
+ });
138
+ };
139
+
140
+ // Create sections if configured
141
+ if (config.sections?.length) {
142
+ config.sections.forEach(section => {
143
+ const sectionEl = document.createElement('div');
144
+ sectionEl.className = `${prefix}-list-section`;
145
+ sectionEl.setAttribute('role', 'group');
146
+ if (section.title) {
147
+ sectionEl.appendChild(createSectionTitle(section.title, prefix));
148
+ }
149
+ createItems(section.items, sectionEl);
150
+ element.appendChild(sectionEl);
151
+ });
152
+ } else if (config.items) {
153
+ createItems(config.items);
154
+ }
155
+
156
+ // Add event listeners
157
+ element.addEventListener('click', (event: MouseEvent) => {
158
+ const target = event.target as HTMLElement;
159
+ const item = target.closest(`.${prefix}-list-item`) as HTMLElement;
160
+ if (item) handleItemClick(item);
161
+ });
162
+
163
+ element.addEventListener('keydown', handleKeyDown);
164
+
165
+ // Clean up
166
+ if (component.lifecycle) {
167
+ const originalDestroy = component.lifecycle.destroy;
168
+ component.lifecycle.destroy = () => {
169
+ items.clear();
170
+ selectedItems.clear();
171
+ element.removeEventListener('keydown', handleKeyDown);
172
+ originalDestroy?.();
173
+ };
174
+ }
175
+
176
+ return {
177
+ ...component,
178
+ items,
179
+ selectedItems,
180
+
181
+ // Public methods
182
+ getSelected: () => Array.from(selectedItems),
183
+
184
+ setSelected: (ids: string[]) => {
185
+ selectedItems.clear();
186
+ items.forEach((item, id) => {
187
+ const isSelected = ids.includes(id);
188
+ item.element.classList.toggle(`${prefix}-list-item--selected`, isSelected);
189
+ item.element.setAttribute('aria-selected', isSelected.toString());
190
+ if (isSelected) selectedItems.add(id);
191
+ });
192
+ component.emit?.('selectionChange', {
193
+ selected: Array.from(selectedItems),
194
+ type: config.type
195
+ } as SelectionChangeEvent);
196
+ },
197
+
198
+ addItem: (itemConfig: ListItemConfig) => {
199
+ if (items.has(itemConfig.id)) return;
200
+
201
+ const item = createListItem({
202
+ ...itemConfig,
203
+ layout: config.layout || LIST_LAYOUTS.HORIZONTAL
204
+ });
205
+
206
+ item.element.dataset.id = itemConfig.id;
207
+ items.set(itemConfig.id, item);
208
+ element.appendChild(item.element);
209
+
210
+ component.emit?.('itemAdded', { id: itemConfig.id, item });
211
+ },
212
+
213
+ removeItem: (id: string) => {
214
+ const item = items.get(id);
215
+ if (!item) return;
216
+
217
+ item.element.remove();
218
+ items.delete(id);
219
+ selectedItems.delete(id);
220
+
221
+ component.emit?.('itemRemoved', { id, item });
222
+ }
223
+ };
224
+ };
@@ -0,0 +1,14 @@
1
+ // src/components/list/index.ts
2
+ export { default } from './list'
3
+ export { default as createListItem } from './list-item'
4
+ export {
5
+ LIST_TYPES,
6
+ LIST_LAYOUTS,
7
+ LIST_ITEM_LAYOUTS
8
+ } from './constants'
9
+ export {
10
+ ListConfig,
11
+ ListComponent,
12
+ ListItemConfig,
13
+ ListSectionConfig
14
+ } from './types'
@@ -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
+ };