Skip to content

Commit f1dc121

Browse files
authored
Merge pull request #13124 from gitbutlerapp/push-lsowvtzstkst
Lite: keyboard shortcut fixes
2 parents cf038a9 + 4e138ef commit f1dc121

3 files changed

Lines changed: 95 additions & 36 deletions

File tree

apps/lite/ui/src/routes/project/$id/branches/route.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ const useSelectionKeyboardShortcuts = ({
8787

8888
const handleKeyDown = useEffectEvent((event: KeyboardEvent) => {
8989
if (event.defaultPrevented || event.repeat) return;
90-
if (event.metaKey || event.ctrlKey || event.altKey) return;
90+
if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) return;
9191
if (isTypingTarget(event.target)) return;
9292
if (selection?._tag !== "Commit") return;
9393

apps/lite/ui/src/routes/project/$id/workspace/-WorkspaceShortcuts.ts

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ import {
3131

3232
type SelectionAction =
3333
| { _tag: "Move"; offset: -1 | 1 }
34-
| { _tag: "NextSection" }
3534
| { _tag: "PreviousSection" }
35+
| { _tag: "NextSection" }
3636
| { _tag: "TogglePreview" }
3737
| { _tag: "OpenFullscreenPreview" };
3838

@@ -71,18 +71,18 @@ const selectionBindings: Array<ShortcutBinding<SelectionAction>> = [
7171
keys: ["ArrowDown", "j"],
7272
action: { _tag: "Move", offset: 1 },
7373
},
74-
{
75-
id: "next-section",
76-
description: "Next section",
77-
keys: ["J"],
78-
action: { _tag: "NextSection" },
79-
},
8074
{
8175
id: "previous-section",
8276
description: "Previous section",
83-
keys: ["K"],
77+
keys: ["Shift+ArrowUp", "Shift+k"],
8478
action: { _tag: "PreviousSection" },
8579
},
80+
{
81+
id: "next-section",
82+
description: "Next section",
83+
keys: ["Shift+ArrowDown", "Shift+j"],
84+
action: { _tag: "NextSection" },
85+
},
8686
togglePreviewBinding,
8787
openFullscreenPreviewBinding,
8888
];
@@ -193,17 +193,12 @@ export const handleCommitEditingMessageKeyDown = ({
193193
const action = getAction(commitEditingMessageBindings, event);
194194
if (!action) return;
195195

196+
event.preventDefault();
197+
196198
Match.value(action).pipe(
197199
Match.tagsExhaustive({
198-
Save: () => {
199-
if (event.shiftKey) return;
200-
event.preventDefault();
201-
onSave();
202-
},
203-
Cancel: () => {
204-
event.preventDefault();
205-
onCancel();
206-
},
200+
Save: onSave,
201+
Cancel: onCancel,
207202
}),
208203
);
209204
};
@@ -464,17 +459,17 @@ export const useWorkspaceShortcuts = ({
464459

465460
const move = (offset: -1 | 1, selection: Item) =>
466461
select(getAdjacentItem(navigationModel, selection, offset));
467-
const nextSection = (selection: Item) =>
468-
select(getAdjacentSection(navigationModel, selection, 1));
469462
const previousSection = (selection: Item) =>
470463
select(getParentSection(selection) ?? getAdjacentSection(navigationModel, selection, -1));
464+
const nextSection = (selection: Item) =>
465+
select(getAdjacentSection(navigationModel, selection, 1));
471466

472467
const handleSelectionAction = (action: SelectionAction, selection: Item) =>
473468
Match.value(action).pipe(
474469
Match.tagsExhaustive({
475470
Move: ({ offset }) => move(offset, selection),
476-
NextSection: () => nextSection(selection),
477471
PreviousSection: () => previousSection(selection),
472+
NextSection: () => nextSection(selection),
478473
TogglePreview: () => setShowPreviewPanel((visible) => !visible),
479474
OpenFullscreenPreview: () => setShowFullscreenPreview(true),
480475
}),
@@ -546,7 +541,6 @@ export const useWorkspaceShortcuts = ({
546541

547542
const handleKeyDown = useEffectEvent((event: KeyboardEvent) => {
548543
if (event.defaultPrevented) return;
549-
if (event.metaKey || event.ctrlKey || event.altKey) return;
550544
if (isTypingTarget(event.target)) return;
551545
if (isWithinBaseUiInert(event.target)) return;
552546

apps/lite/ui/src/shortcuts.ts

Lines changed: 79 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,33 +12,98 @@ export type ShortcutBinding<Action extends ShortcutActionBase> = {
1212
repeat?: boolean;
1313
};
1414

15+
const normalizeShortcutKey = (key: string): string => (key.length === 1 ? key.toLowerCase() : key);
16+
17+
type ParsedShortcutKey = {
18+
key: string;
19+
altKey: boolean;
20+
ctrlKey: boolean;
21+
metaKey: boolean;
22+
shiftKey: boolean;
23+
};
24+
25+
const parseShortcutKey = (shortcutKey: string): ParsedShortcutKey => {
26+
const parsed: ParsedShortcutKey = {
27+
key: "",
28+
altKey: false,
29+
ctrlKey: false,
30+
metaKey: false,
31+
shiftKey: false,
32+
};
33+
34+
for (const part of shortcutKey.split("+"))
35+
switch (part) {
36+
case "Alt":
37+
parsed.altKey = true;
38+
break;
39+
case "Ctrl":
40+
parsed.ctrlKey = true;
41+
break;
42+
case "Meta":
43+
parsed.metaKey = true;
44+
break;
45+
case "Shift":
46+
parsed.shiftKey = true;
47+
break;
48+
default:
49+
parsed.key = normalizeShortcutKey(part);
50+
}
51+
52+
return parsed;
53+
};
54+
55+
const eventMatchesShortcutKey = (event: KeyboardEvent, shortcutKey: string): boolean => {
56+
const parsed = parseShortcutKey(shortcutKey);
57+
if (parsed.key === "") return false;
58+
59+
return (
60+
normalizeShortcutKey(event.key) === parsed.key &&
61+
event.altKey === parsed.altKey &&
62+
event.ctrlKey === parsed.ctrlKey &&
63+
event.metaKey === parsed.metaKey &&
64+
event.shiftKey === parsed.shiftKey
65+
);
66+
};
67+
1568
export const getAction = <Action extends ShortcutActionBase>(
1669
bindings: Array<ShortcutBinding<Action>>,
1770
event: KeyboardEvent,
1871
): Action | null => {
1972
for (const binding of bindings) {
20-
if (!binding.keys.includes(event.key)) continue;
73+
if (!binding.keys.some((shortcutKey) => eventMatchesShortcutKey(event, shortcutKey))) continue;
2174
if (binding.repeat === false && event.repeat) continue;
2275
return binding.action;
2376
}
2477

2578
return null;
2679
};
2780

81+
const formatShortcutKey = (key: string): string => {
82+
const parsed = parseShortcutKey(key);
83+
if (parsed.key === "") return key;
84+
85+
const modifiers = [
86+
parsed.ctrlKey ? "ctrl" : null,
87+
parsed.altKey ? "alt" : null,
88+
parsed.shiftKey ? "shift" : null,
89+
parsed.metaKey ? "meta" : null,
90+
].filter((modifier) => modifier !== null);
91+
92+
const formattedKey = Match.value(parsed.key).pipe(
93+
Match.when("ArrowUp", () => "↑"),
94+
Match.when("ArrowDown", () => "↓"),
95+
Match.when("ArrowLeft", () => "←"),
96+
Match.when("ArrowRight", () => "→"),
97+
Match.when("Escape", () => "esc"),
98+
Match.when("Enter", () => "enter"),
99+
Match.orElse(() => parsed.key),
100+
);
101+
102+
return [...modifiers, formattedKey].join("+");
103+
};
104+
28105
export const formatShortcutKeys = (keys: Array<string>): string =>
29-
keys
30-
.map((key) =>
31-
Match.value(key).pipe(
32-
Match.when("ArrowUp", () => "↑"),
33-
Match.when("ArrowDown", () => "↓"),
34-
Match.when("ArrowLeft", () => "←"),
35-
Match.when("ArrowRight", () => "→"),
36-
Match.when("Escape", () => "esc"),
37-
Match.when("Enter", () => "enter"),
38-
Match.orElse(() => key),
39-
),
40-
)
41-
.join("/");
106+
keys.map((key) => formatShortcutKey(key)).join("/");
42107

43108
export const bindingButtonLabel = (binding: ShortcutBinding<ShortcutActionBase>): string =>
44109
`${binding.description} (${formatShortcutKeys(binding.keys)})`;

0 commit comments

Comments
 (0)