mtrl 0.2.7 → 0.2.9

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 (190) hide show
  1. package/index.ts +2 -0
  2. package/package.json +14 -3
  3. package/src/components/badge/api.ts +23 -14
  4. package/src/components/badge/badge.ts +2 -2
  5. package/src/components/badge/config.ts +10 -11
  6. package/src/components/badge/features.ts +15 -10
  7. package/src/components/badge/index.ts +27 -2
  8. package/src/components/badge/types.ts +28 -8
  9. package/src/components/bottom-app-bar/bottom-app-bar.ts +2 -44
  10. package/src/components/bottom-app-bar/config.ts +1 -45
  11. package/src/components/bottom-app-bar/index.ts +7 -1
  12. package/src/components/bottom-app-bar/types.ts +7 -1
  13. package/src/components/button/button.ts +0 -1
  14. package/src/components/button/config.ts +1 -2
  15. package/src/components/button/index.ts +10 -2
  16. package/src/components/button/types.ts +14 -2
  17. package/src/components/card/config.ts +17 -9
  18. package/src/components/card/content.ts +8 -10
  19. package/src/components/card/features.ts +4 -6
  20. package/src/components/card/index.ts +29 -2
  21. package/src/components/card/types.ts +6 -23
  22. package/src/components/checkbox/config.ts +3 -4
  23. package/src/components/checkbox/index.ts +1 -2
  24. package/src/components/checkbox/types.ts +12 -3
  25. package/src/components/chip/api.ts +170 -221
  26. package/src/components/chip/chip.ts +34 -302
  27. package/src/components/chip/config.ts +1 -2
  28. package/src/components/chip/index.ts +10 -2
  29. package/src/components/chip/types.ts +224 -35
  30. package/src/components/datepicker/api.ts +18 -25
  31. package/src/components/datepicker/config.ts +9 -12
  32. package/src/components/datepicker/datepicker.ts +7 -12
  33. package/src/components/datepicker/index.ts +10 -7
  34. package/src/components/datepicker/render.ts +16 -18
  35. package/src/components/datepicker/types.ts +164 -35
  36. package/src/components/datepicker/utils.ts +1 -2
  37. package/src/components/dialog/api.ts +7 -8
  38. package/src/components/dialog/config.ts +3 -4
  39. package/src/components/dialog/features.ts +56 -22
  40. package/src/components/dialog/index.ts +38 -8
  41. package/src/components/dialog/types.ts +33 -10
  42. package/src/components/divider/index.ts +5 -1
  43. package/src/components/extended-fab/config.ts +6 -2
  44. package/src/components/extended-fab/index.ts +7 -2
  45. package/src/components/extended-fab/types.ts +21 -4
  46. package/src/components/fab/config.ts +3 -4
  47. package/src/components/fab/fab.ts +1 -1
  48. package/src/components/fab/index.ts +7 -2
  49. package/src/components/fab/types.ts +21 -4
  50. package/src/components/list/config.ts +4 -5
  51. package/src/components/list/features.ts +6 -7
  52. package/src/components/list/index.ts +7 -9
  53. package/src/components/list/list-item.ts +12 -13
  54. package/src/components/list/types.ts +50 -5
  55. package/src/components/list/utils.ts +30 -3
  56. package/src/components/menu/features/items-manager.ts +9 -9
  57. package/src/components/menu/features/positioning.ts +7 -7
  58. package/src/components/menu/features/visibility.ts +7 -7
  59. package/src/components/menu/index.ts +7 -9
  60. package/src/components/menu/menu-item.ts +6 -6
  61. package/src/components/menu/menu.ts +22 -0
  62. package/src/components/menu/types.ts +29 -10
  63. package/src/components/menu/utils.ts +67 -0
  64. package/src/components/navigation/api.ts +131 -96
  65. package/src/components/navigation/config.ts +22 -10
  66. package/src/components/navigation/features/controller.ts +273 -0
  67. package/src/components/navigation/features/items.ts +160 -87
  68. package/src/components/navigation/index.ts +0 -6
  69. package/src/components/navigation/nav-item.ts +12 -24
  70. package/src/components/navigation/navigation.ts +21 -8
  71. package/src/components/navigation/system-types.ts +124 -0
  72. package/src/components/navigation/system.ts +776 -0
  73. package/src/components/navigation/types.ts +228 -203
  74. package/src/components/progress/api.ts +2 -3
  75. package/src/components/progress/config.ts +2 -3
  76. package/src/components/progress/index.ts +0 -1
  77. package/src/components/progress/progress.ts +1 -2
  78. package/src/components/progress/types.ts +186 -33
  79. package/src/components/radios/config.ts +1 -1
  80. package/src/components/radios/index.ts +0 -1
  81. package/src/components/radios/types.ts +0 -7
  82. package/src/components/search/config.ts +1 -2
  83. package/src/components/search/features/search.ts +14 -15
  84. package/src/components/search/features/states.ts +5 -1
  85. package/src/components/search/features/structure.ts +3 -4
  86. package/src/components/search/index.ts +0 -3
  87. package/src/components/search/types.ts +18 -6
  88. package/src/components/segmented-button/config.ts +20 -7
  89. package/src/components/segmented-button/segment.ts +6 -7
  90. package/src/components/segmented-button/segmented-button.ts +4 -5
  91. package/src/components/segmented-button/types.ts +37 -2
  92. package/src/components/slider/config.ts +20 -2
  93. package/src/components/slider/features/controller.ts +761 -0
  94. package/src/components/slider/features/handlers.ts +18 -15
  95. package/src/components/slider/features/index.ts +3 -2
  96. package/src/components/slider/features/range.ts +104 -0
  97. package/src/components/slider/slider.ts +34 -14
  98. package/src/components/slider/structure.ts +152 -0
  99. package/src/components/slider/types.ts +34 -8
  100. package/src/components/snackbar/config.ts +2 -3
  101. package/src/components/snackbar/constants.ts +0 -32
  102. package/src/components/snackbar/index.ts +0 -1
  103. package/src/components/snackbar/position.ts +9 -1
  104. package/src/components/snackbar/types.ts +122 -46
  105. package/src/components/switch/config.ts +2 -3
  106. package/src/components/switch/index.ts +0 -1
  107. package/src/components/switch/types.ts +3 -2
  108. package/src/components/tabs/config.ts +3 -4
  109. package/src/components/tabs/index.ts +0 -15
  110. package/src/components/tabs/tab-api.ts +12 -4
  111. package/src/components/tabs/tab.ts +18 -6
  112. package/src/components/tabs/types.ts +13 -3
  113. package/src/components/textfield/api.ts +53 -0
  114. package/src/components/textfield/config.ts +2 -3
  115. package/src/components/textfield/features.ts +322 -0
  116. package/src/components/textfield/index.ts +0 -1
  117. package/src/components/textfield/textfield.ts +8 -0
  118. package/src/components/textfield/types.ts +29 -6
  119. package/src/components/timepicker/api.ts +1 -1
  120. package/src/components/timepicker/clockdial.ts +2 -5
  121. package/src/components/timepicker/config.ts +102 -4
  122. package/src/components/timepicker/index.ts +1 -6
  123. package/src/components/timepicker/render.ts +1 -1
  124. package/src/components/timepicker/timepicker.ts +1 -1
  125. package/src/components/tooltip/api.ts +1 -1
  126. package/src/components/tooltip/config.ts +27 -6
  127. package/src/components/tooltip/index.ts +0 -1
  128. package/src/components/tooltip/types.ts +13 -3
  129. package/src/core/compose/features/textinput.ts +15 -2
  130. package/src/core/compose/features/textlabel.ts +0 -3
  131. package/src/core/composition/features/dom.ts +33 -0
  132. package/src/core/composition/features/icon.ts +131 -0
  133. package/src/core/composition/features/index.ts +11 -0
  134. package/src/core/composition/features/label.ts +156 -0
  135. package/src/core/composition/features/structure.ts +22 -0
  136. package/src/core/composition/index.ts +26 -0
  137. package/src/core/index.ts +1 -1
  138. package/src/core/structure.ts +288 -0
  139. package/src/index.ts +1 -0
  140. package/src/styles/components/_navigation-mobile.scss +244 -0
  141. package/src/styles/components/_navigation-system.scss +151 -0
  142. package/src/{components/tabs/_styles.scss → styles/components/_tabs.scss} +1 -1
  143. package/src/{components/textfield/_styles.scss → styles/components/_textfield.scss} +314 -72
  144. package/src/styles/main.scss +98 -49
  145. package/src/components/badge/constants.ts +0 -40
  146. package/src/components/button/constants.ts +0 -11
  147. package/src/components/card/constants.ts +0 -84
  148. package/src/components/datepicker/constants.ts +0 -98
  149. package/src/components/dialog/constants.ts +0 -32
  150. package/src/components/extended-fab/constants.ts +0 -36
  151. package/src/components/fab/constants.ts +0 -41
  152. package/src/components/menu/constants.ts +0 -154
  153. package/src/components/navigation/constants.ts +0 -200
  154. package/src/components/progress/constants.ts +0 -29
  155. package/src/components/search/constants.ts +0 -21
  156. package/src/components/segmented-button/constants.ts +0 -42
  157. package/src/components/slider/features/slider.ts +0 -318
  158. package/src/components/slider/features/structure.ts +0 -181
  159. package/src/components/slider/features/ui.ts +0 -388
  160. package/src/components/switch/constants.ts +0 -80
  161. package/src/components/tabs/constants.ts +0 -89
  162. package/src/components/textfield/constants.ts +0 -100
  163. package/src/components/timepicker/constants.ts +0 -138
  164. /package/src/{components/badge/_styles.scss → styles/components/_badge.scss} +0 -0
  165. /package/src/{components/bottom-app-bar/_styles.scss → styles/components/_bottom-app-bar.scss} +0 -0
  166. /package/src/{components/button/_styles.scss → styles/components/_button.scss} +0 -0
  167. /package/src/{components/card/_styles.scss → styles/components/_card.scss} +0 -0
  168. /package/src/{components/carousel/_styles.scss → styles/components/_carousel.scss} +0 -0
  169. /package/src/{components/checkbox/_styles.scss → styles/components/_checkbox.scss} +0 -0
  170. /package/src/{components/chip/_styles.scss → styles/components/_chip.scss} +0 -0
  171. /package/src/{components/datepicker/_styles.scss → styles/components/_datepicker.scss} +0 -0
  172. /package/src/{components/dialog/_styles.scss → styles/components/_dialog.scss} +0 -0
  173. /package/src/{components/divider/_styles.scss → styles/components/_divider.scss} +0 -0
  174. /package/src/{components/extended-fab/_styles.scss → styles/components/_extended-fab.scss} +0 -0
  175. /package/src/{components/fab/_styles.scss → styles/components/_fab.scss} +0 -0
  176. /package/src/{components/list/_styles.scss → styles/components/_list.scss} +0 -0
  177. /package/src/{components/menu/_styles.scss → styles/components/_menu.scss} +0 -0
  178. /package/src/{components/navigation/_styles.scss → styles/components/_navigation.scss} +0 -0
  179. /package/src/{components/progress/_styles.scss → styles/components/_progress.scss} +0 -0
  180. /package/src/{components/radios/_styles.scss → styles/components/_radios.scss} +0 -0
  181. /package/src/{components/search/_styles.scss → styles/components/_search.scss} +0 -0
  182. /package/src/{components/segmented-button/_styles.scss → styles/components/_segmented-button.scss} +0 -0
  183. /package/src/{components/sheet/_styles.scss → styles/components/_sheet.scss} +0 -0
  184. /package/src/{components/slider/_styles.scss → styles/components/_slider.scss} +0 -0
  185. /package/src/{components/snackbar/_styles.scss → styles/components/_snackbar.scss} +0 -0
  186. /package/src/{components/switch/_styles.scss → styles/components/_switch.scss} +0 -0
  187. /package/src/{components/timepicker/_styles.scss → styles/components/_timepicker.scss} +0 -0
  188. /package/src/{components/tooltip/_styles.scss → styles/components/_tooltip.scss} +0 -0
  189. /package/src/{components/top-app-bar/_styles.scss → styles/components/_top-app-bar.scss} +0 -0
  190. /package/src/styles/utilities/{_color.scss → _colors.scss} +0 -0
