diff --git a/frontend/src/components/CommandPalette.css b/frontend/src/components/CommandPalette.css index 8df5128..c8e493a 100644 --- a/frontend/src/components/CommandPalette.css +++ b/frontend/src/components/CommandPalette.css @@ -21,6 +21,22 @@ .command-palette-search-icon { color: #71717a; flex-shrink: 0; + /* Explicit drag handle for the panel. Wails native window drag does not + support NSPanel yet, so dragging is implemented in JS (mousedown/move + -> spotlight:drag events) and applied via SetPosition on the backend. + The grab cursor makes the handle discoverable. */ + cursor: grab; +} + +/* SVG child elements (circle/line) have zero clientWidth/clientHeight, which + breaks offset-based hit checks. Route all pointer events to the svg root + so the drag handle behaves consistently. */ +.command-palette-search-icon * { + pointer-events: none; +} + +.command-palette-search-icon:active { + cursor: grabbing; } .command-palette-input { @@ -59,7 +75,7 @@ } .command-palette-results { - height: 264px; /* Results area: 80% of original (332 * 0.8) */ + height: auto; /* Grows with the window; the JS backend drives window height */ overflow: hidden; background: #18181b; } @@ -73,7 +89,7 @@ .command-palette-list { overflow-y: auto; - max-height: 252px; /* 80% of original (320 * 0.8) */ + max-height: calc(100vh - 49px); /* Scroll within the window; 49px = search box */ } .command-palette-item { diff --git a/frontend/src/components/CommandPalette.jsx b/frontend/src/components/CommandPalette.jsx index be4fc3e..b1ef2f5 100644 --- a/frontend/src/components/CommandPalette.jsx +++ b/frontend/src/components/CommandPalette.jsx @@ -212,6 +212,49 @@ export function CommandPalette() { }); const inputRef = useRef(null); const listRef = useRef(null); + const dragRef = useRef(null); // { startX, startY } in screen coords + + // Custom panel dragging: Wails native window drag doesn't support NSPanel, + // so we track the pointer in JS and let the backend move the window. + const handleDragMouseDown = useCallback((e) => { + if (e.button !== 0) return; + dragRef.current = { startX: e.screenX, startY: e.screenY }; + try { + Events.Emit('spotlight:drag:start'); + } catch (err) { + console.error('Failed to emit spotlight:drag:start', err); + } + }, []); + + useEffect(() => { + const onMouseMove = (e) => { + if (!dragRef.current) return; + const { startX, startY } = dragRef.current; + try { + Events.Emit('spotlight:drag', { + dx: e.screenX - startX, + dy: e.screenY - startY, + }); + } catch (err) { + console.error('Failed to emit spotlight:drag', err); + } + }; + const onMouseUp = () => { + if (!dragRef.current) return; + dragRef.current = null; + try { + Events.Emit('spotlight:drag:end'); + } catch (err) { + console.error('Failed to emit spotlight:drag:end', err); + } + }; + window.addEventListener('mousemove', onMouseMove); + window.addEventListener('mouseup', onMouseUp); + return () => { + window.removeEventListener('mousemove', onMouseMove); + window.removeEventListener('mouseup', onMouseUp); + }; + }, []); // Calculate fuzzy match score const fuzzyScore = (target, query) => { @@ -251,7 +294,7 @@ export function CommandPalette() { // Listen for command palette opened event useEffect(() => { - const unsubscribe = window.runtime?.EventsOn?.('command-palette:opened', () => { + const unsubscribe = window.runtime?.EventsOn?.('spotlight:opened', () => { setSearchQuery(''); setSelectedIndex(0); setTimeout(() => inputRef.current?.focus(), 100); @@ -261,6 +304,20 @@ export function CommandPalette() { }; }, []); + // Resize the spotlight window to fit the visible results (Spotlight-like + // dynamic height). The backend listens for spotlight:resize and resizes + // the NSPanel while keeping it centered. + useEffect(() => { + const itemCount = commands.length; + const shown = Math.min(itemCount, 12); + const height = Math.max(100, Math.min(600, 49 + shown * 37 + 2)); + try { + Events.Emit('spotlight:resize', height); + } catch (err) { + console.error('Failed to emit spotlight:resize', err); + } + }, [commands.length]); + // Save recent command const saveRecentCommand = useCallback((commandId) => { setRecentCommands((prev) => { @@ -323,9 +380,9 @@ export function CommandPalette() { e.preventDefault(); console.log('[CommandPalette] Escape pressed, closing...'); try { - Events.Emit('command-palette:close'); + Events.Emit('spotlight:close'); } catch (err) { - console.error('Failed to emit command-palette:close', err); + console.error('Failed to emit spotlight:close', err); } } }, @@ -343,7 +400,11 @@ export function CommandPalette() { return (