From 99f994f4e5cdb7b198a57c346320d7795d2ca3bc Mon Sep 17 00:00:00 2001 From: isaacclad Date: Tue, 23 Jun 2026 02:51:39 +0000 Subject: [PATCH] Add slash keyboard shortcut to focus search. Press / from any non-input page to jump to the topbar or full-screen search field, with a fallback route when search is not visible. Co-authored-by: Cursor --- resources/js/app.js | 2 + resources/js/ladill-search-shortcut.js | 97 +++++++++++++++++++ resources/views/layouts/hosting.blade.php | 2 +- .../views/partials/search-screen.blade.php | 4 +- resources/views/partials/topbar.blade.php | 2 +- 5 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 resources/js/ladill-search-shortcut.js diff --git a/resources/js/app.js b/resources/js/app.js index f9f7f99..f65cb5a 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -1,8 +1,10 @@ import './bootstrap'; import { registerLadillConfirmStore, registerLadillModalHelpers } from './ladill-modals'; +import { registerLadillSearchShortcut } from './ladill-search-shortcut'; registerLadillModalHelpers(); +registerLadillSearchShortcut(); import Alpine from 'alpinejs'; diff --git a/resources/js/ladill-search-shortcut.js b/resources/js/ladill-search-shortcut.js new file mode 100644 index 0000000..5ea893d --- /dev/null +++ b/resources/js/ladill-search-shortcut.js @@ -0,0 +1,97 @@ +function isEditableTarget(element) { + if (!(element instanceof HTMLElement)) { + return false; + } + + const tag = element.tagName; + + if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') { + return true; + } + + return element.isContentEditable; +} + +function isVisibleInput(input) { + if (!(input instanceof HTMLElement)) { + return false; + } + + const style = window.getComputedStyle(input); + + return style.display !== 'none' + && style.visibility !== 'hidden' + && style.opacity !== '0'; +} + +function findSearchInput() { + const marked = [...document.querySelectorAll('[data-ladill-search-input]')]; + const visibleMarked = marked.find(isVisibleInput); + + if (visibleMarked) { + return visibleMarked; + } + + if (marked.length > 0) { + return marked[0]; + } + + return document.querySelector('[x-data*="topbarSearch"] input'); +} + +function focusSearchInput() { + const input = findSearchInput(); + + if (!input) { + const fallbackUrl = document.body?.dataset?.ladillSearchUrl; + + if (fallbackUrl) { + window.location.href = fallbackUrl; + } + + return false; + } + + if (!isVisibleInput(input)) { + const fallbackUrl = document.body?.dataset?.ladillSearchUrl; + + if (fallbackUrl) { + window.location.href = fallbackUrl; + + return true; + } + } + + input.focus(); + + if (input instanceof HTMLInputElement && input.type === 'text') { + input.select(); + } + + return true; +} + +export function registerLadillSearchShortcut() { + if (window.__ladillSearchShortcutRegistered) { + return; + } + + window.__ladillSearchShortcutRegistered = true; + + document.addEventListener('keydown', (event) => { + if (event.key !== '/' && event.code !== 'Slash') { + return; + } + + if (event.ctrlKey || event.metaKey || event.altKey) { + return; + } + + if (isEditableTarget(document.activeElement)) { + return; + } + + event.preventDefault(); + focusSearchInput(); + }); +} diff --git a/resources/views/layouts/hosting.blade.php b/resources/views/layouts/hosting.blade.php index e12b698..2d0f9ac 100644 --- a/resources/views/layouts/hosting.blade.php +++ b/resources/views/layouts/hosting.blade.php @@ -10,7 +10,7 @@ @vite(['resources/css/app.css', 'resources/js/app.js']) - +