Skip to content

Commit 8dc7254

Browse files
shamilkottaserhalp
andauthored
fix: ignore shortcuts on text input (#547)
Co-authored-by: Philippe Serhal <philippe.serhal@gmail.com>
1 parent 21682bb commit 8dc7254

4 files changed

Lines changed: 38 additions & 23 deletions

File tree

app/app.vue

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script setup lang="ts">
22
import type { Directions } from '@nuxtjs/i18n'
33
import { useEventListener } from '@vueuse/core'
4+
import { isEditableElement } from '~/utils/input'
45
56
const route = useRoute()
67
const router = useRouter()
@@ -39,12 +40,7 @@ if (import.meta.server) {
3940
// "/" focuses search or navigates to search page
4041
// "?" highlights all keyboard shortcut elements
4142
function handleGlobalKeydown(e: KeyboardEvent) {
42-
const target = e.target as HTMLElement
43-
44-
const isEditableTarget =
45-
target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable
46-
47-
if (isEditableTarget) return
43+
if (isEditableElement(e.target)) return
4844
4945
if (e.key === '/') {
5046
e.preventDefault()

app/components/AppHeader.vue

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
<script setup lang="ts">
2+
import { isEditableElement } from '~/utils/input'
3+
24
withDefaults(
35
defineProps<{
46
showLogo?: boolean
@@ -62,11 +64,7 @@ function handleSearchFocus() {
6264
onKeyStroke(
6365
',',
6466
e => {
65-
// Don't trigger if user is typing in an input
66-
const target = e.target as HTMLElement
67-
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
68-
return
69-
}
67+
if (isEditableElement(e.target)) return
7068
7169
e.preventDefault()
7270
navigateTo('/settings')
@@ -79,12 +77,7 @@ onKeyStroke(
7977
e => {
8078
// Allow more specific handlers to take precedence
8179
if (e.defaultPrevented) return
82-
83-
// Don't trigger if user is typing in an input
84-
const target = e.target as HTMLElement
85-
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
86-
return
87-
}
80+
if (isEditableElement(e.target)) return
8881
8982
e.preventDefault()
9083
navigateTo('/compare')

app/pages/[...package].vue

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { JsrPackageInfo } from '#shared/types/jsr'
44
import { assertValidPackageName } from '#shared/utils/npm'
55
import { joinURL } from 'ufo'
66
import { areUrlsEquivalent } from '#shared/utils/url'
7+
import { isEditableElement } from '~/utils/input'
78
89
definePageMeta({
910
name: 'package',
@@ -113,7 +114,10 @@ const deprecationNotice = computed(() => {
113114
114115
// If latest is deprecated, show "package deprecated"
115116
if (isLatestDeprecated) {
116-
return { type: 'package' as const, message: displayVersion.value.deprecated }
117+
return {
118+
type: 'package' as const,
119+
message: displayVersion.value.deprecated,
120+
}
117121
}
118122
119123
// Otherwise show "version deprecated"
@@ -215,7 +219,9 @@ const docsLink = computed(() => {
215219
216220
return {
217221
name: 'docs' as const,
218-
params: { path: [...pkg.value!.name.split('/'), 'v', displayVersion.value.version] },
222+
params: {
223+
path: [...pkg.value!.name.split('/'), 'v', displayVersion.value.version],
224+
},
219225
}
220226
})
221227
@@ -314,6 +320,7 @@ useSeoMeta({
314320
onKeyStroke(
315321
'.',
316322
e => {
323+
if (isEditableElement(e.target)) return
317324
if (pkg.value && displayVersion.value) {
318325
e.preventDefault()
319326
navigateTo({
@@ -330,6 +337,7 @@ onKeyStroke(
330337
onKeyStroke(
331338
'd',
332339
e => {
340+
if (isEditableElement(e.target)) return
333341
if (docsLink.value) {
334342
e.preventDefault()
335343
navigateTo(docsLink.value)
@@ -339,6 +347,7 @@ onKeyStroke(
339347
)
340348
341349
onKeyStroke('c', e => {
350+
if (isEditableElement(e.target)) return
342351
if (pkg.value) {
343352
e.preventDefault()
344353
router.push({ path: '/compare', query: { packages: pkg.value.name } })
@@ -487,7 +496,9 @@ function handleClick(event: MouseEvent) {
487496
<NuxtLink
488497
:to="{
489498
name: 'code',
490-
params: { path: [...pkg.name.split('/'), 'v', displayVersion.version] },
499+
params: {
500+
path: [...pkg.name.split('/'), 'v', displayVersion.version],
501+
},
491502
}"
492503
class="px-2 py-1.5 font-mono text-xs rounded transition-colors duration-150 border border-transparent text-fg-subtle hover:text-fg hover:bg-bg hover:shadow hover:border-border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 inline-flex items-center gap-1.5"
493504
aria-keyshortcuts="."
@@ -637,7 +648,9 @@ function handleClick(event: MouseEvent) {
637648
<NuxtLink
638649
:to="{
639650
name: 'code',
640-
params: { path: [...pkg.name.split('/'), 'v', displayVersion.version] },
651+
params: {
652+
path: [...pkg.name.split('/'), 'v', displayVersion.version],
653+
},
641654
}"
642655
class="link-subtle font-mono text-sm inline-flex items-center gap-1.5"
643656
>
@@ -671,7 +684,9 @@ function handleClick(event: MouseEvent) {
671684
<p v-if="deprecationNotice.message" class="text-base m-0">
672685
<MarkdownText :text="deprecationNotice.message" />
673686
</p>
674-
<p v-else class="text-base m-0 italic">{{ $t('package.deprecation.no_reason') }}</p>
687+
<p v-else class="text-base m-0 italic">
688+
{{ $t('package.deprecation.no_reason') }}
689+
</p>
675690
</div>
676691

677692
<!-- Stats grid -->
@@ -1057,7 +1072,9 @@ function handleClick(event: MouseEvent) {
10571072
role="alert"
10581073
class="flex flex-col items-center py-20 text-center"
10591074
>
1060-
<h1 class="font-mono text-2xl font-medium mb-4">{{ $t('package.not_found') }}</h1>
1075+
<h1 class="font-mono text-2xl font-medium mb-4">
1076+
{{ $t('package.not_found') }}
1077+
</h1>
10611078
<p class="text-fg-muted mb-8">
10621079
{{ error?.message ?? $t('package.not_found_message') }}
10631080
</p>

app/utils/input.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,12 @@ export const noCorrect = {
44
autocorrect: 'off',
55
spellcheck: 'false',
66
} as const
7+
8+
/**
9+
* Check if an event target is an editable element (input, textarea, or contenteditable).
10+
* Useful for keyboard shortcut handlers that should not trigger when the user is typing.
11+
*/
12+
export function isEditableElement(target: EventTarget | null): boolean {
13+
if (!target || !(target instanceof HTMLElement)) return false
14+
return target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable
15+
}

0 commit comments

Comments
 (0)