@@ -1,6 +1,6 @@
1
1
  // src/components/menu/features/items-manager.ts
2
2
  import { createMenuItem } from '../menu-item';
3
- import { MENU_EVENTS } from '../constants';
3
+ import { MENU_EVENT } from '../utils';
4
4
  import { BaseComponent, MenuConfig, MenuItemConfig, MenuItemData } from '../types';
5
5
 
6
6
  interface SubmenuMap {
@@ -63,8 +63,8 @@ export const withItemsManager = (config: MenuConfig) => (component: BaseComponen
63
63
  });
64
64
 
65
65
  // Handle submenu selection
66
- submenu.on?.(MENU_EVENTS.SELECT, (detail: any) => {
67
- component.emit?.(MENU_EVENTS.SELECT, {
66
+ submenu.on?.(MENU_EVENT.SELECT, (detail: any) => {
67
+ component.emit?.(MENU_EVENT.SELECT, {
68
68
  name: `${name}:${detail.name}`,
69
69
  text: detail.text,
70
70
  path: [name, detail.name]
@@ -116,7 +116,7 @@ export const withItemsManager = (config: MenuConfig) => (component: BaseComponen
116
116
  });
117
117
 
118
118
  // Emit submenu open event
119
- component.emit?.(MENU_EVENTS.SUBMENU_OPEN, { name });
119
+ component.emit?.(MENU_EVENT.SUBMENU_OPEN, { name });
120
120
  }
121
121
  };
122
122
 
@@ -143,7 +143,7 @@ export const withItemsManager = (config: MenuConfig) => (component: BaseComponen
143
143
  activeSubmenu = null;
144
144
 
145
145
  // Emit submenu close event
146
- component.emit?.(MENU_EVENTS.SUBMENU_CLOSE, { name });
146
+ component.emit?.(MENU_EVENT.SUBMENU_CLOSE, { name });
147
147
  };
148
148
 
149
149
  /**
@@ -160,7 +160,7 @@ export const withItemsManager = (config: MenuConfig) => (component: BaseComponen
160
160
 
161
161
  // Cancel any pending close timer for this item
162
162
  if (closeTimers.has(name)) {
163
- window.clearTimeout(closeTimers.get(name));
163
+ window.clearTimeout(closeTimers.get(name)!);
164
164
  closeTimers.delete(name);
165
165
  }
166
166
 
@@ -194,7 +194,7 @@ export const withItemsManager = (config: MenuConfig) => (component: BaseComponen
194
194
  if (submenu && submenu.element) {
195
195
  // Cancel any existing close timer for this item
196
196
  if (closeTimers.has(name)) {
197
- window.clearTimeout(closeTimers.get(name));
197
+ window.clearTimeout(closeTimers.get(name)!);
198
198
  }
199
199
 
200
200
  // Set a new close timer
@@ -254,7 +254,7 @@ export const withItemsManager = (config: MenuConfig) => (component: BaseComponen
254
254
  // For regular items, emit select event
255
255
  const name = item.getAttribute('data-name');
256
256
  if (name) {
257
- component.emit?.(MENU_EVENTS.SELECT, { name, text: item.textContent });
257
+ component.emit?.(MENU_EVENT.SELECT, { name, text: item.textContent });
258
258
  // Hide menu after selection unless configured otherwise
259
259
  if (!config.stayOpenOnSelect) {
260
260
  component.hide?.();
@@ -430,7 +430,7 @@ export const withItemsManager = (config: MenuConfig) => (component: BaseComponen
430
430
 
431
431
  /**
432
432
  * Gets all registered items
433
- * @returns {Map<string, MenuItemConfig>} Map of item names to configurations
433
+ * @returns {Map<string, MenuItemData>} Map of item names to data
434
434
  */
435
435
  getItems(): Map<string, MenuItemData> {
436
436
  const result = new Map<string, MenuItemData>();
@@ -1,6 +1,6 @@
1
1
  // src/components/menu/features/positioning.ts
2
2
  import { BaseComponent, MenuPositionConfig, MenuPosition } from '../types';
3
- import { MENU_ALIGN, MENU_VERTICAL_ALIGN } from '../constants';
3
+ import { MENU_ALIGNMENT, MENU_VERTICAL_ALIGNMENT } from '../utils';
4
4
 
5
5
  /**
6
6
  * Positions a menu element relative to a target element
@@ -34,8 +34,8 @@ export const positionMenu = (
34
34
  menuElement.style.opacity = originalOpacity;
35
35
 
36
36
  const {
37
- align = MENU_ALIGN.LEFT,
38
- vAlign = MENU_VERTICAL_ALIGN.BOTTOM,
37
+ align = MENU_ALIGNMENT.LEFT,
38
+ vAlign = MENU_VERTICAL_ALIGNMENT.BOTTOM,
39
39
  offsetX = 0,
40
40
  offsetY = 0
41
41
  } = options;
@@ -44,16 +44,16 @@ export const positionMenu = (
44
44
  let top = targetRect.bottom + offsetY;
45
45
 
46
46
  // Handle horizontal alignment
47
- if (align === MENU_ALIGN.RIGHT) {
47
+ if (align === MENU_ALIGNMENT.RIGHT) {
48
48
  left = targetRect.right - menuRect.width + offsetX;
49
- } else if (align === MENU_ALIGN.CENTER) {
49
+ } else if (align === MENU_ALIGNMENT.CENTER) {
50
50
  left = targetRect.left + (targetRect.width - menuRect.width) / 2 + offsetX;
51
51
  }
52
52
 
53
53
  // Handle vertical alignment
54
- if (vAlign === MENU_VERTICAL_ALIGN.TOP) {
54
+ if (vAlign === MENU_VERTICAL_ALIGNMENT.TOP) {
55
55
  top = targetRect.top - menuRect.height + offsetY;
56
- } else if (vAlign === MENU_VERTICAL_ALIGN.MIDDLE) {
56
+ } else if (vAlign === MENU_VERTICAL_ALIGNMENT.MIDDLE) {
57
57
  top = targetRect.top + (targetRect.height - menuRect.height) / 2 + offsetY;
58
58
  }
59
59
 
@@ -1,6 +1,6 @@
1
1
  // src/components/menu/features/visibility.ts
2
2
  import { BaseComponent, MenuConfig } from '../types';
3
- import { MENU_EVENTS } from '../constants';
3
+ import { MENU_EVENT, MENU_CLASSES } from '../utils';
4
4
 
5
5
  /**
6
6
  * Adds visibility management functionality to a menu component
@@ -56,11 +56,11 @@ export const withVisibility = (config: MenuConfig) => (component: BaseComponent)
56
56
  // Force a reflow before adding the visible class for animation
57
57
  // eslint-disable-next-line no-void
58
58
  void component.element.offsetHeight;
59
- component.element.classList.add(`${prefix}-menu--visible`);
59
+ component.element.classList.add(`${prefix}-${MENU_CLASSES.VISIBLE}`);
60
60
  component.element.setAttribute('aria-hidden', 'false');
61
61
 
62
62
  // Emit open event
63
- component.emit?.(MENU_EVENTS.OPEN, {});
63
+ component.emit?.(MENU_EVENT.OPEN, {});
64
64
 
65
65
  return this;
66
66
  },
@@ -92,7 +92,7 @@ export const withVisibility = (config: MenuConfig) => (component: BaseComponent)
92
92
  }
93
93
 
94
94
  // Hide the menu with visual indication first
95
- component.element.classList.remove(`${prefix}-menu--visible`);
95
+ component.element.classList.remove(`${prefix}-${MENU_CLASSES.VISIBLE}`);
96
96
  component.element.setAttribute('aria-hidden', 'true');
97
97
 
98
98
  // Define a reliable cleanup function
@@ -123,7 +123,7 @@ export const withVisibility = (config: MenuConfig) => (component: BaseComponent)
123
123
  setTimeout(cleanupElement, 300);
124
124
 
125
125
  // Emit close event
126
- component.emit?.(MENU_EVENTS.CLOSE, {});
126
+ component.emit?.(MENU_EVENT.CLOSE, {});
127
127
 
128
128
  return this;
129
129
  },
@@ -145,7 +145,7 @@ export const withVisibility = (config: MenuConfig) => (component: BaseComponent)
145
145
  if (!isVisible) return;
146
146
 
147
147
  // Store the opening button if available
148
- const openingButton = config.openingButton?.element;
148
+ const origin = config.origin?.element;
149
149
 
150
150
  // Check if click is outside the menu but not on the opening button
151
151
  const clickedElement = event.target as Node;
@@ -156,7 +156,7 @@ export const withVisibility = (config: MenuConfig) => (component: BaseComponent)
156
156
  }
157
157
 
158
158
  // Don't close if the click is on the opening button (it will handle opening/closing)
159
- if (openingButton && (openingButton === clickedElement || openingButton.contains(clickedElement))) {
159
+ if (origin && (origin === clickedElement || origin.contains(clickedElement))) {
160
160
  return;
161
161
  }
162
162
 
@@ -1,14 +1,12 @@
1
1
  // src/components/menu/index.ts
2
- export { default } from './menu'
3
- export {
4
- MENU_ALIGN,
5
- MENU_VERTICAL_ALIGN,
6
- MENU_ITEM_TYPES,
7
- MENU_EVENTS
8
- } from './constants'
2
+ export { default } from './menu';
9
3
  export {
10
4
  MenuConfig,
11
5
  MenuComponent,
12
6
  MenuItemConfig,
13
- MenuPositionConfig
14
- } from './types'
7
+ MenuPositionConfig,
8
+ MenuAlign,
9
+ MenuVerticalAlign,
10
+ MenuItemType,
11
+ MenuEvent
12
+ } from './types';
@@ -1,6 +1,6 @@
1
1
  // src/components/menu/menu-item.ts
2
2
  import { MenuItemConfig } from './types';
3
- import { MENU_ITEM_TYPES } from './constants';
3
+ import { MENU_ITEM_TYPE, getMenuClass } from './utils';
4
4
 
5
5
  /**
6
6
  * Creates a menu item element
@@ -10,10 +10,10 @@ import { MENU_ITEM_TYPES } from './constants';
10
10
  */
11
11
  export const createMenuItem = (itemConfig: MenuItemConfig, prefix: string): HTMLElement => {
12
12
  const item = document.createElement('li');
13
- item.className = `${prefix}-menu-item`;
13
+ item.className = `${prefix}-${getMenuClass('ITEM')}`;
14
14
 
15
- if (itemConfig.type === MENU_ITEM_TYPES.DIVIDER) {
16
- item.className = `${prefix}-menu-divider`;
15
+ if (itemConfig.type === MENU_ITEM_TYPE.DIVIDER) {
16
+ item.className = `${prefix}-${getMenuClass('DIVIDER')}`;
17
17
  return item;
18
18
  }
19
19
 
@@ -23,7 +23,7 @@ export const createMenuItem = (itemConfig: MenuItemConfig, prefix: string): HTML
23
23
 
24
24
  if (itemConfig.disabled) {
25
25
  item.setAttribute('aria-disabled', 'true');
26
- item.className += ` ${prefix}-menu-item--disabled`;
26
+ item.className += ` ${prefix}-${getMenuClass('ITEM')}--disabled`;
27
27
  }
28
28
 
29
29
  if (itemConfig.name) {
@@ -33,7 +33,7 @@ export const createMenuItem = (itemConfig: MenuItemConfig, prefix: string): HTML
33
33
  item.textContent = itemConfig.text || '';
34
34
 
35
35
  if (itemConfig.items?.length) {
36
- item.className += ` ${prefix}-menu-item--submenu`;
36
+ item.className += ` ${prefix}-${getMenuClass('ITEM')}--submenu`;
37
37
  item.setAttribute('aria-haspopup', 'true');
38
38
  item.setAttribute('aria-expanded', 'false');
39
39
  // We don't need to add a submenu indicator as it's handled by CSS ::after
@@ -18,6 +18,28 @@ import {
18
18
  * Creates a new Menu component
19
19
  * @param {MenuConfig} config - Menu configuration
20
20
  * @returns {MenuComponent} Menu component instance
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * // Create a basic menu with items
25
+ * const menu = createMenu({
26
+ * items: [
27
+ * { name: 'item1', text: 'Option 1' },
28
+ * { name: 'item2', text: 'Option 2' },
29
+ * { type: 'divider' },
30
+ * { name: 'item3', text: 'Option 3' }
31
+ * ]
32
+ * });
33
+ *
34
+ * // Show the menu positioned relative to a button
35
+ * const button = document.getElementById('menuButton');
36
+ * menu.position(button).show();
37
+ *
38
+ * // Listen for item selection
39
+ * menu.on('select', (event) => {
40
+ * console.log(`Selected: ${event.name}`);
41
+ * });
42
+ * ```
21
43
  */
22
44
  const createMenu = (config: MenuConfig = {}): MenuComponent => {
23
45
  const baseConfig = createBaseConfig(config);
@@ -1,9 +1,28 @@
1
1
  // src/components/menu/types.ts
2
- import {
3
- MENU_ALIGN,
4
- MENU_VERTICAL_ALIGN,
5
- MENU_ITEM_TYPES
6
- } from './constants';
2
+
3
+ /**
4
+ * Menu alignment options
5
+ * @category Components
6
+ */
7
+ export type MenuAlign = 'left' | 'right' | 'center';
8
+
9
+ /**
10
+ * Menu vertical alignment options
11
+ * @category Components
12
+ */
13
+ export type MenuVerticalAlign = 'top' | 'bottom' | 'middle';
14
+
15
+ /**
16
+ * Menu item types
17
+ * @category Components
18
+ */
19
+ export type MenuItemType = 'item' | 'divider';
20
+
21
+ /**
22
+ * Menu events
23
+ * @category Components
24
+ */
25
+ export type MenuEvent = 'select' | 'open' | 'close' | 'submenuOpen' | 'submenuClose';
7
26
 
8
27
  /**
9
28
  * Menu item configuration
@@ -16,7 +35,7 @@ export interface MenuItemConfig {
16
35
  text: string;
17
36
 
18
37
  /** Type of menu item */
19
- type?: keyof typeof MENU_ITEM_TYPES | string;
38
+ type?: MenuItemType | string;
20
39
 
21
40
  /** Whether the item is disabled */
22
41
  disabled?: boolean;
@@ -33,10 +52,10 @@ export interface MenuItemConfig {
33
52
  */
34
53
  export interface MenuPositionConfig {
35
54
  /** Horizontal alignment */
36
- align?: keyof typeof MENU_ALIGN | string;
55
+ align?: MenuAlign | string;
37
56
 
38
57
  /** Vertical alignment */
39
- vAlign?: keyof typeof MENU_VERTICAL_ALIGN | string;
58
+ vAlign?: MenuVerticalAlign | string;
40
59
 
41
60
  /** Horizontal offset in pixels */
42
61
  offsetX?: number;
@@ -94,8 +113,8 @@ export interface MenuConfig {
94
113
  /** Whether to keep menu open after selection */
95
114
  stayOpenOnSelect?: boolean;
96
115
 
97
- /** Button element that opens the menu */
98
- openingButton?: HTMLElement | { element: HTMLElement };
116
+ /** Orgin element that opens the menu */
117
+ origin?: HTMLElement | { element: HTMLElement };
99
118
 
100
119
  /** Parent item element (for submenus) */
101
120
  parentItem?: HTMLElement;
@@ -0,0 +1,67 @@
1
+ // src/components/menu/utils.ts
2
+
3
+ /**
4
+ * Menu alignment constants for internal use
5
+ * @internal
6
+ */
7
+ export const MENU_ALIGNMENT = {
8
+ LEFT: 'left',
9
+ RIGHT: 'right',
10
+ CENTER: 'center'
11
+ };
12
+
13
+ /**
14
+ * Menu vertical alignment constants for internal use
15
+ * @internal
16
+ */
17
+ export const MENU_VERTICAL_ALIGNMENT = {
18
+ TOP: 'top',
19
+ BOTTOM: 'bottom',
20
+ MIDDLE: 'middle'
21
+ };
22
+
23
+ /**
24
+ * Menu item types for internal use
25
+ * @internal
26
+ */
27
+ export const MENU_ITEM_TYPE = {
28
+ ITEM: 'item',
29
+ DIVIDER: 'divider'
30
+ };
31
+
32
+ /**
33
+ * Menu events for internal use
34
+ * @internal
35
+ */
36
+ export const MENU_EVENT = {
37
+ SELECT: 'select',
38
+ OPEN: 'open',
39
+ CLOSE: 'close',
40
+ SUBMENU_OPEN: 'submenuOpen',
41
+ SUBMENU_CLOSE: 'submenuClose'
42
+ };
43
+
44
+ /**
45
+ * Menu CSS classes for internal use
46
+ * @internal
47
+ */
48
+ export const MENU_CLASSES = {
49
+ ROOT: 'menu',
50
+ ITEM: 'menu-item',
51
+ ITEM_CONTAINER: 'menu-item-container',
52
+ LIST: 'menu-list',
53
+ DIVIDER: 'menu-divider',
54
+ SUBMENU: 'menu--submenu',
55
+ VISIBLE: 'menu--visible',
56
+ DISABLED: 'menu--disabled'
57
+ };
58
+
59
+ /**
60
+ * Gets a class name for menu elements
61
+ * @param {string} element - Element name from MENU_CLASSES
62
+ * @returns {string} The class name
63
+ * @internal
64
+ */
65
+ export const getMenuClass = (element: keyof typeof MENU_CLASSES): string => {
66
+ return MENU_CLASSES[element];
67
+ };
@@ -1,107 +1,142 @@
1
- // src/components/button/api.ts
2
- import { ButtonComponent } from './types';
3
-
4
- interface ApiOptions {
5
- disabled: {
6
- enable: () => void;
7
- disable: () => void;
8
- };
9
- lifecycle: {
10
- destroy: () => void;
11
- };
12
- }
13
-
14
- interface ComponentWithElements {
15
- element: HTMLElement;
16
- text: {
17
- setText: (content: string) => any;
18
- getText: () => string;
19
- getElement: () => HTMLElement | null;
20
- };
21
- icon: {
22
- setIcon: (html: string) => any;
23
- getIcon: () => string;
24
- getElement: () => HTMLElement | null;
25
- };
26
- getClass: (name: string) => string;
27
- }
1
+ // src/components/navigation/api.ts
2
+ import { NavigationComponent, NavItemConfig, NavItemData, BaseComponent, ApiOptions } from './types';
28
3
 
29
4
  /**
30
- * Enhances a button component with API methods
5
+ * Enhances a component with navigation-specific API methods
31
6
  * @param {ApiOptions} options - API configuration options
32
7
  * @returns {Function} Higher-order function that adds API methods to component
33
- * @internal This is an internal utility for the Button component
34
8
  */
35
- export const withAPI = ({ disabled, lifecycle }: ApiOptions) =>
36
- (component: ComponentWithElements): ButtonComponent => ({
37
- ...component as any,
38
- element: component.element as HTMLButtonElement,
9
+ export const withAPI = (options: ApiOptions) =>
10
+ (component: BaseComponent): NavigationComponent => {
39
11
 
40
- getValue: () => component.element.value,
41
-
42
- setValue(value: string) {
43
- component.element.value = value;
44
- return this;
45
- },
46
-
47
- enable() {
48
- disabled.enable();
49
- return this;
50
- },
51
-
52
- disable() {
53
- disabled.disable();
54
- return this;
55
- },
56
-
57
- setText(content: string) {
58
- component.text.setText(content);
59
- this.updateCircularStyle();
12
+ const navComponent = {
13
+ ...component,
14
+ element: component.element,
15
+ items: component.items || new Map(),
16
+
17
+ // Basic item operations
18
+ addItem(config: NavItemConfig): NavigationComponent {
19
+ if (typeof component.addItem === 'function') {
20
+ component.addItem(config);
21
+ }
22
+ return this;
23
+ },
24
+
25
+ removeItem(id: string): NavigationComponent {
26
+ if (typeof component.removeItem === 'function') {
27
+ component.removeItem(id);
28
+ }
29
+ return this;
30
+ },
31
+
32
+ getItem(id: string): NavItemData | undefined {
33
+ if (typeof component.getItem === 'function') {
34
+ return component.getItem(id);
35
+ }
36
+ return this.items.get(id);
37
+ },
38
+
39
+ getAllItems(): NavItemData[] {
40
+ if (typeof component.getAllItems === 'function') {
41
+ return component.getAllItems();
42
+ }
43
+ return Array.from(this.items.values());
44
+ },
45
+
46
+ // Path and active item management
47
+ getActive(): NavItemData | null {
48
+ if (typeof component.getActive === 'function') {
49
+ return component.getActive();
50
+ }
51
+ return null;
52
+ },
53
+
54
+ getItemPath(id: string): string[] {
55
+ if (typeof component.getItemPath === 'function') {
56
+ return component.getItemPath(id);
57
+ }
58
+ return [];
59
+ },
60
60
 
61
- // If removing text from a button with an icon, ensure it has an accessible name
62
- if (!content && component.icon.getElement()) {
63
- if (!this.element.getAttribute('aria-label')) {
64
- const className = this.element.className.split(' ')
65
- .find(cls => !cls.startsWith(`${component.getClass('button')}`));
66
-
67
- if (className) {
68
- this.element.setAttribute('aria-label', className);
61
+ setActive(id: string, silent): NavigationComponent {
62
+ // Use the controller if available for consistent handling
63
+ if (typeof component.handleItemClick === 'function') {
64
+ component.handleItemClick(id);
65
+ } else if (typeof component.setActive === 'function') {
66
+ component.setActive(id);
67
+ } else {
68
+ // Fallback if setActive is not available
69
+ const item = this.items.get(id);
70
+ if (item && item.element) {
71
+ // Emit a change event to propagate the state change
72
+ if (component.emit) {
73
+ component.emit('change', {
74
+ id,
75
+ item,
76
+ source: 'api'
77
+ });
78
+ }
69
79
  }
70
80
  }
71
- }
81
+ return this;
82
+ },
72
83
 
73
- return this;
74
- },
75
-
76
- getText() {
77
- return component.text.getText();
78
- },
79
-
80
- setIcon(icon: string) {
81
- component.icon.setIcon(icon);
82
- this.updateCircularStyle();
83
- return this;
84
- },
85
-
86
- getIcon() {
87
- return component.icon.getIcon();
88
- },
89
-
90
- setAriaLabel(label: string) {
91
- component.element.setAttribute('aria-label', label);
92
- return this;
93
- },
94
-
95
- destroy() {
96
- lifecycle.destroy();
97
- },
98
-
99
- updateCircularStyle() {
100
- const hasText = component.text.getText();
101
- if (!hasText && component.icon.getElement()) {
102
- component.element.classList.add(`${component.getClass('button')}--circular`);
103
- } else {
104
- component.element.classList.remove(`${component.getClass('button')}--circular`);
84
+ // Navigation state management
85
+ enable(): NavigationComponent {
86
+ if (options.disabled.enable) {
87
+ options.disabled.enable();
88
+ }
89
+ return this;
90
+ },
91
+
92
+ disable(): NavigationComponent {
93
+ if (options.disabled.disable) {
94
+ options.disabled.disable();
95
+ }
96
+ return this;
97
+ },
98
+
99
+ expand(): NavigationComponent {
100
+ this.element.classList.remove(`${this.element.className.split(' ')[0]}--hidden`);
101
+ this.element.setAttribute('aria-hidden', 'false');
102
+
103
+ if (component.emit) {
104
+ component.emit('expanded', { source: 'api' });
105
+ }
106
+ return this;
107
+ },
108
+
109
+ collapse(): NavigationComponent {
110
+ this.element.classList.add(`${this.element.className.split(' ')[0]}--hidden`);
111
+ this.element.setAttribute('aria-hidden', 'true');
112
+
113
+ if (component.emit) {
114
+ component.emit('collapsed', { source: 'api' });
115
+ }
116
+ return this;
117
+ },
118
+
119
+ isExpanded(): boolean {
120
+ return !this.element.classList.contains(`${this.element.className.split(' ')[0]}--hidden`);
121
+ },
122
+
123
+ toggle(): NavigationComponent {
124
+ return this.isExpanded() ? this.collapse() : this.expand();
125
+ },
126
+ // on: component.on,
127
+ // off: component.off,
128
+ // emit: component.emit,
129
+
130
+ // Destruction
131
+ destroy(): void {
132
+ if (options.lifecycle.destroy) {
133
+ options.lifecycle.destroy();
134
+ }
105
135
  }
106
- }
107
- });
136
+ };
137
+
138
+ // Return the enhanced component
139
+ return navComponent;
140
+ };
141
+
142
+ export default withAPI;