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,172 @@
1
+ // src/components/chip/types.ts
2
+ import { CHIP_VARIANTS, CHIP_SIZES } from './constants';
3
+
4
+ /**
5
+ * Configuration interface for the Chip component
6
+ */
7
+ export interface ChipConfig {
8
+ /** Chip variant (filled, outlined, elevated, assist, filter, input, suggestion) */
9
+ variant?: keyof typeof CHIP_VARIANTS | string;
10
+
11
+ /** Chip size (small, medium, large) */
12
+ size?: keyof typeof CHIP_SIZES | string;
13
+
14
+ /** Whether the chip is initially disabled */
15
+ disabled?: boolean;
16
+
17
+ /** Whether the chip is initially selected */
18
+ selected?: boolean;
19
+
20
+ /** Initial chip text content */
21
+ text?: string;
22
+
23
+ /** Initial chip icon HTML content */
24
+ icon?: string;
25
+
26
+ /** Optional leading icon HTML content */
27
+ leadingIcon?: string;
28
+
29
+ /** Optional trailing icon HTML content */
30
+ trailingIcon?: string;
31
+
32
+ /** Additional CSS classes */
33
+ class?: string;
34
+
35
+ /** Chip value attribute */
36
+ value?: string;
37
+
38
+ /** Whether to enable ripple effect */
39
+ ripple?: boolean;
40
+
41
+ /** Ripple effect configuration */
42
+ rippleConfig?: {
43
+ duration?: number;
44
+ timing?: string;
45
+ opacity?: [string, string];
46
+ };
47
+
48
+ /** Function called when the trailing icon is clicked */
49
+ onTrailingIconClick?: (chip: ChipComponent) => void;
50
+
51
+ /** Function called when the chip is selected */
52
+ onSelect?: (chip: ChipComponent) => void;
53
+ }
54
+
55
+ /**
56
+ * Icon API interface
57
+ */
58
+ export interface IconAPI {
59
+ setIcon: (html: string) => IconAPI;
60
+ getIcon: () => string;
61
+ getElement: () => HTMLElement | null;
62
+ }
63
+
64
+ /**
65
+ * Text API interface
66
+ */
67
+ export interface TextAPI {
68
+ setText: (content: string) => TextAPI;
69
+ getText: () => string;
70
+ getElement: () => HTMLElement | null;
71
+ }
72
+
73
+ /**
74
+ * Chip component interface
75
+ */
76
+ export interface ChipComponent {
77
+ element: HTMLElement;
78
+ text: TextAPI;
79
+ icon: IconAPI;
80
+ disabled: {
81
+ enable: () => void;
82
+ disable: () => void;
83
+ isDisabled: () => boolean;
84
+ };
85
+ lifecycle: {
86
+ destroy: () => void;
87
+ };
88
+
89
+ /** Gets the class with the specified name */
90
+ getClass: (name: string) => string;
91
+
92
+ /** Gets the chip's value */
93
+ getValue: () => string | null;
94
+
95
+ /** Sets the chip's value */
96
+ setValue: (value: string) => ChipComponent;
97
+
98
+ /** Enables the chip */
99
+ enable: () => ChipComponent;
100
+
101
+ /** Disables the chip */
102
+ disable: () => ChipComponent;
103
+
104
+ /** Sets the chip's text content */
105
+ setText: (content: string) => ChipComponent;
106
+
107
+ /** Gets the chip's text content */
108
+ getText: () => string;
109
+
110
+ /** Sets the chip's icon */
111
+ setIcon: (icon: string) => ChipComponent;
112
+
113
+ /** Gets the chip's icon */
114
+ getIcon: () => string;
115
+
116
+ /** Sets the chip's trailing icon */
117
+ setTrailingIcon: (icon: string) => ChipComponent;
118
+
119
+ /** Checks if the chip is selected */
120
+ isSelected: () => boolean;
121
+
122
+ /** Sets the chip's selected state */
123
+ setSelected: (selected: boolean) => ChipComponent;
124
+
125
+ /** Toggles the chip's selected state */
126
+ toggleSelected: () => ChipComponent;
127
+
128
+ /** Destroys the chip component and cleans up resources */
129
+ destroy: () => void;
130
+
131
+ /** Adds event listener */
132
+ on: (event: string, handler: Function) => ChipComponent;
133
+
134
+ /** Removes event listener */
135
+ off: (event: string, handler: Function) => ChipComponent;
136
+
137
+ /** Add CSS classes */
138
+ addClass: (...classes: string[]) => ChipComponent;
139
+ }
140
+
141
+ /**
142
+ * API options interface
143
+ */
144
+ export interface ApiOptions {
145
+ disabled: {
146
+ enable: () => void;
147
+ disable: () => void;
148
+ };
149
+ lifecycle: {
150
+ destroy: () => void;
151
+ };
152
+ }
153
+
154
+ /**
155
+ * Base component interface
156
+ */
157
+ export interface BaseComponent {
158
+ element: HTMLElement;
159
+ getClass: (name?: string) => string;
160
+ config: any;
161
+ text?: TextAPI;
162
+ icon?: IconAPI;
163
+ disabled?: {
164
+ enable: () => void;
165
+ disable: () => void;
166
+ isDisabled: () => boolean;
167
+ };
168
+ lifecycle?: {
169
+ destroy: () => void;
170
+ };
171
+ [key: string]: any;
172
+ }
@@ -0,0 +1,72 @@
1
+ // src/components/list/api.ts
2
+ import { BaseComponent, ListComponent, ListItemConfig } from './types';
3
+
4
+ /**
5
+ * API options interface
6
+ */
7
+ interface ApiOptions {
8
+ disabled: {
9
+ enable: () => any;
10
+ disable: () => any;
11
+ };
12
+ lifecycle: {
13
+ destroy: () => void;
14
+ };
15
+ }
16
+
17
+ /**
18
+ * Enhances list component with API methods
19
+ * @param {ApiOptions} options - API configuration
20
+ * @returns {Function} Higher-order function that adds API methods to component
21
+ */
22
+ export const withAPI = ({ disabled, lifecycle }: ApiOptions) =>
23
+ (component: BaseComponent): ListComponent => ({
24
+ ...component as any,
25
+ element: component.element,
26
+ items: component.items as Map<string, any>,
27
+ selectedItems: component.selectedItems as Set<string>,
28
+
29
+ // Selection management
30
+ getSelected(): string[] {
31
+ return component.getSelected?.() || [];
32
+ },
33
+
34
+ setSelected(ids: string[]): void {
35
+ component.setSelected?.(ids);
36
+ },
37
+
38
+ // Item management
39
+ addItem(itemConfig: ListItemConfig): void {
40
+ component.addItem?.(itemConfig);
41
+ },
42
+
43
+ removeItem(id: string): void {
44
+ component.removeItem?.(id);
45
+ },
46
+
47
+ // Event handling
48
+ on(event: string, handler: Function): ListComponent {
49
+ component.on?.(event, handler);
50
+ return this;
51
+ },
52
+
53
+ off(event: string, handler: Function): ListComponent {
54
+ component.off?.(event, handler);
55
+ return this;
56
+ },
57
+
58
+ // State management
59
+ enable(): ListComponent {
60
+ disabled.enable();
61
+ return this;
62
+ },
63
+
64
+ disable(): ListComponent {
65
+ disabled.disable();
66
+ return this;
67
+ },
68
+
69
+ destroy(): void {
70
+ lifecycle.destroy();
71
+ }
72
+ });
@@ -0,0 +1,43 @@
1
+ // src/components/list/config.ts
2
+ import {
3
+ createComponentConfig,
4
+ createElementConfig,
5
+ BaseComponentConfig
6
+ } from '../../core/config/component-config';
7
+ import { ListConfig, BaseComponent } from './types';
8
+ import { LIST_TYPES, LIST_LAYOUTS } from './constants';
9
+
10
+ /**
11
+ * Default configuration for the List component
12
+ */
13
+ export const defaultConfig: ListConfig = {
14
+ type: LIST_TYPES.DEFAULT,
15
+ layout: LIST_LAYOUTS.HORIZONTAL,
16
+ items: []
17
+ };
18
+
19
+ /**
20
+ * Creates the base configuration for List component
21
+ * @param {ListConfig} config - User provided configuration
22
+ * @returns {ListConfig} Complete configuration with defaults applied
23
+ */
24
+ export const createBaseConfig = (config: ListConfig = {}): ListConfig =>
25
+ createComponentConfig(defaultConfig, config, 'list') as ListConfig;
26
+
27
+ /**
28
+ * Generates element configuration for the List component
29
+ * @param {ListConfig} config - List configuration
30
+ * @returns {Object} Element configuration object for withElement
31
+ */
32
+ export const getElementConfig = (config: ListConfig) =>
33
+ createElementConfig(config, {
34
+ tag: 'div',
35
+ role: config.type === LIST_TYPES.DEFAULT ? 'list' : 'listbox',
36
+ attrs: {
37
+ 'aria-multiselectable': config.type === LIST_TYPES.MULTI_SELECT ? 'true' : undefined
38
+ },
39
+ componentName: 'list',
40
+ className: config.class
41
+ });
42
+
43
+ export default defaultConfig;
@@ -1,4 +1,4 @@
1
- // src/components/list/constants.js
1
+ // src/components/list/constants.ts
2
2
 
