Skip to content

Commit

Permalink
fix: Fixes hotkeys on radix select, dropdown etc (#4701)
Browse files Browse the repository at this point in the history
closes #4550

## Description

1. Detect radix dropdown,select and co as part of the form controls that
also implement keyboard shortcuts and don't use global shortcuts on
these
2. Simplify shortcut settings by having just one setting that works for
everything: form tags, content editable, radix

## Steps for reproduction

1. click button
3. expect xyz

## Code Review

- [ ] hi @kof, I need you to do
  - conceptual review (architecture, feature-correctness)
  - detailed review (read every line)
  - test it on preview

## Before requesting a review

- [ ] made a self-review
- [ ] added inline comments where things may be not obvious (the "why",
not "what")

## Before merging

- [ ] tested locally and on preview environment (preview dev login:
0000)
- [ ] updated [test
cases](https://github.com/webstudio-is/webstudio/blob/main/apps/builder/docs/test-cases.md)
document
- [ ] added tests
- [ ] if any new env variables are added, added them to `.env` file
  • Loading branch information
kof authored Jan 3, 2025
1 parent be69438 commit 42f5760
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 47 deletions.
33 changes: 11 additions & 22 deletions apps/builder/app/builder/shared/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ const makeBreakpointCommand = <CommandName extends string>(
name,
hidden: true,
defaultHotkeys: [`${number}`],
disableHotkeyOnFormTags: true,
disableHotkeyOnContentEditable: true,
disableOnInputLikeControls: true,
handler: () => {
selectBreakpointByOrder(number);
},
Expand Down Expand Up @@ -299,17 +298,15 @@ export const { emitCommand, subscribeCommands } = createCommandsEmitter({
handler: () => {
$publishDialog.set("publish");
},
disableHotkeyOnFormTags: true,
disableHotkeyOnContentEditable: true,
disableOnInputLikeControls: true,
},
{
name: "openExportDialog",
defaultHotkeys: ["shift+E"],
handler: () => {
$publishDialog.set("export");
},
disableHotkeyOnFormTags: true,
disableHotkeyOnContentEditable: true,
disableOnInputLikeControls: true,
},
{
name: "toggleComponentsPanel",
Expand All @@ -323,17 +320,15 @@ export const { emitCommand, subscribeCommands } = createCommandsEmitter({
}
toggleActiveSidebarPanel("components");
},
disableHotkeyOnFormTags: true,
disableHotkeyOnContentEditable: true,
disableOnInputLikeControls: true,
},
{
name: "toggleNavigatorPanel",
defaultHotkeys: ["z"],
handler: () => {
toggleActiveSidebarPanel("navigator");
},
disableHotkeyOnFormTags: true,
disableHotkeyOnContentEditable: true,
disableOnInputLikeControls: true,
},
{
name: "openStylePanel",
Expand All @@ -347,17 +342,15 @@ export const { emitCommand, subscribeCommands } = createCommandsEmitter({
}
$activeInspectorPanel.set("style");
},
disableHotkeyOnFormTags: true,
disableHotkeyOnContentEditable: true,
disableOnInputLikeControls: true,
},
{
name: "openSettingsPanel",
defaultHotkeys: ["d"],
handler: () => {
$activeInspectorPanel.set("settings");
},
disableHotkeyOnFormTags: true,
disableHotkeyOnContentEditable: true,
disableOnInputLikeControls: true,
},
makeBreakpointCommand("selectBreakpoint1", 1),
makeBreakpointCommand("selectBreakpoint2", 2),
Expand All @@ -373,10 +366,9 @@ export const { emitCommand, subscribeCommands } = createCommandsEmitter({
{
name: "toggleAiCommandBar",
defaultHotkeys: ["space"],
disableHotkeyOnContentEditable: true,
// this disables hotkey for inputs on style panel
// but still work for input on canvas which call event.preventDefault() in keydown handler
disableHotkeyOnFormTags: true,
disableOnInputLikeControls: true,
handler: () => {
$isAiCommandBarVisible.set($isAiCommandBarVisible.get() === false);
},
Expand All @@ -388,10 +380,9 @@ export const { emitCommand, subscribeCommands } = createCommandsEmitter({
{
name: "deleteInstance",
defaultHotkeys: ["backspace", "delete"],
disableHotkeyOnContentEditable: true,
// this disables hotkey for inputs on style panel
// but still work for input on canvas which call event.preventDefault() in keydown handler
disableHotkeyOnFormTags: true,
disableOnInputLikeControls: true,
handler: deleteSelectedInstance,
},
{
Expand Down Expand Up @@ -479,8 +470,7 @@ export const { emitCommand, subscribeCommands } = createCommandsEmitter({
name: "undo",
// safari use cmd+z to reopen closed tabs, here added ctrl as alternative
defaultHotkeys: ["meta+z", "ctrl+z"],
disableHotkeyOnContentEditable: true,
disableHotkeyOnFormTags: true,
disableOnInputLikeControls: true,
handler: () => {
serverSyncStore.undo();
},
Expand All @@ -489,8 +479,7 @@ export const { emitCommand, subscribeCommands } = createCommandsEmitter({
name: "redo",
// safari use cmd+z to reopen closed tabs, here added ctrl as alternative
defaultHotkeys: ["meta+shift+z", "ctrl+shift+z"],
disableHotkeyOnContentEditable: true,
disableHotkeyOnFormTags: true,
disableOnInputLikeControls: true,
handler: () => {
serverSyncStore.redo();
},
Expand Down
4 changes: 2 additions & 2 deletions apps/builder/app/canvas/shared/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const { emitCommand, subscribeCommands } = createCommandsEmitter({
name: "editInstanceText",
hidden: true,
defaultHotkeys: ["enter"],
disableHotkeyOnContentEditable: true,
disableOnInputLikeControls: true,
// builder invokes command with custom hotkey setup
disableHotkeyOutsideApp: true,
handler: () => {
Expand Down Expand Up @@ -94,7 +94,7 @@ export const { emitCommand, subscribeCommands } = createCommandsEmitter({
name: "escapeSelection",
hidden: true,
defaultHotkeys: ["escape"],
disableHotkeyOnContentEditable: true,
disableOnInputLikeControls: true,
// reset selection for canvas, but not for the builder
disableHotkeyOutsideApp: true,
handler: () => {
Expand Down
37 changes: 14 additions & 23 deletions apps/builder/app/shared/commands-emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,10 @@ type CommandMeta<CommandName extends string> = {
/** listen hotkeys only locally without sharing with other apps */
disableHotkeyOutsideApp?: boolean;
/**
* input, select and textarea will not invoke command when hotkey is hit
* input, select and textarea, content editable and role=option used in Radix will not invoke command when hotkey is hit
* with the exception when default event behavior is prevented
**/
disableHotkeyOnFormTags?: boolean;
/**
* element with contenteditable=true will not invoke command
* when hotkey is hit with the exception when default
* event behavior is prevented
**/
disableHotkeyOnContentEditable?: boolean;
disableOnInputLikeControls?: boolean;
/**
* hide the command in cmd+k panel
*/
Expand Down Expand Up @@ -155,30 +149,27 @@ export const createCommandsEmitter = <CommandName extends string>({
continue;
}
const element = event.target as HTMLElement;
const tagName = element.tagName.toLowerCase();
const isOnFormTags = ["input", "select", "textarea"].includes(tagName);
const isOnContentEditable = element.isContentEditable;
const { disableHotkeyOnFormTags, disableHotkeyOnContentEditable } =
commandMeta;
const isOnInputLikeControl =
["input", "select", "textarea"].includes(
element.tagName.toLowerCase()
) ||
element.isContentEditable ||
// Detect Radix select, dropdown and co.
element.getAttribute("role") === "option";
const { disableOnInputLikeControls } = commandMeta;

// in some cases hotkey override default behavior
// on form tags and contentEditable
// though still proceed when default behavior is prevented
// this hack makes hotkeys work on canvas instances of input etc.
if (
isOnFormTags &&
disableHotkeyOnFormTags &&
isOnInputLikeControl &&
disableOnInputLikeControls &&
event.defaultPrevented === false
) {
continue;
}
if (
isOnContentEditable &&
disableHotkeyOnContentEditable
// editors usually manage history in controlled way
// so do not check if event is prevented
) {
continue;
}

emitted = true;
if (commandMeta.preventDefault === false) {
preventDefault = false;
Expand Down

0 comments on commit 42f5760

Please sign in to comment.