Skip to content

Commit 8f15e86

Browse files
:bug fix(week-view): activate cmd palette shortcut for Windows (#1324)
* :bug fix(week-view): activate cmd palette shortcut for Windows :recycle refactor(keyboard-events): enhance keyboard event handling * Update packages/web/src/common/utils/dom-events/event-emitter.util.ts Co-authored-by: Copilot <[email protected]> * :bug fix(week-view): correct function name for navigating to the next week --------- Co-authored-by: Copilot <[email protected]>
1 parent 386ae28 commit 8f15e86

File tree

3 files changed

+114
-104
lines changed

3 files changed

+114
-104
lines changed

packages/web/src/common/hooks/useKeyboardEvent.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,23 @@ export function useKeyboardEvent({
6464
);
6565

6666
const listenFilter = useCallback(
67-
({ event }: KeyCombination) =>
68-
listenWhileEditing ? true : !isEditable(event.target),
67+
({ event }: KeyCombination) => {
68+
const targetElement = event.target as HTMLElement;
69+
const activeElement = document.activeElement as HTMLElement;
70+
const activeElementEditable = isEditable(activeElement);
71+
const eventTargetEditable = isEditable(targetElement);
72+
const isInsideEditable = activeElementEditable || eventTargetEditable;
73+
74+
if (listenWhileEditing && isInsideEditable) {
75+
if (activeElement) {
76+
activeElement?.blur?.();
77+
} else if (targetElement) {
78+
targetElement?.blur?.();
79+
}
80+
}
81+
82+
return listenWhileEditing ? true : !isInsideEditable;
83+
},
6984
[listenWhileEditing],
7085
);
7186

@@ -75,13 +90,28 @@ export function useKeyboardEvent({
7590
return combination;
7691
}, []);
7792

93+
const resetSequence = useCallback((combination: KeyCombination) => {
94+
const { event, sequence } = combination;
95+
const metaKeys = ["Meta", "Control", "Alt", "Shift"];
96+
const nextSequence = sequence.filter((key) => metaKeys.includes(key));
97+
98+
if (nextSequence.length === 0) {
99+
keyPressed.next(null);
100+
} else {
101+
keyPressed.next({ event, sequence: nextSequence });
102+
}
103+
104+
return combination;
105+
}, []);
106+
78107
useEffect(() => {
79108
if (!handler) return;
80109

81110
const subscription = $event
82111
.pipe(filter(combinationFilter))
83112
.pipe(filter(listenFilter))
84113
.pipe(map(preventDefault))
114+
.pipe(map(resetSequence))
85115
.subscribe(handler);
86116

87117
return () => subscription.unsubscribe();
@@ -90,6 +120,7 @@ export function useKeyboardEvent({
90120
combinationFilter,
91121
listenFilter,
92122
preventDefault,
123+
resetSequence,
93124
handler,
94125
// eslint-disable-next-line react-hooks/exhaustive-deps
95126
...(deps ?? []),

packages/web/src/common/utils/dom-events/event-emitter.util.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,11 @@ export function globalOnKeyUpHandler(e: KeyboardEvent) {
4848
const { event, sequence = [] } = keyPressed.getValue() ?? {};
4949

5050
if (event) {
51-
const meta = sequence[0];
52-
const key = StringV4Schema.safeParse(e.key).data;
51+
const firstKeyInSequence = sequence[0];
52+
const releasedKey = StringV4Schema.safeParse(e.key).data;
53+
const firstKeyReleased = releasedKey === firstKeyInSequence;
5354

54-
if (key === meta) keyPressed.next(null);
55+
if (firstKeyReleased) keyPressed.next(null);
5556
}
5657

5758
keyReleased.next({ event: e, sequence });
Lines changed: 77 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
1-
import { useEffect } from "react";
2-
import { useHotkeys } from "react-hotkeys-hook";
3-
import { useNavigate } from "react-router-dom";
4-
import { Key } from "ts-keycode-enum";
1+
import { useCallback } from "react";
52
import {
63
SOMEDAY_MONTH_LIMIT_MSG,
74
SOMEDAY_WEEK_LIMIT_MSG,
85
} from "@core/constants/core.constants";
96
import { Categories_Event } from "@core/types/event.types";
107
import { Dayjs } from "@core/util/date/dayjs";
11-
import { ROOT_ROUTES } from "@web/common/constants/routes";
12-
import { ID_REMINDER_INPUT } from "@web/common/constants/web.constants";
8+
import {
9+
useKeyDownEvent,
10+
useKeyUpEvent,
11+
} from "@web/common/hooks/useKeyboardEvent";
1312
import {
1413
createAlldayDraft,
1514
createTimedDraft,
@@ -42,8 +41,6 @@ export interface ShortcutProps {
4241
}
4342

4443
export const useWeekShortcuts = ({
45-
today,
46-
dateCalcs,
4744
isCurrentWeek,
4845
startOfView,
4946
endOfView,
@@ -52,22 +49,16 @@ export const useWeekShortcuts = ({
5249
}: ShortcutProps) => {
5350
const dispatch = useAppDispatch();
5451
const context = useSidebarContext(true);
55-
const navigate = useNavigate();
5652

5753
const isAtMonthlyLimit = useAppSelector(selectIsAtMonthlyLimit);
5854
const isAtWeeklyLimit = useAppSelector(selectIsAtWeeklyLimit);
5955
const tab = useAppSelector(selectSidebarTab);
6056
const isSidebarOpen = useAppSelector(selectIsSidebarOpen);
57+
const { decrementWeek, incrementWeek, goToToday } = util;
58+
const { scrollToNow } = scrollUtil;
6159

62-
useHotkeys("shift+1", () => {
63-
dispatch(viewSlice.actions.updateSidebarTab("tasks"));
64-
});
65-
useHotkeys("shift+2", () => {
66-
dispatch(viewSlice.actions.updateSidebarTab("monthWidget"));
67-
});
68-
69-
useEffect(() => {
70-
const _createSomedayDraft = async (
60+
const _createSomedayDraft = useCallback(
61+
async (
7162
category: Categories_Event.SOMEDAY_WEEK | Categories_Event.SOMEDAY_MONTH,
7263
) => {
7364
if (category === Categories_Event.SOMEDAY_WEEK && isAtWeeklyLimit) {
@@ -89,86 +80,73 @@ export const useWeekShortcuts = ({
8980
if (tab !== "tasks") {
9081
dispatch(viewSlice.actions.updateSidebarTab("tasks"));
9182
}
92-
};
83+
},
84+
[context, isAtMonthlyLimit, isAtWeeklyLimit, isSidebarOpen, tab, dispatch],
85+
);
9386

94-
const _discardDraft = () => {
95-
if (isEventFormOpen()) {
96-
dispatch(draftSlice.actions.discard(undefined));
97-
}
98-
};
99-
100-
const keyDownHandler = (e: KeyboardEvent) => {
101-
// Prevent shortcuts from triggering unexpectedly
102-
if (isEventFormOpen()) return;
103-
104-
const isEditingHeader =
105-
document.getElementById(ID_REMINDER_INPUT) !== null;
106-
if (isEditingHeader) return;
107-
108-
const isCmdPaletteOpen =
109-
document.getElementById("headlessui-portal-root") !== null;
110-
if (isCmdPaletteOpen) return;
111-
112-
if (e.metaKey) return;
113-
114-
// map shortcuts to handler
115-
const handlersByKey = {
116-
[Key.OpenBracket]: () => dispatch(viewSlice.actions.toggleSidebar()),
117-
[Key.C]: () =>
118-
createTimedDraft(
119-
isCurrentWeek,
120-
startOfView,
121-
"createShortcut",
122-
dispatch,
123-
),
124-
[Key.A]: () => {
125-
createAlldayDraft(startOfView, endOfView, "createShortcut", dispatch);
126-
},
127-
[Key.T]: () => {
128-
scrollUtil.scrollToNow();
129-
_discardDraft();
130-
util.goToToday();
131-
},
132-
[Key.J]: () => {
133-
_discardDraft();
134-
util.decrementWeek();
135-
},
136-
[Key.K]: () => {
137-
_discardDraft();
138-
util.incrementWeek();
139-
},
140-
[Key.M]: () => _createSomedayDraft(Categories_Event.SOMEDAY_MONTH),
141-
[Key.W]: () => _createSomedayDraft(Categories_Event.SOMEDAY_WEEK),
142-
[Key.Z]: () => {
143-
navigate(ROOT_ROUTES.LOGOUT);
144-
},
145-
} as { [key: number]: () => void };
146-
147-
const handler = handlersByKey[e.which];
148-
if (!handler) return;
149-
150-
setTimeout(handler);
151-
};
152-
153-
document.addEventListener("keydown", keyDownHandler);
154-
155-
return () => {
156-
document.removeEventListener("keydown", keyDownHandler);
157-
};
158-
}, [
159-
dateCalcs,
160-
dispatch,
161-
today,
162-
isAtMonthlyLimit,
163-
isAtWeeklyLimit,
164-
isCurrentWeek,
165-
navigate,
166-
startOfView,
167-
endOfView,
168-
scrollUtil,
169-
util,
170-
tab,
171-
context,
172-
isSidebarOpen,
173-
]);
87+
const _discardDraft = useCallback(() => {
88+
if (isEventFormOpen()) {
89+
dispatch(draftSlice.actions.discard(undefined));
90+
}
91+
}, [dispatch]);
92+
93+
const openTasks = useCallback(() => {
94+
dispatch(viewSlice.actions.updateSidebarTab("tasks"));
95+
}, [dispatch]);
96+
97+
const openMonthWidget = useCallback(() => {
98+
dispatch(viewSlice.actions.updateSidebarTab("monthWidget"));
99+
}, [dispatch]);
100+
101+
const goToPreviousWeek = useCallback(() => {
102+
_discardDraft();
103+
decrementWeek();
104+
}, [decrementWeek, _discardDraft]);
105+
106+
const toToday = useCallback(() => {
107+
scrollToNow();
108+
_discardDraft();
109+
goToToday();
110+
}, [scrollToNow, _discardDraft, goToToday]);
111+
112+
const goToNextWeek = useCallback(() => {
113+
_discardDraft();
114+
incrementWeek();
115+
}, [incrementWeek, _discardDraft]);
116+
117+
const openSidebar = useCallback(
118+
() => dispatch(viewSlice.actions.toggleSidebar()),
119+
[dispatch],
120+
);
121+
122+
const createAllDayDraftEvent = useCallback(() => {
123+
createAlldayDraft(startOfView, endOfView, "createShortcut", dispatch);
124+
}, [dispatch, startOfView, endOfView]);
125+
126+
const createTimedDraftEvent = useCallback(
127+
() =>
128+
createTimedDraft(isCurrentWeek, startOfView, "createShortcut", dispatch),
129+
[isCurrentWeek, startOfView, dispatch],
130+
);
131+
132+
const createSomedayMonthDraft = useCallback(() => {
133+
_createSomedayDraft(Categories_Event.SOMEDAY_MONTH);
134+
}, [_createSomedayDraft]);
135+
136+
const createSomedayWeekDraft = useCallback(() => {
137+
_createSomedayDraft(Categories_Event.SOMEDAY_WEEK);
138+
}, [_createSomedayDraft]);
139+
140+
useKeyDownEvent({ combination: ["Shift", "1"], handler: openTasks });
141+
useKeyDownEvent({ combination: ["Shift", "2"], handler: openMonthWidget });
142+
useKeyDownEvent({ combination: ["Shift", "!"], handler: openTasks });
143+
useKeyDownEvent({ combination: ["Shift", "@"], handler: openMonthWidget });
144+
useKeyUpEvent({ combination: ["["], handler: openSidebar });
145+
useKeyUpEvent({ combination: ["j"], handler: goToPreviousWeek });
146+
useKeyUpEvent({ combination: ["k"], handler: goToNextWeek });
147+
useKeyUpEvent({ combination: ["t"], handler: toToday });
148+
useKeyUpEvent({ combination: ["a"], handler: createAllDayDraftEvent });
149+
useKeyUpEvent({ combination: ["c"], handler: createTimedDraftEvent });
150+
useKeyUpEvent({ combination: ["m"], handler: createSomedayMonthDraft });
151+
useKeyUpEvent({ combination: ["w"], handler: createSomedayWeekDraft });
174152
};

0 commit comments

Comments
 (0)