Skip to content

Commit 798442b

Browse files
committed
Support keyboard control
1 parent 559f78a commit 798442b

File tree

5 files changed

+136
-25
lines changed

5 files changed

+136
-25
lines changed

src/examples/ContextMenuExample.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ class ContextMenuExample extends React.Component<IProps, IState> {
6161
label: 'Back (enabled: false)',
6262
icon: <ArrowLeftOutlined />,
6363
click: this.onClickMenu,
64-
enabled: false,
6564
},
6665
{
6766
label: 'Forward',

src/pages/Sample.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,13 @@ class Index extends React.Component<IProps, IState> {
5959
const { viewMenuBarRaw, viewBasicRaw } = this.state;
6060

6161
return (
62-
<Component>
62+
<Component style={{ outline: 0 }}>
6363
<header className={'app-header'}>
6464
<StyledContainer>
6565
<div className={'logo-img'}>
6666
<img src={axuiLogo} />
6767
</div>
6868
<h1>react-electron-window-menu</h1>
69-
7069
<div>
7170
{window.location.host !== 'localhost:3000' && (
7271
<>

src/react-electron-window-menu/@types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export namespace IREWMenu {
1010
export type OnClickItem = (
1111
menuItem: IMenuItem,
1212
browserWindow: Window,
13-
event: React.MouseEvent<HTMLDivElement>,
13+
event?: React.MouseEvent<HTMLDivElement>,
1414
) => void;
1515

1616
export interface IMenuItem {

src/react-electron-window-menu/ContextMenu.tsx

Lines changed: 133 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,15 @@ class ContextMenu implements IREWMenu.IContextMenu {
5151
} else {
5252
this.container = document.createElement('div');
5353
this.container.setAttribute('data-rewm-contextmenu-container', id);
54+
this.container.setAttribute('tabindex', '0');
5455
document.body.appendChild(this.container);
5556
}
5657

5758
// set style of this.container
5859
this.container.style.position = 'absolute';
5960
this.container.style.left = containerLeft + 'px';
6061
this.container.style.top = containerTop + 'px';
62+
this.container.style.outline = '0';
6163

6264
if (this.container) {
6365
this.visible = true;
@@ -69,15 +71,21 @@ class ContextMenu implements IREWMenu.IContextMenu {
6971
}
7072

7173
onClickItem: IREWMenu.OnClickItem = (menuItem, w, e) => {
72-
const { type = 'normal', enabled = true, visible = true } = menuItem;
74+
const { type = 'normal', enabled = true } = menuItem;
7375

7476
if (enabled) {
77+
if (!menuItem.submenu && menuItem.click) {
78+
menuItem.click(menuItem, w, e);
79+
}
80+
7581
if (type === 'checkbox') {
7682
menuItem.checked = !menuItem.checked;
83+
this.render();
84+
} else {
85+
// 메뉴가 클릭되었다는 것을 인지하는 곳.
86+
this.visible = false;
7787
}
7888
}
79-
// 메뉴가 클릭되었다는 것은 인지하는 곳.
80-
this.visible = false;
8189
};
8290

8391
// document.body에서 마우스 다운이 일어난 경우 contextMenu안쪽이 클릭된 것이지 바깥쪽에서 마우스 다운이 일어 난 건지 체크.
@@ -88,9 +96,123 @@ class ContextMenu implements IREWMenu.IContextMenu {
8896
}
8997
};
9098

91-
onKeyDownWindow = (e: KeyboardEvent) => {
92-
if (e.which === 27) {
93-
this.visible = false;
99+
getCurrentHoveredIndex = (): number[] => {
100+
const currentHoveredIndex: number[] = [];
101+
const findOpenedItem = (items: IREWMenu.IMenuItem[]) => {
102+
const findex = items.findIndex(item => item.opened);
103+
if (findex > -1) {
104+
currentHoveredIndex.push(findex);
105+
if (items[findex].submenu) {
106+
findOpenedItem(items[findex].submenu!);
107+
}
108+
}
109+
};
110+
findOpenedItem(this.menuItems);
111+
return currentHoveredIndex;
112+
};
113+
114+
moveHoveredIndex = (type: 'UP' | 'DOWN' | 'LEFT' | 'RIGHT') => {
115+
const currentHoveredIndexes = this.getCurrentHoveredIndex();
116+
let targetItems = this.menuItems;
117+
let targetItemsHoveredIndex = currentHoveredIndexes[0];
118+
119+
currentHoveredIndexes.forEach((v, i) => {
120+
if (i < currentHoveredIndexes.length - 1 && targetItems[v].submenu) {
121+
targetItems = targetItems[v].submenu!;
122+
targetItemsHoveredIndex = currentHoveredIndexes[i + 1];
123+
}
124+
});
125+
if (type === 'UP' || type === 'DOWN') {
126+
targetItems.forEach(item => (item.opened = false));
127+
128+
let ing = true;
129+
do {
130+
const nextMenuIndex =
131+
type === 'DOWN'
132+
? targetItemsHoveredIndex === undefined
133+
? 0
134+
: targetItemsHoveredIndex + 1
135+
: targetItemsHoveredIndex === undefined
136+
? targetItems.length - 1
137+
: targetItemsHoveredIndex - 1;
138+
const nextMenu = targetItems[nextMenuIndex];
139+
if (!nextMenu) {
140+
targetItems[targetItemsHoveredIndex].opened = true;
141+
ing = false;
142+
break;
143+
}
144+
if (nextMenu.type === 'separator' || nextMenu.visible === false) {
145+
targetItemsHoveredIndex = nextMenuIndex;
146+
continue;
147+
}
148+
targetItems[nextMenuIndex].opened = true;
149+
ing = false;
150+
} while (ing);
151+
152+
this.render();
153+
} else if (type === 'RIGHT') {
154+
if (targetItemsHoveredIndex === undefined) targetItemsHoveredIndex = 0;
155+
targetItems[targetItemsHoveredIndex].submenu?.forEach(
156+
item => (item.opened = false),
157+
);
158+
const submenu = targetItems[targetItemsHoveredIndex].submenu;
159+
if (submenu) {
160+
submenu[0].opened = true;
161+
}
162+
this.render();
163+
} else if (type === 'LEFT') {
164+
targetItems.forEach(item => (item.opened = false));
165+
this.render();
166+
}
167+
};
168+
169+
handleClickItem = (e: KeyboardEvent) => {
170+
let menu: IREWMenu.IMenuItem | undefined;
171+
const findOpenedItem = (items: IREWMenu.IMenuItem[]) => {
172+
const findex = items.findIndex(item => item.opened);
173+
if (findex > -1) {
174+
menu = items[findex];
175+
if (items[findex].submenu) {
176+
findOpenedItem(items[findex].submenu!);
177+
}
178+
}
179+
};
180+
findOpenedItem(this.menuItems);
181+
182+
if (menu) {
183+
this.onClickItem(menu, window);
184+
}
185+
};
186+
187+
onKeyDown = (e: KeyboardEvent) => {
188+
e.preventDefault();
189+
190+
switch (e.key) {
191+
case 'Down': // IE/Edge specific value
192+
case 'ArrowDown':
193+
this.moveHoveredIndex('DOWN');
194+
break;
195+
case 'Up': // IE/Edge specific value
196+
case 'ArrowUp':
197+
this.moveHoveredIndex('UP');
198+
break;
199+
case 'Left': // IE/Edge specific value
200+
case 'ArrowLeft':
201+
this.moveHoveredIndex('LEFT');
202+
break;
203+
case 'Right': // IE/Edge specific value
204+
case 'ArrowRight':
205+
this.moveHoveredIndex('RIGHT');
206+
break;
207+
case 'Enter':
208+
this.handleClickItem(e);
209+
break;
210+
case 'Esc': // IE/Edge specific value
211+
case 'Escape':
212+
this.visible = false;
213+
break;
214+
default:
215+
return; // Quit when this doesn't handle the key event.
94216
}
95217
};
96218

@@ -115,11 +237,14 @@ class ContextMenu implements IREWMenu.IContextMenu {
115237
);
116238

117239
if (this.visible) {
240+
this.container.focus();
118241
document.body.addEventListener('mousedown', this.onMousedownBody);
119-
window.addEventListener('keydown', this.onKeyDownWindow);
242+
this.container.addEventListener('keydown', this.onKeyDown);
120243
} else {
121244
document.body.removeEventListener('mousedown', this.onMousedownBody);
122-
window.removeEventListener('keydown', this.onKeyDownWindow);
245+
this.container.removeEventListener('keydown', this.onKeyDown);
246+
this.container.blur();
247+
this.container.remove();
123248
}
124249
}
125250
}

src/react-electron-window-menu/components/MenuItem.tsx

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,7 @@ class MenuItem extends React.Component<IREWMenu.IMenuItemProps> {
5757

5858
render() {
5959
const { item, onClickItem, onHoverItem } = this.props;
60-
const {
61-
type = 'normal',
62-
label,
63-
icon,
64-
checked,
65-
submenu,
66-
click,
67-
enabled = true,
68-
visible = true,
69-
} = item;
60+
const { type = 'normal', icon, enabled = true, visible = true } = item;
7061
const itemProps = {};
7162

7263
if (!visible) {
@@ -91,9 +82,6 @@ class MenuItem extends React.Component<IREWMenu.IMenuItemProps> {
9182
// has click and dont have submenu
9283
if (!item.submenu && enabled) {
9384
onClickItem(item, window, e);
94-
if (click) {
95-
click(item, window, e);
96-
}
9785
}
9886
}}
9987
onMouseOver={e => {

0 commit comments

Comments
 (0)