diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentDropdownMenu.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentDropdownMenu.svelte index d141a7885e..aeaa577455 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentDropdownMenu.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentDropdownMenu.svelte @@ -9,14 +9,15 @@ $: isBlock = definition?.block === true const keyboardEvent = (key, ctrlKey = false) => { - // Ensure this component is selected first - if (component._id !== $store.selectedComponentId) { - store.update(state => { - state.selectedComponentId = component._id - return state + document.dispatchEvent( + new CustomEvent("component-menu", { + detail: { + key, + ctrlKey, + id: component?._id, + }, }) - } - document.dispatchEvent(new KeyboardEvent("keydown", { key, ctrlKey })) + ) } diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentListPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentListPanel.svelte index 488f7fe587..86b71b9785 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentListPanel.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentListPanel.svelte @@ -7,14 +7,66 @@ import NavItem from "components/common/NavItem.svelte" import ScreenslotDropdownMenu from "./ScreenslotDropdownMenu.svelte" import { setContext, onMount } from "svelte" - import { get } from "svelte/store" import DNDPositionIndicator from "./DNDPositionIndicator.svelte" import { DropPosition } from "./dndStore" import ConfirmDialog from "components/common/ConfirmDialog.svelte" import { notifications, Button } from "@budibase/bbui" + import { findComponent } from "builderStore/componentUtils" let scrollRef let confirmDeleteDialog + let confirmEjectDialog + let componentToDelete + let componentToEject + + const keyHandlers = { + ["^ArrowUp"]: async component => { + await store.actions.components.moveUp(component) + }, + ["^ArrowDown"]: async component => { + await store.actions.components.moveDown(component) + }, + ["^c"]: component => { + store.actions.components.copy(component, false) + }, + ["^x"]: component => { + store.actions.components.copy(component, true) + }, + ["^v"]: async component => { + await store.actions.components.paste(component, "inside") + }, + ["^d"]: async component => { + store.actions.components.copy(component) + await store.actions.components.paste(component, "below") + }, + ["^e"]: component => { + componentToEject = component + confirmEjectDialog.show() + }, + ["^Enter"]: () => { + $goto("./new") + }, + ["Delete"]: component => { + // Don't show confirmation for the screen itself + if (component?._id === $selectedScreen.props._id) { + return false + } + componentToDelete = component + confirmDeleteDialog.show() + }, + ["ArrowUp"]: () => { + store.actions.components.selectPrevious() + }, + ["ArrowDown"]: () => { + store.actions.components.selectNext() + }, + ["Escape"]: () => { + if (!$isActive("/new")) { + return false + } + $goto("./") + }, + } const scrollTo = bounds => { if (!bounds) { @@ -59,6 +111,11 @@ }) } + // Set scroll context so components can invoke scrolling when selected + setContext("scroll", { + scrollTo, + }) + const onDrop = async () => { try { await dndStore.actions.drop() @@ -68,13 +125,28 @@ } } - // Set scroll context so components can invoke scrolling when selected - setContext("scroll", { - scrollTo, - }) - - const deleteComponent = async () => { - await store.actions.components.delete(get(selectedComponent)) + const handleKeyAction = async (component, key, ctrlKey = false) => { + if (!component || !key) { + return false + } + try { + // Delete and backspace are the same + if (key === "Backspace") { + key = "Delete" + } + // Prefix key with a caret for ctrl modifier + if (ctrlKey) { + key = "^" + key + } + const handler = keyHandlers[key] + if (!handler) { + return false + } + return handler(component) + } catch (error) { + console.log(error) + notifications.error("Error handling key press") + } } const handleKeyPress = async e => { @@ -87,62 +159,23 @@ if (["input", "textarea"].indexOf(activeTag) !== -1 && e.key !== "Escape") { return } - const component = get(selectedComponent) - try { - if (e.ctrlKey || e.metaKey) { - if (e.key === "ArrowUp") { - e.preventDefault() - await store.actions.components.moveUp(component) - } else if (e.key === "ArrowDown") { - e.preventDefault() - await store.actions.components.moveDown(component) - } else if (e.key === "c") { - e.preventDefault() - await store.actions.components.copy(component, false) - } else if (e.key === "x") { - e.preventDefault() - store.actions.components.copy(component, true) - } else if (e.key === "v") { - e.preventDefault() - await store.actions.components.paste(component, "inside") - } else if (e.key === "d") { - e.preventDefault() - await store.actions.components.copy(component) - await store.actions.components.paste(component, "below") - } else if (e.key === "Enter") { - e.preventDefault() - $goto("./new") - } else if (e.key === "e") { - e.preventDefault() - await store.actions.components.requestEjectBlock(component?._id) - } - } else if (e.key === "Backspace" || e.key === "Delete") { - // Don't show confirmation for the screen itself - if (component._id === get(selectedScreen).props._id) { - return - } - e.preventDefault() - confirmDeleteDialog.show() - } else if (e.key === "ArrowUp") { - e.preventDefault() - await store.actions.components.selectPrevious() - } else if (e.key === "ArrowDown") { - e.preventDefault() - await store.actions.components.selectNext() - } else if (e.key === "Escape" && $isActive("./new")) { - e.preventDefault() - $goto("./") - } - } catch (error) { - console.log(error) - notifications.error("Error handling key press") - } + // Key events are always for the selected component + return handleKeyAction($selectedComponent, e.key, e.ctrlKey || e.metaKey) + } + + const handleComponentMenu = async e => { + // Menu events can be for any component + const { id, key, ctrlKey } = e.detail + const component = findComponent($selectedScreen.props, id) + return await handleKeyAction(component, key, ctrlKey) } onMount(() => { document.addEventListener("keydown", handleKeyPress) + document.addEventListener("component-menu", handleComponentMenu) return () => { document.removeEventListener("keydown", handleKeyPress) + document.removeEventListener("component-menu", handleComponentMenu) } }) @@ -195,9 +228,16 @@ store.actions.components.delete(componentToDelete)} +/> + store.actions.components.requestEjectBlock(componentToEject?._id)} + okText="Eject block" />