Unverified 提交 6ee1a931 authored 作者: Mohamed Aziz Mejri's avatar Mohamed Aziz Mejri 提交者: GitHub

Component selection shortcut (#1139)

This PR introduces a new keyboard shortcut to improve the efficiency of selecting components in the app. Users can now quickly select components using Meta + Shift + C for Mac and Ctrl + Shift + C for Other devices (Windows/Linux) <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Add a shortcut to quickly activate the component selector from the preview. Use Meta+Shift+C on macOS and Ctrl+Shift+C on Windows/Linux. - **New Features** - Added useShortcut hook to handle key combos and prevent default on match. - Wired shortcut in PreviewIframe with OS detection for Meta vs Ctrl. - Forwarded keydown events from the iframe to the parent via postMessage (dyad-shortcut-triggered) so the shortcut works inside preview content. <!-- End of auto-generated description by cubic. -->
上级 207f3fc3
......@@ -40,6 +40,7 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
import { useRunApp } from "@/hooks/useRunApp";
import { useShortcut } from "@/hooks/useShortcut";
interface ErrorBannerProps {
error: string | undefined;
......@@ -149,6 +150,9 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
const iframeRef = useRef<HTMLIFrameElement>(null);
const [isPicking, setIsPicking] = useState(false);
//detect if the user is using Mac
const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0;
// Deactivate component selector when selection is cleared
useEffect(() => {
if (!selectedComponentPreview) {
......@@ -301,6 +305,15 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
}
};
// Activate component selector using a shortcut
useShortcut(
"c",
{ shift: true, ctrl: !isMac, meta: isMac },
handleActivateComponentSelector,
isComponentSelectorInitialized,
iframeRef,
);
// Function to navigate back
const handleNavigateBack = () => {
if (canGoBack && iframeRef.current?.contentWindow) {
......@@ -433,6 +446,7 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
? "Deactivate component selector"
: "Select component"}
</p>
<p>{isMac ? "⌘ + ⇧ + C" : "Ctrl + ⇧ + C"}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
......
import { useEffect } from "react";
export function useShortcut(
key: string,
modifiers: { ctrl?: boolean; shift?: boolean; meta?: boolean },
callback: () => void,
isComponentSelectorInitialized: boolean,
iframeRef?: React.RefObject<HTMLIFrameElement | null>,
): void {
useEffect(() => {
const isModifierActive = (
modKey: boolean | undefined,
eventKey: boolean,
) => (modKey ? eventKey : true);
const validateShortcut = (
eventKey: string,
eventModifiers: { ctrl?: boolean; shift?: boolean; meta?: boolean },
) => {
const keyMatches = eventKey === key.toLowerCase();
const ctrlMatches = isModifierActive(
modifiers.ctrl,
eventModifiers.ctrl || false,
);
const shiftMatches = isModifierActive(
modifiers.shift,
eventModifiers.shift || false,
);
const metaMatches = isModifierActive(
modifiers.meta,
eventModifiers.meta || false,
);
if (
keyMatches &&
ctrlMatches &&
shiftMatches &&
metaMatches &&
isComponentSelectorInitialized
) {
callback();
return true;
}
return false;
};
const handleKeyDown = (event: KeyboardEvent) => {
if (
validateShortcut(event.key.toLowerCase(), {
ctrl: event.ctrlKey,
shift: event.shiftKey,
meta: event.metaKey,
})
) {
event.preventDefault();
}
};
const handleMessageEvent = (event: MessageEvent) => {
// Only handle messages from our iframe
if (event.source !== iframeRef?.current?.contentWindow) {
return;
}
if (event.data?.type === "dyad-select-component-shortcut") {
if (isComponentSelectorInitialized) {
callback();
}
}
};
window.addEventListener("keydown", handleKeyDown);
window.addEventListener("message", handleMessageEvent);
return () => {
window.removeEventListener("keydown", handleKeyDown);
window.removeEventListener("message", handleMessageEvent);
};
}, [key, modifiers, callback, isComponentSelectorInitialized, iframeRef]);
}
......@@ -2,6 +2,9 @@
const OVERLAY_ID = "__dyad_overlay__";
let overlay, label;
//detect if the user is using Mac
const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0;
// The possible states are:
// { type: 'inactive' }
// { type: 'inspecting', element: ?HTMLElement }
......@@ -146,6 +149,31 @@
);
}
function onKeyDown(e) {
// Ignore keystrokes if the user is typing in an input field, textarea, or editable element
if (
e.target.tagName === "INPUT" ||
e.target.tagName === "TEXTAREA" ||
e.target.isContentEditable
) {
return;
}
// Forward shortcuts to parent window
const key = e.key.toLowerCase();
const hasShift = e.shiftKey;
const hasCtrlOrMeta = isMac ? e.metaKey : e.ctrlKey;
if (key === "c" && hasShift && hasCtrlOrMeta) {
e.preventDefault();
window.parent.postMessage(
{
type: "dyad-select-component-shortcut",
},
"*",
);
}
}
/* ---------- activation / deactivation --------------------------------- */
function activate() {
if (state.type === "inactive") {
......@@ -178,6 +206,9 @@
if (e.data.type === "deactivate-dyad-component-selector") deactivate();
});
// Always listen for keyboard shortcuts
window.addEventListener("keydown", onKeyDown, true);
function initializeComponentSelector() {
if (!document.body) {
console.error(
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论