Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions app/app.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script setup lang="ts">
import type { Directions } from '@nuxtjs/i18n'
import { useEventListener } from '@vueuse/core'
import { isEditableElement } from '~/utils/input'

const route = useRoute()
const router = useRouter()
Expand Down Expand Up @@ -39,12 +40,7 @@ if (import.meta.server) {
// "/" focuses search or navigates to search page
// "?" highlights all keyboard shortcut elements
function handleGlobalKeydown(e: KeyboardEvent) {
const target = e.target as HTMLElement

const isEditableTarget =
target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable

if (isEditableTarget) return
if (isEditableElement(e.target)) return

if (e.key === '/') {
e.preventDefault()
Expand Down
15 changes: 4 additions & 11 deletions app/components/AppHeader.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<script setup lang="ts">
import { isEditableElement } from '~/utils/input'

withDefaults(
defineProps<{
showLogo?: boolean
Expand Down Expand Up @@ -62,11 +64,7 @@ function handleSearchFocus() {
onKeyStroke(
',',
e => {
// Don't trigger if user is typing in an input
const target = e.target as HTMLElement
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
return
}
if (isEditableElement(e.target)) return

e.preventDefault()
navigateTo('/settings')
Expand All @@ -79,12 +77,7 @@ onKeyStroke(
e => {
// Allow more specific handlers to take precedence
if (e.defaultPrevented) return

// Don't trigger if user is typing in an input
const target = e.target as HTMLElement
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
return
}
if (isEditableElement(e.target)) return

e.preventDefault()
navigateTo('/compare')
Expand Down
29 changes: 23 additions & 6 deletions app/pages/[...package].vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { JsrPackageInfo } from '#shared/types/jsr'
import { assertValidPackageName } from '#shared/utils/npm'
import { joinURL } from 'ufo'
import { areUrlsEquivalent } from '#shared/utils/url'
import { isEditableElement } from '~/utils/input'

definePageMeta({
name: 'package',
Expand Down Expand Up @@ -113,7 +114,10 @@ const deprecationNotice = computed(() => {

// If latest is deprecated, show "package deprecated"
if (isLatestDeprecated) {
return { type: 'package' as const, message: displayVersion.value.deprecated }
return {
type: 'package' as const,
message: displayVersion.value.deprecated,
}
}

// Otherwise show "version deprecated"
Expand Down Expand Up @@ -215,7 +219,9 @@ const docsLink = computed(() => {

return {
name: 'docs' as const,
params: { path: [...pkg.value!.name.split('/'), 'v', displayVersion.value.version] },
params: {
path: [...pkg.value!.name.split('/'), 'v', displayVersion.value.version],
},
}
})

Expand Down Expand Up @@ -314,6 +320,7 @@ useSeoMeta({
onKeyStroke(
'.',
e => {
if (isEditableElement(e.target)) return
if (pkg.value && displayVersion.value) {
e.preventDefault()
navigateTo({
Expand All @@ -330,6 +337,7 @@ onKeyStroke(
onKeyStroke(
'd',
e => {
if (isEditableElement(e.target)) return
if (docsLink.value) {
e.preventDefault()
navigateTo(docsLink.value)
Expand All @@ -339,6 +347,7 @@ onKeyStroke(
)

onKeyStroke('c', e => {
if (isEditableElement(e.target)) return
if (pkg.value) {
e.preventDefault()
router.push({ path: '/compare', query: { packages: pkg.value.name } })
Expand Down Expand Up @@ -486,7 +495,9 @@ function handleClick(event: MouseEvent) {
<NuxtLink
:to="{
name: 'code',
params: { path: [...pkg.name.split('/'), 'v', displayVersion.version] },
params: {
path: [...pkg.name.split('/'), 'v', displayVersion.version],
},
}"
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"
aria-keyshortcuts="."
Expand Down Expand Up @@ -636,7 +647,9 @@ function handleClick(event: MouseEvent) {
<NuxtLink
:to="{
name: 'code',
params: { path: [...pkg.name.split('/'), 'v', displayVersion.version] },
params: {
path: [...pkg.name.split('/'), 'v', displayVersion.version],
},
}"
class="link-subtle font-mono text-sm inline-flex items-center gap-1.5"
>
Expand Down Expand Up @@ -670,7 +683,9 @@ function handleClick(event: MouseEvent) {
<p v-if="deprecationNotice.message" class="text-base m-0">
<MarkdownText :text="deprecationNotice.message" />
</p>
<p v-else class="text-base m-0 italic">{{ $t('package.deprecation.no_reason') }}</p>
<p v-else class="text-base m-0 italic">
{{ $t('package.deprecation.no_reason') }}
</p>
</div>

<!-- Stats grid -->
Expand Down Expand Up @@ -1056,7 +1071,9 @@ function handleClick(event: MouseEvent) {
role="alert"
class="flex flex-col items-center py-20 text-center"
>
<h1 class="font-mono text-2xl font-medium mb-4">{{ $t('package.not_found') }}</h1>
<h1 class="font-mono text-2xl font-medium mb-4">
{{ $t('package.not_found') }}
</h1>
<p class="text-fg-muted mb-8">
{{ error?.message ?? $t('package.not_found_message') }}
</p>
Expand Down
9 changes: 9 additions & 0 deletions app/utils/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,12 @@ export const noCorrect = {
autocorrect: 'off',
spellcheck: 'false',
} as const

/**
* Check if an event target is an editable element (input, textarea, or contenteditable).
* Useful for keyboard shortcut handlers that should not trigger when the user is typing.
*/
export function isEditableElement(target: EventTarget | null): boolean {
if (!target || !(target instanceof HTMLElement)) return false
return target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable
}
Loading