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.
- package/LICENSE +21 -0
- package/README.md +0 -0
- package/dist/chunks/action-button.cjs +296 -0
- package/dist/chunks/action-button.cjs.map +1 -0
- package/dist/chunks/action-button.js +297 -0
- package/dist/chunks/action-button.js.map +1 -0
- package/dist/chunks/alert.cjs +4 -4
- package/dist/chunks/alert.cjs.map +1 -1
- package/dist/chunks/alert.js +5 -5
- package/dist/chunks/alert.js.map +1 -1
- package/dist/chunks/badge.cjs +1 -1
- package/dist/chunks/badge.cjs.map +1 -1
- package/dist/chunks/badge.js +2 -2
- package/dist/chunks/badge.js.map +1 -1
- package/dist/chunks/breadcrumb-item.cjs +2 -2
- package/dist/chunks/breadcrumb-item.cjs.map +1 -1
- package/dist/chunks/breadcrumb-item.js +3 -3
- package/dist/chunks/breadcrumb-item.js.map +1 -1
- package/dist/chunks/button-group.cjs +1 -1
- package/dist/chunks/button-group.cjs.map +1 -1
- package/dist/chunks/button-group.js +2 -2
- package/dist/chunks/button-group.js.map +1 -1
- package/dist/chunks/button.cjs +12 -15
- package/dist/chunks/button.cjs.map +1 -1
- package/dist/chunks/button.js +13 -16
- package/dist/chunks/button.js.map +1 -1
- package/dist/chunks/card.cjs +1 -1
- package/dist/chunks/card.cjs.map +1 -1
- package/dist/chunks/card.js +2 -2
- package/dist/chunks/card.js.map +1 -1
- package/dist/chunks/checkbox.cjs +18 -5
- package/dist/chunks/checkbox.cjs.map +1 -1
- package/dist/chunks/checkbox.js +18 -5
- package/dist/chunks/checkbox.js.map +1 -1
- package/dist/chunks/copy-button.cjs +168 -0
- package/dist/chunks/copy-button.cjs.map +1 -0
- package/dist/chunks/copy-button.js +169 -0
- package/dist/chunks/copy-button.js.map +1 -0
- package/dist/chunks/detail.cjs +7 -4
- package/dist/chunks/detail.cjs.map +1 -1
- package/dist/chunks/detail.js +8 -5
- package/dist/chunks/detail.js.map +1 -1
- package/dist/chunks/dialog.cjs +1 -1
- package/dist/chunks/dialog.cjs.map +1 -1
- package/dist/chunks/dialog.js +2 -2
- package/dist/chunks/dialog.js.map +1 -1
- package/dist/chunks/divider.cjs +1 -1
- package/dist/chunks/divider.cjs.map +1 -1
- package/dist/chunks/divider.js +2 -2
- package/dist/chunks/divider.js.map +1 -1
- package/dist/chunks/drawer.cjs +180 -0
- package/dist/chunks/drawer.cjs.map +1 -0
- package/dist/chunks/drawer.js +181 -0
- package/dist/chunks/drawer.js.map +1 -0
- package/dist/chunks/dropdown-button.cjs +2 -2
- package/dist/chunks/dropdown-button.cjs.map +1 -1
- package/dist/chunks/dropdown-button.js +6 -6
- package/dist/chunks/dropdown-button.js.map +1 -1
- package/dist/chunks/icon.cjs +31 -1
- package/dist/chunks/icon.cjs.map +1 -1
- package/dist/chunks/icon.js +32 -2
- package/dist/chunks/icon.js.map +1 -1
- package/dist/chunks/menu.cjs +396 -0
- package/dist/chunks/menu.cjs.map +1 -0
- package/dist/chunks/menu.js +397 -0
- package/dist/chunks/menu.js.map +1 -0
- package/dist/chunks/navbar.cjs +2 -3
- package/dist/chunks/navbar.cjs.map +1 -1
- package/dist/chunks/navbar.js +3 -4
- package/dist/chunks/navbar.js.map +1 -1
- package/dist/chunks/pagination.cjs +475 -0
- package/dist/chunks/pagination.cjs.map +1 -0
- package/dist/chunks/pagination.js +476 -0
- package/dist/chunks/pagination.js.map +1 -0
- package/dist/chunks/progress-bar.cjs +101 -0
- package/dist/chunks/progress-bar.cjs.map +1 -0
- package/dist/chunks/progress-bar.js +102 -0
- package/dist/chunks/progress-bar.js.map +1 -0
- package/dist/chunks/radio.cjs +11 -7
- package/dist/chunks/radio.cjs.map +1 -1
- package/dist/chunks/radio.js +11 -7
- package/dist/chunks/radio.js.map +1 -1
- package/dist/chunks/select.cjs +97 -66
- package/dist/chunks/select.cjs.map +1 -1
- package/dist/chunks/select.js +97 -66
- package/dist/chunks/select.js.map +1 -1
- package/dist/chunks/slider.cjs +9 -46
- package/dist/chunks/slider.cjs.map +1 -1
- package/dist/chunks/slider.js +9 -46
- package/dist/chunks/slider.js.map +1 -1
- package/dist/chunks/spinner.cjs +102 -0
- package/dist/chunks/spinner.cjs.map +1 -0
- package/dist/chunks/spinner.js +103 -0
- package/dist/chunks/spinner.js.map +1 -0
- package/dist/chunks/switch.cjs +110 -16
- package/dist/chunks/switch.cjs.map +1 -1
- package/dist/chunks/switch.js +111 -17
- package/dist/chunks/switch.js.map +1 -1
- package/dist/chunks/tab-panel.cjs +6 -7
- package/dist/chunks/tab-panel.cjs.map +1 -1
- package/dist/chunks/tab-panel.js +7 -8
- package/dist/chunks/tab-panel.js.map +1 -1
- package/dist/chunks/tag.cjs +1 -1
- package/dist/chunks/tag.cjs.map +1 -1
- package/dist/chunks/tag.js +2 -2
- package/dist/chunks/tag.js.map +1 -1
- package/dist/chunks/text-input.cjs +11 -16
- package/dist/chunks/text-input.cjs.map +1 -1
- package/dist/chunks/text-input.js +11 -16
- package/dist/chunks/text-input.js.map +1 -1
- package/dist/chunks/textarea.cjs +137 -0
- package/dist/chunks/textarea.cjs.map +1 -0
- package/dist/chunks/textarea.js +138 -0
- package/dist/chunks/textarea.js.map +1 -0
- package/dist/chunks/tooltip.cjs +293 -0
- package/dist/chunks/tooltip.cjs.map +1 -0
- package/dist/chunks/tooltip.js +294 -0
- package/dist/chunks/tooltip.js.map +1 -0
- package/dist/chunks/tree.cjs +468 -0
- package/dist/chunks/tree.cjs.map +1 -0
- package/dist/chunks/tree.js +469 -0
- package/dist/chunks/tree.js.map +1 -0
- package/dist/chunks/variables.cjs +2 -2
- package/dist/chunks/variables.js +2 -2
- package/dist/copy-button.cjs +6 -0
- package/dist/copy-button.cjs.map +1 -0
- package/dist/copy-button.js +6 -0
- package/dist/copy-button.js.map +1 -0
- package/dist/drawer.cjs +6 -0
- package/dist/drawer.cjs.map +1 -0
- package/dist/drawer.js +6 -0
- package/dist/drawer.js.map +1 -0
- package/dist/dropdown.js +4 -4
- package/dist/index.cjs +186 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +201 -15
- package/dist/index.js.map +1 -1
- package/dist/menu.cjs +6 -0
- package/dist/menu.cjs.map +1 -0
- package/dist/menu.js +6 -0
- package/dist/menu.js.map +1 -0
- package/dist/pagination.cjs +6 -0
- package/dist/pagination.cjs.map +1 -0
- package/dist/pagination.js +6 -0
- package/dist/pagination.js.map +1 -0
- package/dist/progress-bar.cjs +6 -0
- package/dist/progress-bar.cjs.map +1 -0
- package/dist/progress-bar.js +6 -0
- package/dist/progress-bar.js.map +1 -0
- package/dist/select.cjs +1 -1
- package/dist/select.cjs.map +1 -1
- package/dist/select.js +2 -2
- package/dist/select.js.map +1 -1
- package/dist/spinner.cjs +6 -0
- package/dist/spinner.cjs.map +1 -0
- package/dist/spinner.js +6 -0
- package/dist/spinner.js.map +1 -0
- package/dist/textarea.cjs +5 -0
- package/dist/textarea.cjs.map +1 -0
- package/dist/textarea.js +5 -0
- package/dist/textarea.js.map +1 -0
- package/dist/tooltip.cjs +6 -0
- package/dist/tooltip.cjs.map +1 -0
- package/dist/tooltip.js +6 -0
- package/dist/tooltip.js.map +1 -0
- package/dist/tree.cjs +6 -0
- package/dist/tree.cjs.map +1 -0
- package/dist/tree.js +6 -0
- package/dist/tree.js.map +1 -0
- package/dist/types/aeico-field.d.ts +57 -5
- package/dist/types/alert/alert.d.ts +1 -0
- package/dist/types/button/button.d.ts +2 -1
- package/dist/types/checkbox/checkbox.d.ts +5 -5
- package/dist/types/copy-button/copy-button.d.ts +32 -0
- package/dist/types/copy-button/defines.d.ts +1 -0
- package/dist/types/copy-button/index.d.ts +3 -0
- package/dist/types/detail/defines.d.ts +1 -0
- package/dist/types/detail/detail.d.ts +3 -1
- package/dist/types/detail/index.d.ts +1 -1
- package/dist/types/detail-group/detail-group.d.ts +39 -0
- package/dist/types/detail-group/index.d.ts +2 -0
- package/dist/types/drawer/defines.d.ts +1 -0
- package/dist/types/drawer/drawer.d.ts +31 -0
- package/dist/types/drawer/index.d.ts +3 -0
- package/dist/types/icon/built-in-icons.d.ts +1 -0
- package/dist/types/icon/icon.d.ts +1 -0
- package/dist/types/icon/registry.d.ts +8 -0
- package/dist/types/index.d.ts +19 -0
- package/dist/types/menu/defines.d.ts +15 -0
- package/dist/types/menu/index.d.ts +5 -0
- package/dist/types/menu/menu-item.d.ts +63 -0
- package/dist/types/menu/menu.d.ts +34 -0
- package/dist/types/number-input/index.d.ts +2 -0
- package/dist/types/number-input/number-input.d.ts +35 -0
- package/dist/types/pagination/defines.d.ts +2 -0
- package/dist/types/pagination/index.d.ts +3 -0
- package/dist/types/pagination/pagination.d.ts +77 -0
- package/dist/types/progress-bar/defines.d.ts +1 -0
- package/dist/types/progress-bar/index.d.ts +3 -0
- package/dist/types/progress-bar/progress-bar.d.ts +37 -0
- package/dist/types/radio-group/radio-group.d.ts +1 -1
- package/dist/types/select/select.d.ts +3 -3
- package/dist/types/spinner/defines.d.ts +3 -0
- package/dist/types/spinner/index.d.ts +3 -0
- package/dist/types/spinner/spinner.d.ts +35 -0
- package/dist/types/switch/defines.d.ts +1 -0
- package/dist/types/switch/switch.d.ts +13 -9
- package/dist/types/text-input/text-input.d.ts +0 -4
- package/dist/types/textarea/index.d.ts +1 -0
- package/dist/types/textarea/textarea.d.ts +26 -0
- package/dist/types/tooltip/defines.d.ts +2 -0
- package/dist/types/tooltip/index.d.ts +4 -0
- package/dist/types/tooltip/tooltip.d.ts +48 -0
- package/dist/types/tree/defines.d.ts +23 -0
- package/dist/types/tree/index.d.ts +5 -0
- package/dist/types/tree/tree-item.d.ts +54 -0
- package/dist/types/tree/tree.d.ts +64 -0
- package/package.json +6 -6
- package/src/aeico-field.ts +154 -15
- package/src/alert/alert.ts +3 -2
- package/src/button/button.ts +11 -13
- package/src/checkbox/checkbox.ts +21 -6
- package/src/copy-button/copy-button.ts +146 -0
- package/src/copy-button/defines.ts +5 -0
- package/src/copy-button/index.ts +3 -0
- package/src/detail/defines.ts +1 -0
- package/src/detail/detail.ts +5 -1
- package/src/detail/index.ts +1 -1
- package/src/detail-group/detail-group.ts +104 -0
- package/src/detail-group/index.ts +2 -0
- package/src/drawer/defines.ts +1 -0
- package/src/drawer/drawer.ts +157 -0
- package/src/drawer/index.ts +3 -0
- package/src/icon/built-in-icons.ts +21 -0
- package/src/icon/icon.ts +1 -0
- package/src/icon/registry.ts +22 -0
- package/src/index.ts +32 -0
- package/src/menu/defines.ts +17 -0
- package/src/menu/index.ts +5 -0
- package/src/menu/menu-item.ts +315 -0
- package/src/menu/menu.ts +81 -0
- package/src/navbar/navbar.ts +1 -3
- package/src/number-input/index.ts +2 -0
- package/src/number-input/number-input.ts +137 -0
- package/src/pagination/defines.ts +2 -0
- package/src/pagination/index.ts +3 -0
- package/src/pagination/pagination.ts +310 -0
- package/src/progress-bar/defines.ts +8 -0
- package/src/progress-bar/index.ts +3 -0
- package/src/progress-bar/progress-bar.ts +80 -0
- package/src/radio-group/radio-group.ts +12 -5
- package/src/select/select.ts +112 -71
- package/src/slider/slider.ts +9 -2
- package/src/spinner/defines.ts +12 -0
- package/src/spinner/index.ts +3 -0
- package/src/spinner/spinner.ts +81 -0
- package/src/styles/components/action-button.css +37 -0
- package/src/styles/components/checkbox.css +4 -26
- package/src/styles/components/copy-button.css +119 -0
- package/src/styles/components/detail-group.css +10 -0
- package/src/styles/components/detail.css +10 -1
- package/src/styles/components/drawer.css +161 -0
- package/src/styles/components/field-label.css +120 -0
- package/src/styles/components/menu-item.css +168 -0
- package/src/styles/components/menu.css +17 -0
- package/src/styles/components/number-input.css +167 -0
- package/src/styles/components/pagination.css +205 -0
- package/src/styles/components/progress-bar.css +44 -0
- package/src/styles/components/radio-group.css +0 -23
- package/src/styles/components/select.css +12 -39
- package/src/styles/components/slider.css +0 -42
- package/src/styles/components/spinner.css +80 -0
- package/src/styles/components/switch.css +68 -19
- package/src/styles/components/tab-panel.css +1 -1
- package/src/styles/components/tabs.css +1 -0
- package/src/styles/components/text-input.css +7 -45
- package/src/styles/components/textarea.css +75 -0
- package/src/styles/components/tooltip.css +103 -0
- package/src/styles/components/tree-item.css +152 -0
- package/src/styles/components/tree.css +10 -0
- package/src/styles/layout.css +457 -25
- package/src/switch/defines.ts +1 -0
- package/src/switch/switch.ts +65 -16
- package/src/tabs/tab.ts +1 -1
- package/src/tabs/tabs.ts +1 -2
- package/src/text-input/text-input.ts +10 -15
- package/src/textarea/index.ts +1 -0
- package/src/textarea/textarea.ts +107 -0
- package/src/tooltip/defines.ts +11 -0
- package/src/tooltip/index.ts +4 -0
- package/src/tooltip/tooltip.ts +183 -0
- package/src/tree/defines.ts +26 -0
- package/src/tree/index.ts +5 -0
- package/src/tree/tree-item.ts +258 -0
- package/src/tree/tree.ts +237 -0
- package/dist/chunks/aeico-field.cjs +0 -179
- package/dist/chunks/aeico-field.cjs.map +0 -1
- package/dist/chunks/aeico-field.js +0 -180
- 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 };
|
package/src/menu/menu.ts
ADDED
|
@@ -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>;
|
package/src/navbar/navbar.ts
CHANGED
|
@@ -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,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>;
|