aeico-components 0.1.4 → 0.1.6

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 (299) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +0 -0
  3. package/dist/chunks/action-button.cjs +296 -0
  4. package/dist/chunks/action-button.cjs.map +1 -0
  5. package/dist/chunks/action-button.js +297 -0
  6. package/dist/chunks/action-button.js.map +1 -0
  7. package/dist/chunks/alert.cjs +4 -4
  8. package/dist/chunks/alert.cjs.map +1 -1
  9. package/dist/chunks/alert.js +5 -5
  10. package/dist/chunks/alert.js.map +1 -1
  11. package/dist/chunks/badge.cjs +1 -1
  12. package/dist/chunks/badge.cjs.map +1 -1
  13. package/dist/chunks/badge.js +2 -2
  14. package/dist/chunks/badge.js.map +1 -1
  15. package/dist/chunks/breadcrumb-item.cjs +2 -2
  16. package/dist/chunks/breadcrumb-item.cjs.map +1 -1
  17. package/dist/chunks/breadcrumb-item.js +3 -3
  18. package/dist/chunks/breadcrumb-item.js.map +1 -1
  19. package/dist/chunks/button-group.cjs +1 -1
  20. package/dist/chunks/button-group.cjs.map +1 -1
  21. package/dist/chunks/button-group.js +2 -2
  22. package/dist/chunks/button-group.js.map +1 -1
  23. package/dist/chunks/button.cjs +12 -15
  24. package/dist/chunks/button.cjs.map +1 -1
  25. package/dist/chunks/button.js +13 -16
  26. package/dist/chunks/button.js.map +1 -1
  27. package/dist/chunks/card.cjs +1 -1
  28. package/dist/chunks/card.cjs.map +1 -1
  29. package/dist/chunks/card.js +2 -2
  30. package/dist/chunks/card.js.map +1 -1
  31. package/dist/chunks/checkbox.cjs +18 -5
  32. package/dist/chunks/checkbox.cjs.map +1 -1
  33. package/dist/chunks/checkbox.js +18 -5
  34. package/dist/chunks/checkbox.js.map +1 -1
  35. package/dist/chunks/copy-button.cjs +168 -0
  36. package/dist/chunks/copy-button.cjs.map +1 -0
  37. package/dist/chunks/copy-button.js +169 -0
  38. package/dist/chunks/copy-button.js.map +1 -0
  39. package/dist/chunks/detail.cjs +7 -4
  40. package/dist/chunks/detail.cjs.map +1 -1
  41. package/dist/chunks/detail.js +8 -5
  42. package/dist/chunks/detail.js.map +1 -1
  43. package/dist/chunks/dialog.cjs +1 -1
  44. package/dist/chunks/dialog.cjs.map +1 -1
  45. package/dist/chunks/dialog.js +2 -2
  46. package/dist/chunks/dialog.js.map +1 -1
  47. package/dist/chunks/divider.cjs +1 -1
  48. package/dist/chunks/divider.cjs.map +1 -1
  49. package/dist/chunks/divider.js +2 -2
  50. package/dist/chunks/divider.js.map +1 -1
  51. package/dist/chunks/drawer.cjs +180 -0
  52. package/dist/chunks/drawer.cjs.map +1 -0
  53. package/dist/chunks/drawer.js +181 -0
  54. package/dist/chunks/drawer.js.map +1 -0
  55. package/dist/chunks/dropdown-button.cjs +2 -2
  56. package/dist/chunks/dropdown-button.cjs.map +1 -1
  57. package/dist/chunks/dropdown-button.js +6 -6
  58. package/dist/chunks/dropdown-button.js.map +1 -1
  59. package/dist/chunks/icon.cjs +31 -1
  60. package/dist/chunks/icon.cjs.map +1 -1
  61. package/dist/chunks/icon.js +32 -2
  62. package/dist/chunks/icon.js.map +1 -1
  63. package/dist/chunks/menu.cjs +396 -0
  64. package/dist/chunks/menu.cjs.map +1 -0
  65. package/dist/chunks/menu.js +397 -0
  66. package/dist/chunks/menu.js.map +1 -0
  67. package/dist/chunks/navbar.cjs +2 -3
  68. package/dist/chunks/navbar.cjs.map +1 -1
  69. package/dist/chunks/navbar.js +3 -4
  70. package/dist/chunks/navbar.js.map +1 -1
  71. package/dist/chunks/pagination.cjs +475 -0
  72. package/dist/chunks/pagination.cjs.map +1 -0
  73. package/dist/chunks/pagination.js +476 -0
  74. package/dist/chunks/pagination.js.map +1 -0
  75. package/dist/chunks/progress-bar.cjs +101 -0
  76. package/dist/chunks/progress-bar.cjs.map +1 -0
  77. package/dist/chunks/progress-bar.js +102 -0
  78. package/dist/chunks/progress-bar.js.map +1 -0
  79. package/dist/chunks/radio.cjs +11 -7
  80. package/dist/chunks/radio.cjs.map +1 -1
  81. package/dist/chunks/radio.js +11 -7
  82. package/dist/chunks/radio.js.map +1 -1
  83. package/dist/chunks/select.cjs +97 -66
  84. package/dist/chunks/select.cjs.map +1 -1
  85. package/dist/chunks/select.js +97 -66
  86. package/dist/chunks/select.js.map +1 -1
  87. package/dist/chunks/slider.cjs +9 -46
  88. package/dist/chunks/slider.cjs.map +1 -1
  89. package/dist/chunks/slider.js +9 -46
  90. package/dist/chunks/slider.js.map +1 -1
  91. package/dist/chunks/spinner.cjs +102 -0
  92. package/dist/chunks/spinner.cjs.map +1 -0
  93. package/dist/chunks/spinner.js +103 -0
  94. package/dist/chunks/spinner.js.map +1 -0
  95. package/dist/chunks/switch.cjs +110 -16
  96. package/dist/chunks/switch.cjs.map +1 -1
  97. package/dist/chunks/switch.js +111 -17
  98. package/dist/chunks/switch.js.map +1 -1
  99. package/dist/chunks/tab-panel.cjs +6 -7
  100. package/dist/chunks/tab-panel.cjs.map +1 -1
  101. package/dist/chunks/tab-panel.js +7 -8
  102. package/dist/chunks/tab-panel.js.map +1 -1
  103. package/dist/chunks/tag.cjs +1 -1
  104. package/dist/chunks/tag.cjs.map +1 -1
  105. package/dist/chunks/tag.js +2 -2
  106. package/dist/chunks/tag.js.map +1 -1
  107. package/dist/chunks/text-input.cjs +11 -16
  108. package/dist/chunks/text-input.cjs.map +1 -1
  109. package/dist/chunks/text-input.js +11 -16
  110. package/dist/chunks/text-input.js.map +1 -1
  111. package/dist/chunks/textarea.cjs +137 -0
  112. package/dist/chunks/textarea.cjs.map +1 -0
  113. package/dist/chunks/textarea.js +138 -0
  114. package/dist/chunks/textarea.js.map +1 -0
  115. package/dist/chunks/tooltip.cjs +293 -0
  116. package/dist/chunks/tooltip.cjs.map +1 -0
  117. package/dist/chunks/tooltip.js +294 -0
  118. package/dist/chunks/tooltip.js.map +1 -0
  119. package/dist/chunks/tree.cjs +468 -0
  120. package/dist/chunks/tree.cjs.map +1 -0
  121. package/dist/chunks/tree.js +469 -0
  122. package/dist/chunks/tree.js.map +1 -0
  123. package/dist/chunks/variables.cjs +2 -2
  124. package/dist/chunks/variables.js +2 -2
  125. package/dist/copy-button.cjs +6 -0
  126. package/dist/copy-button.cjs.map +1 -0
  127. package/dist/copy-button.js +6 -0
  128. package/dist/copy-button.js.map +1 -0
  129. package/dist/drawer.cjs +6 -0
  130. package/dist/drawer.cjs.map +1 -0
  131. package/dist/drawer.js +6 -0
  132. package/dist/drawer.js.map +1 -0
  133. package/dist/dropdown.js +4 -4
  134. package/dist/index.cjs +186 -0
  135. package/dist/index.cjs.map +1 -1
  136. package/dist/index.js +201 -15
  137. package/dist/index.js.map +1 -1
  138. package/dist/menu.cjs +6 -0
  139. package/dist/menu.cjs.map +1 -0
  140. package/dist/menu.js +6 -0
  141. package/dist/menu.js.map +1 -0
  142. package/dist/pagination.cjs +6 -0
  143. package/dist/pagination.cjs.map +1 -0
  144. package/dist/pagination.js +6 -0
  145. package/dist/pagination.js.map +1 -0
  146. package/dist/progress-bar.cjs +6 -0
  147. package/dist/progress-bar.cjs.map +1 -0
  148. package/dist/progress-bar.js +6 -0
  149. package/dist/progress-bar.js.map +1 -0
  150. package/dist/select.cjs +1 -1
  151. package/dist/select.cjs.map +1 -1
  152. package/dist/select.js +2 -2
  153. package/dist/select.js.map +1 -1
  154. package/dist/spinner.cjs +6 -0
  155. package/dist/spinner.cjs.map +1 -0
  156. package/dist/spinner.js +6 -0
  157. package/dist/spinner.js.map +1 -0
  158. package/dist/textarea.cjs +5 -0
  159. package/dist/textarea.cjs.map +1 -0
  160. package/dist/textarea.js +5 -0
  161. package/dist/textarea.js.map +1 -0
  162. package/dist/tooltip.cjs +6 -0
  163. package/dist/tooltip.cjs.map +1 -0
  164. package/dist/tooltip.js +6 -0
  165. package/dist/tooltip.js.map +1 -0
  166. package/dist/tree.cjs +6 -0
  167. package/dist/tree.cjs.map +1 -0
  168. package/dist/tree.js +6 -0
  169. package/dist/tree.js.map +1 -0
  170. package/dist/types/aeico-field.d.ts +57 -5
  171. package/dist/types/alert/alert.d.ts +1 -0
  172. package/dist/types/button/button.d.ts +2 -1
  173. package/dist/types/checkbox/checkbox.d.ts +5 -5
  174. package/dist/types/copy-button/copy-button.d.ts +32 -0
  175. package/dist/types/copy-button/defines.d.ts +1 -0
  176. package/dist/types/copy-button/index.d.ts +3 -0
  177. package/dist/types/detail/defines.d.ts +1 -0
  178. package/dist/types/detail/detail.d.ts +3 -1
  179. package/dist/types/detail/index.d.ts +1 -1
  180. package/dist/types/detail-group/detail-group.d.ts +39 -0
  181. package/dist/types/detail-group/index.d.ts +2 -0
  182. package/dist/types/drawer/defines.d.ts +1 -0
  183. package/dist/types/drawer/drawer.d.ts +31 -0
  184. package/dist/types/drawer/index.d.ts +3 -0
  185. package/dist/types/icon/built-in-icons.d.ts +1 -0
  186. package/dist/types/icon/icon.d.ts +1 -0
  187. package/dist/types/icon/registry.d.ts +8 -0
  188. package/dist/types/index.d.ts +19 -0
  189. package/dist/types/menu/defines.d.ts +15 -0
  190. package/dist/types/menu/index.d.ts +5 -0
  191. package/dist/types/menu/menu-item.d.ts +63 -0
  192. package/dist/types/menu/menu.d.ts +34 -0
  193. package/dist/types/number-input/index.d.ts +2 -0
  194. package/dist/types/number-input/number-input.d.ts +35 -0
  195. package/dist/types/pagination/defines.d.ts +2 -0
  196. package/dist/types/pagination/index.d.ts +3 -0
  197. package/dist/types/pagination/pagination.d.ts +77 -0
  198. package/dist/types/progress-bar/defines.d.ts +1 -0
  199. package/dist/types/progress-bar/index.d.ts +3 -0
  200. package/dist/types/progress-bar/progress-bar.d.ts +37 -0
  201. package/dist/types/radio-group/radio-group.d.ts +1 -1
  202. package/dist/types/select/select.d.ts +3 -3
  203. package/dist/types/spinner/defines.d.ts +3 -0
  204. package/dist/types/spinner/index.d.ts +3 -0
  205. package/dist/types/spinner/spinner.d.ts +35 -0
  206. package/dist/types/switch/defines.d.ts +1 -0
  207. package/dist/types/switch/switch.d.ts +13 -9
  208. package/dist/types/text-input/text-input.d.ts +0 -4
  209. package/dist/types/textarea/index.d.ts +1 -0
  210. package/dist/types/textarea/textarea.d.ts +26 -0
  211. package/dist/types/tooltip/defines.d.ts +2 -0
  212. package/dist/types/tooltip/index.d.ts +4 -0
  213. package/dist/types/tooltip/tooltip.d.ts +48 -0
  214. package/dist/types/tree/defines.d.ts +23 -0
  215. package/dist/types/tree/index.d.ts +5 -0
  216. package/dist/types/tree/tree-item.d.ts +54 -0
  217. package/dist/types/tree/tree.d.ts +64 -0
  218. package/package.json +6 -6
  219. package/src/aeico-field.ts +154 -15
  220. package/src/alert/alert.ts +3 -2
  221. package/src/button/button.ts +11 -13
  222. package/src/checkbox/checkbox.ts +21 -6
  223. package/src/copy-button/copy-button.ts +146 -0
  224. package/src/copy-button/defines.ts +5 -0
  225. package/src/copy-button/index.ts +3 -0
  226. package/src/detail/defines.ts +1 -0
  227. package/src/detail/detail.ts +5 -1
  228. package/src/detail/index.ts +1 -1
  229. package/src/detail-group/detail-group.ts +104 -0
  230. package/src/detail-group/index.ts +2 -0
  231. package/src/drawer/defines.ts +1 -0
  232. package/src/drawer/drawer.ts +157 -0
  233. package/src/drawer/index.ts +3 -0
  234. package/src/icon/built-in-icons.ts +21 -0
  235. package/src/icon/icon.ts +1 -0
  236. package/src/icon/registry.ts +22 -0
  237. package/src/index.ts +32 -0
  238. package/src/menu/defines.ts +17 -0
  239. package/src/menu/index.ts +5 -0
  240. package/src/menu/menu-item.ts +315 -0
  241. package/src/menu/menu.ts +81 -0
  242. package/src/navbar/navbar.ts +1 -3
  243. package/src/number-input/index.ts +2 -0
  244. package/src/number-input/number-input.ts +137 -0
  245. package/src/pagination/defines.ts +2 -0
  246. package/src/pagination/index.ts +3 -0
  247. package/src/pagination/pagination.ts +310 -0
  248. package/src/progress-bar/defines.ts +8 -0
  249. package/src/progress-bar/index.ts +3 -0
  250. package/src/progress-bar/progress-bar.ts +80 -0
  251. package/src/radio-group/radio-group.ts +12 -5
  252. package/src/select/select.ts +112 -71
  253. package/src/slider/slider.ts +9 -2
  254. package/src/spinner/defines.ts +12 -0
  255. package/src/spinner/index.ts +3 -0
  256. package/src/spinner/spinner.ts +81 -0
  257. package/src/styles/components/action-button.css +37 -0
  258. package/src/styles/components/checkbox.css +4 -26
  259. package/src/styles/components/copy-button.css +119 -0
  260. package/src/styles/components/detail-group.css +10 -0
  261. package/src/styles/components/detail.css +10 -1
  262. package/src/styles/components/drawer.css +161 -0
  263. package/src/styles/components/field-label.css +120 -0
  264. package/src/styles/components/menu-item.css +168 -0
  265. package/src/styles/components/menu.css +17 -0
  266. package/src/styles/components/number-input.css +167 -0
  267. package/src/styles/components/pagination.css +205 -0
  268. package/src/styles/components/progress-bar.css +44 -0
  269. package/src/styles/components/radio-group.css +0 -23
  270. package/src/styles/components/select.css +12 -39
  271. package/src/styles/components/slider.css +0 -42
  272. package/src/styles/components/spinner.css +80 -0
  273. package/src/styles/components/switch.css +68 -19
  274. package/src/styles/components/tab-panel.css +1 -1
  275. package/src/styles/components/tabs.css +1 -0
  276. package/src/styles/components/text-input.css +7 -45
  277. package/src/styles/components/textarea.css +75 -0
  278. package/src/styles/components/tooltip.css +103 -0
  279. package/src/styles/components/tree-item.css +152 -0
  280. package/src/styles/components/tree.css +10 -0
  281. package/src/styles/layout.css +457 -25
  282. package/src/switch/defines.ts +1 -0
  283. package/src/switch/switch.ts +65 -16
  284. package/src/tabs/tab.ts +1 -1
  285. package/src/tabs/tabs.ts +1 -2
  286. package/src/text-input/text-input.ts +10 -15
  287. package/src/textarea/index.ts +1 -0
  288. package/src/textarea/textarea.ts +107 -0
  289. package/src/tooltip/defines.ts +11 -0
  290. package/src/tooltip/index.ts +4 -0
  291. package/src/tooltip/tooltip.ts +183 -0
  292. package/src/tree/defines.ts +26 -0
  293. package/src/tree/index.ts +5 -0
  294. package/src/tree/tree-item.ts +258 -0
  295. package/src/tree/tree.ts +237 -0
  296. package/dist/chunks/aeico-field.cjs +0 -179
  297. package/dist/chunks/aeico-field.cjs.map +0 -1
  298. package/dist/chunks/aeico-field.js +0 -180
  299. package/dist/chunks/aeico-field.js.map +0 -1
