Skip to content

Commit

Permalink
Add option for disabling vim bindings (#168)
Browse files Browse the repository at this point in the history
  • Loading branch information
bkrausz authored Aug 14, 2023
1 parent ba2e200 commit dc0d0e5
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 8 deletions.
19 changes: 12 additions & 7 deletions cmdk/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import { commandScore } from './command-score'
type Children = { children?: React.ReactNode }
type DivProps = React.HTMLAttributes<HTMLDivElement>

type LoadingProps = Children & DivProps & {
/** Estimated progress of loading asynchronous options. */
progress?: number
}
type LoadingProps = Children &
DivProps & {
/** Estimated progress of loading asynchronous options. */
progress?: number
}
type EmptyProps = Children & DivProps & {}
type SeparatorProps = DivProps & {
/** Whether this separator should always be rendered. Useful if you disable automatic filtering. */
Expand Down Expand Up @@ -90,6 +91,10 @@ type CommandProps = Children &
* Optionally set to `true` to turn on looping around when using the arrow keys.
*/
loop?: boolean
/**
* Set to `false` to disable ctrl+n/j/p/k shortcuts. Defaults to `true`.
*/
vimBindings?: boolean
}

type Context = {
Expand Down Expand Up @@ -160,7 +165,7 @@ const Command = React.forwardRef<HTMLDivElement, CommandProps>((props, forwarded
const ids = useLazyRef<Map<string, string>>(() => new Map()) // id → value
const listeners = useLazyRef<Set<() => void>>(() => new Set()) // [...rerenders]
const propsRef = useAsRef(props)
const { label, children, value, onValueChange, filter, shouldFilter, ...etc } = props
const { label, children, value, onValueChange, filter, shouldFilter, vimBindings = true, ...etc } = props

const listId = React.useId()
const labelId = React.useId()
Expand Down Expand Up @@ -519,7 +524,7 @@ const Command = React.forwardRef<HTMLDivElement, CommandProps>((props, forwarded
case 'n':
case 'j': {
// vim keybind down
if (e.ctrlKey) {
if (vimBindings && e.ctrlKey) {
next(e)
}
break
Expand All @@ -531,7 +536,7 @@ const Command = React.forwardRef<HTMLDivElement, CommandProps>((props, forwarded
case 'p':
case 'k': {
// vim keybind up
if (e.ctrlKey) {
if (vimBindings && e.ctrlKey) {
prev(e)
}
break
Expand Down
22 changes: 22 additions & 0 deletions test/keybind.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,25 @@ test.describe('vim np keybinds', async () => {
await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first')
})
})

test.describe('no-vim keybinds', async () => {
test.beforeEach(async ({ page }) => {
await page.goto('/keybinds?noVim=true')
})

test('ctrl j/k does nothing', async ({ page }) => {
await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first')
await page.locator(`[cmdk-input]`).press('Control+j')
await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first')
await page.locator(`[cmdk-input]`).press('Control+k')
await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first')
})

test('ctrl n/p does nothing', async ({ page }) => {
await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first')
await page.locator(`[cmdk-input]`).press('Control+n')
await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first')

Check failure on line 121 in test/keybind.test.ts

View workflow job for this annotation

GitHub Actions / test

[webkit] › keybind.test.ts:118:3 › no-vim keybinds › ctrl n/p does nothing

1) [webkit] › keybind.test.ts:118:3 › no-vim keybinds › ctrl n/p does nothing ==================== Error: expect(received).toHaveAttribute(expected) Expected string: "first" Received string: "a" Call log: - expect.toHaveAttribute with timeout 5000ms - waiting for selector "[cmdk-item][aria-selected="true"]" - selector resolved to <div id=":R6pam:" cmdk-item="" role="option" data-val…>A</div> - unexpected value "a" - selector resolved to <div id=":R6pam:" cmdk-item="" role="option" data-val…>A</div> - unexpected value "a" - selector resolved to <div id=":R6pam:" cmdk-item="" role="option" data-val…>A</div> - unexpected value "a" - selector resolved to <div id=":R6pam:" cmdk-item="" role="option" data-val…>A</div> - unexpected value "a" - selector resolved to <div id=":R6pam:" cmdk-item="" role="option" data-val…>A</div> - unexpected value "a" - selector resolved to <div id=":R6pam:" cmdk-item="" role="option" data-val…>A</div> - unexpected value "a" 119 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first') 120 | await page.locator(`[cmdk-input]`).press('Control+n') > 121 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first') | ^ 122 | await page.locator(`[cmdk-input]`).press('Control+p') 123 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first') 124 | }) at /home/runner/work/cmdk/cmdk/test/keybind.test.ts:121:69
await page.locator(`[cmdk-input]`).press('Control+p')
await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first')
})
})
6 changes: 5 additions & 1 deletion test/pages/keybinds.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { Command } from 'cmdk'
import { useRouter } from 'next/router'
import * as React from 'react'

const Page = () => {
const {
query: { noVim },
} = useRouter()
return (
<div>
<Command>
<Command vimBindings={!noVim}>
<Command.Input />
<Command.List>
<Command.Empty>No results.</Command.Empty>
Expand Down

1 comment on commit dc0d0e5

@vercel
Copy link

@vercel vercel bot commented on dc0d0e5 Aug 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

cmdk-website – ./

cmdk.vercel.app
cmdk-website-paco.vercel.app
cmdk-website-git-main-paco.vercel.app
cmdk.paco.me

Please sign in to comment.