3
3
  /**
4
4
  * List types/variants
@@ -8,7 +8,7 @@ export const LIST_TYPES = {
8
8
  SINGLE_SELECT: 'single', // Single selection list
9
9
  MULTI_SELECT: 'multi', // Multiple selection list
10
10
  RADIO: 'radio' // Radio button list
11
- }
11
+ } as const;
12
12
 
13
13
  /**
14
14
  * List layout variants
@@ -16,7 +16,15 @@ export const LIST_TYPES = {
16
16
  export const LIST_LAYOUTS = {
17
17
  HORIZONTAL: 'horizontal', // Default horizontal layout
18
18
  VERTICAL: 'vertical' // Items with more content stacked vertically
19
- }
19
+ } as const;
20
+
21
+ /**
22
+ * List item layouts
23
+ */
24
+ export const LIST_ITEM_LAYOUTS = {
25
+ HORIZONTAL: 'horizontal', // Default horizontal layout
26
+ VERTICAL: 'vertical' // Stacked layout with vertical alignment
27
+ } as const;
20
28
 
21
29
  /**
22
30
  * List element class names
@@ -27,11 +35,20 @@ export const LIST_CLASSES = {
27
35
  GROUP_TITLE: 'list-group-title',
28
36
  DIVIDER: 'list-divider',
29
37
  SECTION: 'list-section',
30
- SECTION_TITLE: 'list-section-title'
31
- }
38
+ SECTION_TITLE: 'list-section-title',
39
+ ITEM: 'list-item',
40
+ ITEM_CONTENT: 'list-item-content',
41
+ ITEM_LEADING: 'list-item-leading',
42
+ ITEM_TEXT: 'list-item-text',
43
+ ITEM_OVERLINE: 'list-item-overline',
44
+ ITEM_HEADLINE: 'list-item-headline',
45
+ ITEM_SUPPORTING: 'list-item-supporting',
46
+ ITEM_META: 'list-item-meta',
47
+ ITEM_TRAILING: 'list-item-trailing'
48
+ } as const;
32
49
 
33
50
  /**
34
- * List configuration schema
51
+ * List validation schema
35
52
  */
36
53
  export const LIST_SCHEMA = {
37
54
  type: 'object',
@@ -86,4 +103,14 @@ export const LIST_SCHEMA = {
86
103
  optional: true
87
104
  }
88
105
  }
89
- }
106
+ } as const;
107
+
108
+ /**
109
+ * List item states
110
+ */
111
+ export const LIST_ITEM_STATES = {
112
+ SELECTED: 'selected',
113
+ DISABLED: 'disabled',
114
+ FOCUSED: 'focused',
115
+ HOVERED: 'hovered'
116
+ } as const;
@@ -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'