@@ -0,0 +1,315 @@
1
+ import AeicoComponent from '../aeico-component';
2
+ import type { InferProps } from 'aeico';
3
+ import { html, prop } from 'aeico';
4
+ import style from '../styles/components/menu-item.css?inline';
5
+ import variables from '../styles/variables.css?inline';
6
+ import type {
7
+ MenuMode,
8
+ MenuOrientation,
9
+ MenuTrigger,
10
+ ParentMenuLike,
11
+ MenuIconPlacement,
12
+ } from './defines';
13
+
14
+ /**
15
+ * Menu item — used as a direct child of `<ae-menu>` or nested inside another
16
+ * `<ae-menu-item>` to create a two-level submenu.
17
+ *
18
+ * - **Leaf item**: omit `label`; slot contains the item text.
19
+ * - **Parent item**: set `label` to the trigger text; slot children are
20
+ * `<ae-menu-item>` elements that appear in the submenu panel/section.
21
+ *
22
+ * **Slots (parent items only)**
23
+ * - `expand` — icon shown when the submenu is closed (default: CSS triangle).
24
+ * - `collapse` — icon shown when the submenu is open (default: rotated CSS triangle).
25
+ *
26
+ * @example
27
+ * ```html
28
+ * <ae-menu-item label="Settings" icon-placement="start">
29
+ * <ae-icon name="chevron-right" slot="expand"></ae-icon>
30
+ * <ae-icon name="chevron-down" slot="collapse"></ae-icon>
31
+ * <ae-menu-item key="profile">Profile</ae-menu-item>
32
+ * </ae-menu-item>
33
+ * ```
34
+ */
35
+ class MenuItem extends AeicoComponent {
36
+ static tagName = 'menu-item';
37
+
38
+ protected static styles = [variables, style];
39
+
40
+ @prop({ type: String })
41
+ accessor key: string | undefined;
42
+
43
+ @prop({ type: String })
44
+ accessor label: string | undefined;
45
+
46
+ @prop({ type: Boolean })
47
+ accessor disabled: boolean = false;
48
+
49
+ @prop({ type: String })
50
+ accessor href: string | undefined;
51
+
52
+ @prop({ type: Boolean })
53
+ accessor selected: boolean = false;
54
+
55
+ @prop({ type: Boolean })
56
+ accessor open: boolean = false;
57
+
58
+ @prop({ type: String })
59
+ accessor iconPlacement: MenuIconPlacement = 'end';
60
+
61
+ private _outsideClickHandler: ((e: MouseEvent) => void) | null = null;
62
+ private _closeTimer: ReturnType<typeof setTimeout> | null = null;
63
+
64
+ connectedCallback() {
65
+ super.connectedCallback();
66
+
67
+ // data-depth lets CSS apply depth-specific styles without JS
68
+ const depth = this.parentElement?.closest('ae-menu-item') ? 1 : 0;
69
+ this.dataset.depth = String(depth);
70
+
71
+ // Hover listeners — check mode/trigger at runtime
72
+ this.listen('mouseenter', this._handleMouseEnter);
73
+ this.listen('mouseleave', this._handleMouseLeave);
74
+
75
+ // Outside-click to close flyout panel (not applicable in inline mode)
76
+ this._outsideClickHandler = (e: MouseEvent) => {
77
+ if (!this.open) return;
78
+ if (this._mode === 'inline') return;
79
+ if (!e.composedPath().includes(this)) this.open = false;
80
+ };
81
+
82
+ document.addEventListener('click', this._outsideClickHandler);
83
+
84
+ // Close own submenu when a leaf inside it is selected (flyout)
85
+ this.listen('_menu-item-select', this._handleChildSelect as EventListener);
86
+ }
87
+
88
+ disconnectedCallback() {
89
+ super.disconnectedCallback();
90
+ if (this._outsideClickHandler) {
91
+ document.removeEventListener('click', this._outsideClickHandler);
92
+ this._outsideClickHandler = null;
93
+ }
94
+ if (this._closeTimer !== null) {
95
+ clearTimeout(this._closeTimer);
96
+ this._closeTimer = null;
97
+ }
98
+ }
99
+
100
+ private get _parentMenu(): ParentMenuLike | null {
101
+ return this.closest<ParentMenuLike>('ae-menu');
102
+ }
103
+
104
+ private get _mode(): MenuMode {
105
+ return this._parentMenu?.mode ?? 'flyout';
106
+ }
107
+
108
+ private get _orientation(): MenuOrientation {
109
+ return this._parentMenu?.orientation ?? 'horizontal';
110
+ }
111
+
112
+ private get _trigger(): MenuTrigger {
113
+ return this._parentMenu?.trigger ?? 'click';
114
+ }
115
+
116
+ private get _isParent(): boolean {
117
+ return this.label != null && this.label !== '';
118
+ }
119
+
120
+ /** Compute flyout panel placement based on depth and parent orientation. */
121
+ private get _panelPlacement(): 'bottom' | 'right' {
122
+ const isNested = !!this.parentElement?.closest('ae-menu-item');
123
+ if (!isNested && this._orientation === 'horizontal') return 'bottom';
124
+ return 'right';
125
+ }
126
+
127
+ private _isHoverTrigger(): boolean {
128
+ return this._isParent && this._mode === 'flyout' && this._trigger === 'hover';
129
+ }
130
+
131
+ private _handleMouseEnter = (): void => {
132
+ if (!this._isHoverTrigger()) return;
133
+ if (this._closeTimer !== null) {
134
+ clearTimeout(this._closeTimer);
135
+ this._closeTimer = null;
136
+ }
137
+ this.open = true;
138
+ };
139
+
140
+ private _handleMouseLeave = (): void => {
141
+ if (!this._isHoverTrigger()) return;
142
+ this._closeTimer = setTimeout(() => {
143
+ this.open = false;
144
+ this._closeTimer = null;
145
+ }, 150);
146
+ };
147
+
148
+ private _handleParentClick = (): void => {
149
+ if (this.disabled) return;
150
+ const mode = this._mode;
151
+ const trigger = this._trigger;
152
+ // In hover mode, click should still toggle (accessibility)
153
+ if (mode === 'flyout' && trigger === 'hover') {
154
+ this.open = !this.open;
155
+ return;
156
+ }
157
+ this.open = !this.open;
158
+ };
159
+
160
+ private _handleLeafClick = (e: Event): void => {
161
+ if (this.disabled) {
162
+ e.preventDefault();
163
+ return;
164
+ }
165
+ const path = this._buildKeyPath();
166
+ const label = this.textContent?.trim() ?? '';
167
+ this.dispatchEvent(
168
+ new CustomEvent('_menu-item-select', {
169
+ bubbles: true,
170
+ composed: true,
171
+ detail: { key: this.key ?? '', label, keyPath: path },
172
+ }),
173
+ );
174
+ };
175
+
176
+ private _handleChildSelect = (): void => {
177
+ // Close flyout panel after a child leaf is selected
178
+ if (this._mode === 'flyout') {
179
+ this.open = false;
180
+ }
181
+ };
182
+
183
+ private _handleKeydown = (e: KeyboardEvent): void => {
184
+ if (e.key === 'Escape' && this.open) {
185
+ e.stopPropagation();
186
+ this.open = false;
187
+ }
188
+ };
189
+
190
+ private _buildKeyPath(): string[] {
191
+ const path: string[] = [];
192
+ if (this.key) path.unshift(this.key);
193
+ let el: Element | null = this.parentElement;
194
+ while (el) {
195
+ const tag = el.tagName.toLowerCase();
196
+ if (tag === 'ae-menu-item') {
197
+ const k = (el as MenuItem).key;
198
+ if (k) path.unshift(k);
199
+ } else if (tag === 'ae-menu') {
200
+ break;
201
+ }
202
+ el = el.parentElement;
203
+ }
204
+ return path;
205
+ }
206
+
207
+ protected render() {
208
+ const mode = this._mode;
209
+ const panelPlacement = this._panelPlacement;
210
+ const isParent = this._isParent;
211
+ const isInline = mode === 'inline';
212
+ // Arrow direction: inline always shows ► (rotates to ▼ on open)
213
+ // flyout-bottom shows ▼; flyout-right shows ►
214
+ const arrowDir = !isInline && panelPlacement === 'bottom' ? 'bottom' : 'right';
215
+ // Leaf items inside a submenu panel (depth > 0) need different padding
216
+ const isNested = !!this.parentElement?.closest('ae-menu-item');
217
+
218
+ return html(({ div, button, a, span, slot }) => {
219
+ if (isParent) {
220
+ div({ className: 'item-wrapper' }, () => {
221
+ button(
222
+ {
223
+ type: 'button',
224
+ className: { item: true, 'item--parent': true, 'item--open': this.open },
225
+ disabled: this.disabled,
226
+ 'aria-haspopup': 'menu',
227
+ 'aria-expanded': String(this.open),
228
+ '@click': this._handleParentClick,
229
+ '@keydown': this._handleKeydown,
230
+ },
231
+ () => {
232
+ span({ text: this.label });
233
+ slot({ name: 'expand' }, () => {
234
+ span({ className: `item-arrow item-arrow--${arrowDir}`, 'aria-hidden': 'true' });
235
+ });
236
+ slot({ name: 'collapse' }, () => {
237
+ span({ className: `item-arrow item-arrow--${arrowDir}`, 'aria-hidden': 'true' });
238
+ });
239
+ },
240
+ );
241
+
242
+ if (isInline) {
243
+ div(
244
+ {
245
+ className: { 'submenu-inline': true, open: this.open },
246
+ role: 'menu',
247
+ },
248
+ () => {
249
+ slot();
250
+ },
251
+ );
252
+ } else {
253
+ div(
254
+ {
255
+ className: {
256
+ 'submenu-panel': true,
257
+ [`placement-${panelPlacement}`]: true,
258
+ open: this.open,
259
+ },
260
+ role: 'menu',
261
+ },
262
+ () => {
263
+ slot();
264
+ },
265
+ );
266
+ }
267
+ });
268
+ } else {
269
+ // Leaf item
270
+ const sharedProps = {
271
+ className: { item: true, 'item--leaf-in-panel': isNested },
272
+ role: 'menuitem',
273
+ '@click': this._handleLeafClick,
274
+ '@keydown': this._handleKeydown,
275
+ };
276
+
277
+ if (this.href) {
278
+ a(
279
+ {
280
+ ...sharedProps,
281
+ href: this.disabled ? undefined : this.href,
282
+ 'aria-disabled': this.disabled ? 'true' : undefined,
283
+ },
284
+ () => {
285
+ slot();
286
+ },
287
+ );
288
+ } else {
289
+ button(
290
+ {
291
+ ...sharedProps,
292
+ type: 'button',
293
+ disabled: this.disabled,
294
+ },
295
+ () => {
296
+ slot();
297
+ },
298
+ );
299
+ }
300
+ }
301
+ });
302
+ }
303
+ }
304
+
305
+ MenuItem.register();
306
+
307
+ declare global {
308
+ interface HTMLElementTagNameMap {
309
+ 'ae-menu-item': MenuItem;
310
+ }
311
+ }
312
+
313
+ export default MenuItem;
314
+ export type MenuItemProps = InferProps<typeof MenuItem>;
315
+ export type { MenuIconPlacement };
@@ -0,0 +1,81 @@
1
+ import AeicoComponent from '../aeico-component';
2
+ import type { InferProps } from 'aeico';
3
+ import { html, prop } from 'aeico';
4
+ import style from '../styles/components/menu.css?inline';
5
+ import variables from '../styles/variables.css?inline';
6
+ import type { MenuMode, MenuOrientation, MenuSelectDetail, MenuTrigger } from './defines';
7
+ // Ensure ae-menu-item is registered when this module is used
8
+ import './menu-item';
9
+
10
+ /**
11
+ * Menu navigation component. Renders a horizontal or vertical menu with
12
+ * optional two-level flyout or inline (accordion) submenus.
13
+ *
14
+ * Two modes:
15
+ * - `flyout` (default) — submenus open as floating panels (like a nav bar or
16
+ * context menu).
17
+ * - `inline` — submenus expand in-place (accordion-style sidebar).
18
+ *
19
+ * Emits:
20
+ * - `select` — `{ detail: { key, label, keyPath } }` when a leaf item is clicked.
21
+ */
22
+ class Menu extends AeicoComponent {
23
+ static tagName = 'menu';
24
+
25
+ protected static styles = [variables, style];
26
+
27
+ @prop({ type: String })
28
+ accessor mode: MenuMode = 'flyout';
29
+
30
+ @prop({ type: String })
31
+ accessor orientation: MenuOrientation = 'horizontal';
32
+
33
+ @prop({ type: String })
34
+ accessor trigger: MenuTrigger = 'click';
35
+
36
+ @prop({ type: String })
37
+ accessor selectedKey: string | undefined;
38
+
39
+ connectedCallback() {
40
+ super.connectedCallback();
41
+ this.listen('_menu-item-select', this._handleItemSelect as EventListener);
42
+ }
43
+
44
+ private _handleItemSelect = (e: CustomEvent<MenuSelectDetail>): void => {
45
+ const { key, label, keyPath } = e.detail;
46
+ // Update visual selection on all leaf items
47
+ this.querySelectorAll('ae-menu-item').forEach((el) => {
48
+ const item = el;
49
+ item.selected = item.key === key && !item.label; // only leaf items
50
+ });
51
+ this.selectedKey = key;
52
+ this.emit('select', { detail: { key, label, keyPath } });
53
+ };
54
+
55
+ protected render() {
56
+ const isHorizontal = this.orientation === 'horizontal';
57
+ return html(({ div, slot }) => {
58
+ div(
59
+ {
60
+ className: { 'menu-list': true, 'menu-list--horizontal': isHorizontal },
61
+ role: isHorizontal ? 'menubar' : 'menu',
62
+ 'aria-orientation': this.orientation,
63
+ },
64
+ () => {
65
+ slot();
66
+ },
67
+ );
68
+ });
69
+ }
70
+ }
71
+
72
+ Menu.register();
73
+
74
+ declare global {
75
+ interface HTMLElementTagNameMap {
76
+ 'ae-menu': Menu;
77
+ }
78
+ }
79
+
80
+ export default Menu;
81
+ export type MenuProps = InferProps<typeof Menu>;
@@ -74,8 +74,6 @@ class Navbar extends AeicoComponent {
74
74
 
75
75
  connectedCallback() {
76
76
  super.connectedCallback();
77
- // Close menu when a nav link is clicked on mobile
78
- this.listen('click', this._handleInnerClick);
79
77
  // Close menu when clicking outside the navbar
80
78
  this._outsideClickHandler = (e: MouseEvent) => {
81
79
  // Event retargeting in shadow DOM means e.target is the host element
@@ -119,7 +117,7 @@ class Navbar extends AeicoComponent {
119
117
 
120
118
  protected render() {
121
119
  return html(({ div, nav, button, span, slot }) => {
122
- div({ class: 'inner' }, () => {
120
+ div({ class: 'inner', '@click': this._handleInnerClick }, () => {
123
121
  div({ part: 'brand' }, () => {
124
122
  slot({ name: 'brand' });
125
123
  });
@@ -0,0 +1,2 @@
1
+ export { default, default as NumberInput } from './number-input';
2
+ export type { NumberInputProps } from './number-input';
@@ -0,0 +1,137 @@
1
+ import AeicoField from '../aeico-field';
2
+ import type { InferProps, Props } from 'aeico';
3
+ import { html } from 'aeico';
4
+ import variables from '../styles/variables.css?inline';
5
+ import sizeCSS from '../styles/size.css?inline';
6
+ import fieldLabelCSS from '../styles/components/field-label.css?inline';
7
+ import actionButtonCSS from '../styles/components/action-button.css?inline';
8
+ import style from '../styles/components/number-input.css?inline';
9
+
10
+ class NumberInput extends AeicoField<number> {
11
+ protected fieldElement: HTMLInputElement | null = null;
12
+
13
+ static tagName = 'number-input';
14
+
15
+ static props: Props = {
16
+ placeholder: { type: String },
17
+ min: { type: Number },
18
+ max: { type: Number },
19
+ step: { type: Number },
20
+ controls: { type: Boolean },
21
+ };
22
+
23
+ declare placeholder?: string;
24
+ declare min?: number;
25
+ declare max?: number;
26
+ declare step?: number;
27
+ declare controls?: boolean;
28
+
29
+ protected static styles = [variables, sizeCSS, fieldLabelCSS, actionButtonCSS, style];
30
+
31
+ render() {
32
+ return html(({ div, input, button }) => {
33
+ const id = this.getFieldId();
34
+ this.renderLabel(id);
35
+ div({ className: 'input-container field-body' }, () => {
36
+ this.fieldElement = input({
37
+ id,
38
+ type: 'number',
39
+ placeholder: this.placeholder || '',
40
+ required: Boolean(this.required),
41
+ disabled: Boolean(this.disabled),
42
+ min: this.min != null ? String(this.min) : undefined,
43
+ max: this.max != null ? String(this.max) : undefined,
44
+ step: this.step != null ? String(this.step) : undefined,
45
+ '@input': this.boundOnChange,
46
+ });
47
+
48
+ if (this.controls) {
49
+ div({ className: 'number-controls' }, () => {
50
+ button({
51
+ className: 'number-btn number-btn-increment',
52
+ textContent: '+',
53
+ disabled: Boolean(this.disabled),
54
+ '@click': this.boundOnIncrement,
55
+ });
56
+ button({
57
+ className: 'number-btn number-btn-decrement',
58
+ textContent: '-',
59
+ disabled: Boolean(this.disabled),
60
+ '@click': this.boundOnDecrement,
61
+ });
62
+ });
63
+ }
64
+
65
+ this.renderActionButtons();
66
+ });
67
+ this.renderHelperText();
68
+ this.renderError();
69
+
70
+ if (this.fieldElement && this.value != null) {
71
+ this.fieldElement.value = String(this.value);
72
+ }
73
+ });
74
+ }
75
+
76
+ protected readonly boundOnIncrement = () => {
77
+ const current = this.getValue() || 0;
78
+ const step = this.step ?? 1;
79
+ let next = current + step;
80
+ if (this.max != null && next > this.max) {
81
+ next = this.max;
82
+ }
83
+ this.setValue(next, { silent: false, action: 'change' });
84
+ };
85
+
86
+ protected readonly boundOnDecrement = () => {
87
+ const current = this.getValue() || 0;
88
+ const step = this.step ?? 1;
89
+ let next = current - step;
90
+ if (this.min != null && next < this.min) {
91
+ next = this.min;
92
+ }
93
+ this.setValue(next, { silent: false, action: 'change' });
94
+ };
95
+
96
+ /**
97
+ * Get current value as number
98
+ */
99
+ protected getValue(): number {
100
+ if (!this.fieldElement) return 0;
101
+ const val = this.fieldElement.value;
102
+ if (val === '') return 0;
103
+ const num = parseFloat(val);
104
+ return isNaN(num) ? 0 : num;
105
+ }
106
+
107
+ /**
108
+ * Write value to the input element (DOM only)
109
+ */
110
+ protected writeValue(value: number): void {
111
+ if (this.fieldElement) {
112
+ this.fieldElement.value = value != null ? String(value) : '';
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Get event payload for change events
118
+ */
119
+ protected getEventPayload(
120
+ value: number,
121
+ oldValue: number,
122
+ action: import('../aeico-field').FieldAction,
123
+ ): Record<string, unknown> {
124
+ return { value, oldValue, action };
125
+ }
126
+ }
127
+
128
+ NumberInput.register();
129
+
130
+ declare global {
131
+ interface HTMLElementTagNameMap {
132
+ 'ae-number-input': NumberInput;
133
+ }
134
+ }
135
+
136
+ export default NumberInput;
137
+ export type NumberInputProps = InferProps<typeof NumberInput>;
@@ -0,0 +1,2 @@
1
+ export type PaginationSize = 'xs' | 'sm' | 'md' | 'lg';
2
+ export type PaginationVariant = 'borderless' | 'link';
@@ -0,0 +1,3 @@
1
+ export { default, default as Pagination } from './pagination';
2
+ export type { PaginationProps } from './pagination';
3
+ export type { PaginationSize, PaginationVariant } from './defines';