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
@@ -1,45 +0,0 @@
1
- // src/components/container/container.js
2
- import { PREFIX } from '../../core/config'
3
- import { pipe } from '../../core/compose'
4
- import { createBase, withElement } from '../../core/compose/component'
5
- import { withEvents, withLifecycle } from '../../core/compose/features'
6
- import { withAPI } from './api'
7
-
8
- /**
9
- * Creates a new Container component
10
- * @param {Object} config - Container configuration
11
- * @param {string} [config.variant] - Visual variant
12
- * @param {number} [config.elevation] - Elevation level
13
- * @param {string} [config.class] - Additional CSS classes
14
- */
15
- const createContainer = (config = {}) => {
16
- const baseConfig = {
17
- ...config,
18
- componentName: 'container',
19
- prefix: PREFIX
20
- }
21
-
22
- try {
23
- return pipe(
24
- createBase,
25
- withElement({
26
- tag: 'div',
27
- componentName: 'container',
28
- className: [
29
- config.variant && `${PREFIX}-container--${config.variant}`,
30
- config.elevation && `${PREFIX}-container--elevation-${config.elevation}`,
31
- config.class
32
- ]
33
- }),
34
- withEvents(),
35
- withLifecycle(),
36
- comp => withAPI({
37
- lifecycle: comp.lifecycle
38
- })(comp)
39
- )(baseConfig)
40
- } catch (error) {
41
- throw new Error(`Failed to create container: ${error.message}`)
42
- }
43
- }
44
-
45
- export default createContainer
@@ -1,2 +0,0 @@
1
- // src/components/container/index.js
2
- export { default } from './container.js'
@@ -1,66 +0,0 @@
1
- // src/components/container/_container.scss
2
- @use '../../styles/abstract/base' as base;
3
- @use '../../styles/abstract/variables' as v;
4
- @use '../../styles/abstract/functions' as f;
5
- @use '../../styles/abstract/mixins' as m;
6
- @use '../../styles/abstract/theme' as t;
7
-
8
- $component: '#{base.$prefix}-container';
9
-
10
- .#{$component} {
11
- @include m.shape('medium');
12
- @include m.motion-transition(
13
- background-color,
14
- box-shadow
15
- );
16
-
17
- padding: 16px;
18
- background-color: t.color('surface-container');
19
-
20
- // Surface container variants
21
- &--low {
22
- background-color: t.color('surface-container-low');
23
- }
24
-
25
- &--lowest {
26
- background-color: t.color('surface-container-lowest');
27
- }
28
-
29
- &--high {
30
- background-color: t.color('surface-container-high');
31
- }
32
-
33
- &--highest {
34
- background-color: t.color('surface-container-highest');
35
- }
36
-
37
- // Elevation variants
38
- &--elevation-0 {
39
- @include m.elevation(0);
40
- }
41
-
42
- &--elevation-1 {
43
- @include m.elevation(1);
44
- }
45
-
46
- &--elevation-2 {
47
- @include m.elevation(2);
48
- }
49
-
50
- &--elevation-3 {
51
- @include m.elevation(3);
52
- }
53
-
54
- &--elevation-4 {
55
- @include m.elevation(4);
56
- }
57
-
58
- // Accessibility
59
- @include m.reduced-motion {
60
- transition: none;
61
- }
62
-
63
- @include m.high-contrast {
64
- border: 1px solid currentColor;
65
- }
66
- }
@@ -1,2 +0,0 @@
1
- // src/components/list/index.js
2
- export { default } from './list.js'
@@ -1,147 +0,0 @@
1
- // src/components/list/list-item.js
2
-
3
- import { PREFIX } from '../../core/config'
4
- import { pipe } from '../../core/compose'
5
- import { createBase, withElement } from '../../core/compose/component'
6
- import { withEvents, withDisabled } from '../../core/compose/features'
7
-
8
- /**
9
- * Supported list item layouts
10
- */
11
- export const LIST_ITEM_LAYOUTS = {
12
- HORIZONTAL: 'horizontal', // Default horizontal layout
13
- VERTICAL: 'vertical' // Stacked layout with vertical alignment
14
- }
15
-
16
- /**
17
- * Creates a DOM element with optional class and content
18
- * @param {string} tag - HTML tag name
19
- * @param {string} className - CSS class name
20
- * @param {string|HTMLElement} [content] - Element content or child element
21
- * @returns {HTMLElement} Created element
22
- */
23
- const createElement = (tag, className, content) => {
24
- const element = document.createElement(tag)
25
- element.className = className
26
- if (content) {
27
- if (typeof content === 'string') {
28
- element.textContent = content
29
- } else {
30
- element.appendChild(content)
31
- }
32
- }
33
- return element
34
- }
35
-
36
- /**
37
- * Creates a list item component
38
- * @param {Object} config - List item configuration
39
- * @param {string} [config.layout='horizontal'] - Item layout (horizontal/vertical)
40
- * @param {string|HTMLElement} [config.leading] - Leading content (icon/avatar)
41
- * @param {string} [config.headline] - Primary text
42
- * @param {string} [config.supportingText] - Secondary text
43
- * @param {string|HTMLElement} [config.trailing] - Trailing content (icon/meta)
44
- * @param {string} [config.overline] - Text above headline (vertical only)
45
- * @param {string|HTMLElement} [config.meta] - Meta information (vertical only)
46
- * @param {boolean} [config.disabled] - Disabled state
47
- * @param {boolean} [config.selected] - Selected state
48
- * @param {string} [config.class] - Additional CSS classes
49
- * @param {string} [config.role='listitem'] - ARIA role
50
- */
51
- const createListItem = (config = {}) => {
52
- const baseConfig = {
53
- ...config,
54
- componentName: 'list-item',
55
- prefix: PREFIX
56
- }
57
-
58
- const createContent = (component) => {
59
- const { element } = component
60
- const { prefix } = baseConfig
61
- const isVertical = config.layout === LIST_ITEM_LAYOUTS.VERTICAL
62
-
63
- // Create content container
64
- const content = createElement('div', `${prefix}-list-item-content`)
65
-
66
- // Add leading content (icon/avatar)
67
- if (config.leading) {
68
- const leading = createElement('div', `${prefix}-list-item-leading`)
69
- if (typeof config.leading === 'string') {
70
- leading.innerHTML = config.leading
71
- } else {
72
- leading.appendChild(config.leading)
73
- }
74
- element.appendChild(leading)
75
- }
76
-
77
- // Text wrapper for proper alignment
78
- const textWrapper = createElement('div', `${prefix}-list-item-text`)
79
-
80
- // Add overline text (vertical only)
81
- if (isVertical && config.overline) {
82
- const overline = createElement('div', `${prefix}-list-item-overline`, config.overline)
83
- textWrapper.appendChild(overline)
84
- }
85
-
86
- // Add headline (primary text)
87
- if (config.headline) {
88
- const headline = createElement('div', `${prefix}-list-item-headline`, config.headline)
89
- textWrapper.appendChild(headline)
90
- }
91
-
92
- // Add supporting text (secondary text)
93
- if (config.supportingText) {
94
- const supporting = createElement('div', `${prefix}-list-item-supporting`, config.supportingText)
95
- textWrapper.appendChild(supporting)
96
- }
97
-
98
- content.appendChild(textWrapper)
99
-
100
- // Add meta information (vertical only)
101
- if (isVertical && config.meta) {
102
- const meta = createElement('div', `${prefix}-list-item-meta`)
103
- if (typeof config.meta === 'string') {
104
- meta.textContent = config.meta
105
- } else {
106
- meta.appendChild(config.meta)
107
- }
108
- content.appendChild(meta)
109
- }
110
-
111
- element.appendChild(content)
112
-
113
- // Add trailing content (icon/meta)
114
- if (config.trailing) {
115
- const trailing = createElement('div', `${prefix}-list-item-trailing`)
116
- if (typeof config.trailing === 'string') {
117
- trailing.innerHTML = config.trailing
118
- } else {
119
- trailing.appendChild(config.trailing)
120
- }
121
- element.appendChild(trailing)
122
- }
123
-
124
- // Handle selected state
125
- if (config.selected) {
126
- element.setAttribute('aria-selected', 'true')
127
- element.classList.add(`${prefix}-list-item--selected`)
128
- }
129
-
130
- return component
131
- }
132
-
133
- return pipe(
134
- createBase,
135
- withEvents(),
136
- withElement({
137
- tag: 'div',
138
- role: config.role || 'listitem',
139
- componentName: 'list-item',
140
- className: `${config.layout === LIST_ITEM_LAYOUTS.VERTICAL ? 'vertical' : ''} ${config.class || ''}`
141
- }),
142
- withDisabled(),
143
- createContent
144
- )(baseConfig)
145
- }
146
-
147
- export default createListItem
@@ -1,267 +0,0 @@
1
- // src/components/list/list.js
2
-
3
- import { PREFIX } from '../../core/config'
4
- import { pipe } from '../../core/compose'
5
- import { createBase, withElement } from '../../core/compose/component'
6
- import { withEvents, withDisabled, withLifecycle } from '../../core/compose/features'
7
- import createListItem from './list-item'
8
- import { LIST_TYPES, LIST_LAYOUTS, LIST_CLASSES } from './constants'
9
-
10
- /**
11
- * Creates a divider element
12
- * @param {string} prefix - CSS class prefix
13
- * @returns {HTMLElement} Divider element
14
- */
15
- const createDivider = (prefix) => {
16
- const divider = document.createElement('div')
17
- divider.className = `${prefix}-${LIST_CLASSES.DIVIDER}`
18
- divider.setAttribute('role', 'separator')
19
- return divider
20
- }
21
-
22
- /**
23
- * Creates a section title element
24
- * @param {string} title - Section title text
25
- * @param {string} prefix - CSS class prefix
26
- * @returns {HTMLElement} Section title element
27
- */
28
- const createSectionTitle = (title, prefix) => {
29
- const titleEl = document.createElement('div')
30
- titleEl.className = `${prefix}-${LIST_CLASSES.SECTION_TITLE}`
31
- titleEl.textContent = title
32
- return titleEl
33
- }
34
-
35
- /**
36
- * Creates a list component
37
- * @param {Object} config - List configuration
38
- */
39
- const createList = (config = {}) => {
40
- const baseConfig = {
41
- ...config,
42
- componentName: 'list',
43
- prefix: PREFIX
44
- }
45
-
46
- const createContent = (component) => {
47
- const { element, prefix } = component
48
- const items = new Map()
49
- const selectedItems = new Set()
50
-
51
- // Set list type
52
- element.setAttribute('data-type', config.type || LIST_TYPES.DEFAULT)
53
-
54
- // Handle keyboard navigation
55
- const handleKeyDown = (event) => {
56
- const focusedItem = document.activeElement
57
- if (!focusedItem?.classList.contains(`${prefix}-list-item`)) return
58
-
59
- const items = Array.from(element.querySelectorAll(`.${prefix}-list-item`))
60
- const currentIndex = items.indexOf(focusedItem)
61
-
62
- switch (event.key) {
63
- case 'ArrowDown':
64
- case 'ArrowRight':
65
- event.preventDefault()
66
- const nextItem = items[currentIndex + 1]
67
- if (nextItem) nextItem.focus()
68
- break
69
- case 'ArrowUp':
70
- case 'ArrowLeft':
71
- event.preventDefault()
72
- const prevItem = items[currentIndex - 1]
73
- if (prevItem) prevItem.focus()
74
- break
75
- case 'Home':
76
- event.preventDefault()
77
- items[0]?.focus()
78
- break
79
- case 'End':
80
- event.preventDefault()
81
- items[items.length - 1]?.focus()
82
- break
83
- case ' ':
84
- case 'Enter':
85
- event.preventDefault()
86
- handleItemClick(focusedItem)
87
- break
88
- }
89
- }
90
-
91
- // Handle item selection
92
- const handleItemClick = (itemElement) => {
93
- const id = itemElement.dataset.id
94
- if (!id) return
95
-
96
- const itemData = items.get(id)
97
- if (!itemData || itemData.disabled) return
98
-
99
- switch (config.type) {
100
- case LIST_TYPES.SINGLE_SELECT:
101
- // Deselect previously selected item
102
- selectedItems.forEach(selectedId => {
103
- const selected = items.get(selectedId)
104
- if (selected) {
105
- selected.element.classList.remove(`${prefix}-list-item--selected`)
106
- selected.element.setAttribute('aria-selected', 'false')
107
- }
108
- })
109
- selectedItems.clear()
110
-
111
- // Select new item
112
- itemElement.classList.add(`${prefix}-list-item--selected`)
113
- itemElement.setAttribute('aria-selected', 'true')
114
- selectedItems.add(id)
115
- break
116
-
117
- case LIST_TYPES.MULTI_SELECT:
118
- const isSelected = selectedItems.has(id)
119
- if (isSelected) {
120
- itemElement.classList.remove(`${prefix}-list-item--selected`)
121
- itemElement.setAttribute('aria-selected', 'false')
122
- selectedItems.delete(id)
123
- } else {
124
- itemElement.classList.add(`${prefix}-list-item--selected`)
125
- itemElement.setAttribute('aria-selected', 'true')
126
- selectedItems.add(id)
127
- }
128
- break
129
- }
130
-
131
- component.emit('selectionChange', {
132
- selected: Array.from(selectedItems),
133
- item: itemData,
134
- type: config.type
135
- })
136
- }
137
-
138
- // Create items from configuration
139
- const createItems = (itemsConfig = [], container = element) => {
140
- itemsConfig.forEach((itemConfig, index) => {
141
- if (itemConfig.divider) {
142
- container.appendChild(createDivider(prefix))
143
- return
144
- }
145
-
146
- const item = createListItem({
147
- ...itemConfig,
148
- layout: config.layout || LIST_LAYOUTS.HORIZONTAL,
149
- role: config.type === LIST_TYPES.RADIO ? 'radio' : 'option'
150
- })
151
-
152
- item.element.dataset.id = itemConfig.id
153
- item.element.tabIndex = index === 0 ? 0 : -1
154
- items.set(itemConfig.id, item)
155
-
156
- if (itemConfig.selected) {
157
- selectedItems.add(itemConfig.id)
158
- item.element.classList.add(`${prefix}-list-item--selected`)
159
- item.element.setAttribute('aria-selected', 'true')
160
- }
161
-
162
- container.appendChild(item.element)
163
- })
164
- }
165
-
166
- // Create sections if configured
167
- if (config.sections?.length) {
168
- config.sections.forEach(section => {
169
- const sectionEl = document.createElement('div')
170
- sectionEl.className = `${prefix}-${LIST_CLASSES.SECTION}`
171
- sectionEl.setAttribute('role', 'group')
172
- if (section.title) {
173
- sectionEl.appendChild(createSectionTitle(section.title, prefix))
174
- }
175
- createItems(section.items, sectionEl)
176
- element.appendChild(sectionEl)
177
- })
178
- } else {
179
- createItems(config.items)
180
- }
181
-
182
- // Add event listeners
183
- element.addEventListener('click', (event) => {
184
- const item = event.target.closest(`.${prefix}-list-item`)
185
- if (item) handleItemClick(item)
186
- })
187
-
188
- element.addEventListener('keydown', handleKeyDown)
189
-
190
- // Clean up
191
- if (component.lifecycle) {
192
- const originalDestroy = component.lifecycle.destroy
193
- component.lifecycle.destroy = () => {
194
- items.clear()
195
- selectedItems.clear()
196
- element.removeEventListener('keydown', handleKeyDown)
197
- originalDestroy?.()
198
- }
199
- }
200
-
201
- return {
202
- ...component,
203
- items,
204
- selectedItems,
205
-
206
- // Public methods
207
- getSelected: () => Array.from(selectedItems),
208
-
209
- setSelected: (ids) => {
210
- selectedItems.clear()
211
- items.forEach((item, id) => {
212
- const isSelected = ids.includes(id)
213
- item.element.classList.toggle(`${prefix}-list-item--selected`, isSelected)
214
- item.element.setAttribute('aria-selected', isSelected.toString())
215
- if (isSelected) selectedItems.add(id)
216
- })
217
- component.emit('selectionChange', {
218
- selected: Array.from(selectedItems),
219
- type: config.type
220
- })
221
- },
222
-
223
- addItem: (itemConfig) => {
224
- if (items.has(itemConfig.id)) return
225
-
226
- const item = createListItem({
227
- ...itemConfig,
228
- layout: config.layout || LIST_LAYOUTS.HORIZONTAL
229
- })
230
-
231
- item.element.dataset.id = itemConfig.id
232
- items.set(itemConfig.id, item)
233
- element.appendChild(item.element)
234
-
235
- component.emit('itemAdded', { id: itemConfig.id, item })
236
- },
237
-
238
- removeItem: (id) => {
239
- const item = items.get(id)
240
- if (!item) return
241
-
242
- item.element.remove()
243
- items.delete(id)
244
- selectedItems.delete(id)
245
-
246
- component.emit('itemRemoved', { id, item })
247
- }
248
- }
249
- }
250
-
251
- return pipe(
252
- createBase,
253
- withEvents(),
254
- withElement({
255
- tag: 'div',
256
- role: config.type === LIST_TYPES.DEFAULT ? 'list' : 'listbox',
257
- 'aria-multiselectable': config.type === LIST_TYPES.MULTI_SELECT ? 'true' : undefined,
258
- componentName: LIST_CLASSES.ROOT,
259
- className: config.class
260
- }),
261
- withDisabled(),
262
- withLifecycle(),
263
- createContent
264
- )(baseConfig)
265
- }
266
-
267
- export default createList
@@ -1,117 +0,0 @@
1
- // src/components/menu/api.js
2
-
3
- /**
4
- * Enhances menu component with API methods
5
- * @param {Object} options - API configuration
6
- * @param {Object} options.lifecycle - Lifecycle handlers
7
- */
8
- export const withAPI = ({ lifecycle }) => (component) => ({
9
- ...component,
10
- element: component.element,
11
-
12
- /**
13
- * Shows the menu
14
- * @returns {Object} Component instance
15
- */
16
- show () {
17
- component.show()
18
- return this
19
- },
20
-
21
- /**
22
- * Hides the menu
23
- * @returns {Object} Component instance
24
- */
25
- hide () {
26
- component.hide()
27
- return this
28
- },
29
-
30
- /**
31
- * Positions the menu relative to a target
32
- * @param {HTMLElement} target - Target element
33
- * @param {Object} options - Position options
34
- * @returns {Object} Component instance
35
- */
36
- position (target, options) {
37
- component.position(target, options)
38
- return this
39
- },
40
-
41
- /**
42
- * Adds an item to the menu
43
- * @param {Object} config - Item configuration
44
- * @returns {Object} Component instance
45
- */
46
- addItem (config) {
47
- component.addItem?.(config)
48
- return this
49
- },
50
-
51
- /**
52
- * Removes an item by name
53
- * @param {string} name - Item name to remove
54
- * @returns {Object} Component instance
55
- */
56
- removeItem (name) {
57
- component.removeItem?.(name)
58
- return this
59
- },
60
-
61
- /**
62
- * Gets all registered items
63
- * @returns {Map} Map of item names to configurations
64
- */
65
- getItems () {
66
- return component.getItems?.()
67
- },
68
-
69
- /**
70
- * Checks if the menu is currently visible
71
- * @returns {boolean} Whether the menu is visible
72
- */
73
- isVisible () {
74
- return component.isVisible?.()
75
- },
76
-
77
- /**
78
- * Registers an event handler
79
- * @param {string} event - Event name
80
- * @param {Function} handler - Event handler
81
- * @returns {Object} Component instance
82
- */
83
- on (event, handler) {
84
- component.on(event, handler)
85
- return this
86
- },
87
-
88
- /**
89
- * Unregisters an event handler
90
- * @param {string} event - Event name
91
- * @param {Function} handler - Event handler
92
- * @returns {Object} Component instance
93
- */
94
- off (event, handler) {
95
- component.off(event, handler)
96
- return this
97
- },
98
-
99
- /**
100
- * Destroys the menu component and cleans up resources
101
- * @returns {Object} Component instance
102
- */
103
- destroy () {
104
- // First close any open submenus
105
- component.hide?.()
106
-
107
- // Then destroy the component
108
- lifecycle.destroy?.()
109
-
110
- // Final cleanup - forcibly remove from DOM if still attached
111
- if (component.element && component.element.parentNode) {
112
- component.element.remove()
113
- }
114
-
115
- return this
116
- }
117
- })
@@ -1,42 +0,0 @@
1
- // src/components/menu/constants.js
2
-
3
- /**
4
- * Menu alignment options
5
- * @enum {string}
6
- */
7
- export const MENU_ALIGN = {
8
- LEFT: 'left',
9
- RIGHT: 'right',
10
- CENTER: 'center'
11
- }
12
-
13
- /**
14
- * Menu vertical alignment options
15
- * @enum {string}
16
- */
17
- export const MENU_VERTICAL_ALIGN = {
18
- TOP: 'top',
19
- BOTTOM: 'bottom',
20
- MIDDLE: 'middle'
21
- }
22
-
23
- /**
24
- * Menu item types
25
- * @enum {string}
26
- */
27
- export const MENU_ITEM_TYPES = {
28
- ITEM: 'item',
29
- DIVIDER: 'divider'
30
- }
31
-
32
- /**
33
- * Menu events
34
- * @enum {string}
35
- */
36
- export const MENU_EVENTS = {
37
- SELECT: 'select',
38
- OPEN: 'open',
39
- CLOSE: 'close',
40
- SUBMENU_OPEN: 'submenuOpen',
41
- SUBMENU_CLOSE: 'submenuClose'
42
- }