Skip to content

Commit db9d917

Browse files
authored
fix(PinInput): keyboard nav/replacement (#1872)
1 parent 2d96579 commit db9d917

File tree

3 files changed

+121
-3
lines changed

3 files changed

+121
-3
lines changed

.changeset/fruity-singers-rule.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"bits-ui": patch
3+
---
4+
5+
fix(Pin Input): keyboard navigation

packages/bits-ui/src/lib/bits/pin-input/pin-input.svelte.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -350,14 +350,14 @@ export class PinInputRootState {
350350
} else if (maxLength > 1 && val.length > 1) {
351351
let offset = 0;
352352
if (prev[0] !== null && prev[1] !== null) {
353-
direction = c < prev[0] ? "backward" : "forward";
353+
direction = c < prev[1] ? "backward" : "forward";
354354
const wasPreviouslyInserting = prev[0] === prev[1] && prev[0] < maxLength;
355355
if (direction === "backward" && !wasPreviouslyInserting) {
356356
offset = -1;
357357
}
358358
}
359359

360-
start = offset - c;
360+
start = offset + c;
361361
end = offset + c + 1;
362362
}
363363
}

tests/src/tests/pin-input/pin-input.browser.test.ts

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { render } from "vitest-browser-svelte";
33
import { REGEXP_ONLY_DIGITS } from "bits-ui";
44
import { getTestKbd } from "../utils.js";
55
import PinInputTest from "./pin-input-test.svelte";
6-
import { type ComponentProps } from "svelte";
6+
import type { ComponentProps } from "svelte";
77
import { page, userEvent } from "@vitest/browser/context";
88

99
const kbd = getTestKbd();
@@ -220,3 +220,116 @@ it("should allow pasting more than the max-length if transformation is provided"
220220
expect(mockComplete).toHaveBeenCalledTimes(1);
221221
expect(mockComplete).toHaveBeenCalledWith("123456");
222222
});
223+
224+
it("should handle ArrowLeft navigation correctly", async () => {
225+
const t = setup();
226+
227+
await t.hiddenInput.click();
228+
await userEvent.keyboard("1");
229+
await userEvent.keyboard("2");
230+
await userEvent.keyboard("3");
231+
await userEvent.keyboard("4");
232+
await expect.element(t.hiddenInput).toHaveValue("1234");
233+
await expect.element(t.cells[4]).toHaveAttribute("data-active");
234+
235+
await userEvent.keyboard(kbd.ARROW_LEFT);
236+
await expect.element(t.cells[3]).toHaveAttribute("data-active");
237+
await expect.element(t.cells[4]).not.toHaveAttribute("data-active");
238+
await expect.element(t.cells[2]).not.toHaveAttribute("data-active");
239+
240+
await userEvent.keyboard(kbd.ARROW_LEFT);
241+
await expect.element(t.cells[2]).toHaveAttribute("data-active");
242+
await expect.element(t.cells[3]).not.toHaveAttribute("data-active");
243+
244+
await userEvent.keyboard(kbd.ARROW_LEFT);
245+
await expect.element(t.cells[1]).toHaveAttribute("data-active");
246+
await expect.element(t.cells[2]).not.toHaveAttribute("data-active");
247+
248+
await userEvent.keyboard(kbd.ARROW_LEFT);
249+
await expect.element(t.cells[0]).toHaveAttribute("data-active");
250+
await expect.element(t.cells[1]).not.toHaveAttribute("data-active");
251+
});
252+
253+
it("should correctly replace characters when navigating back and typing", async () => {
254+
const t = setup();
255+
256+
await t.hiddenInput.click();
257+
await t.hiddenInput.fill("1234");
258+
await expect.element(t.hiddenInput).toHaveValue("1234");
259+
260+
// navigate to beginning using Home
261+
await userEvent.keyboard(kbd.HOME);
262+
await expect.element(t.cells[0]).toHaveAttribute("data-active");
263+
264+
// type 5 and 6 - should replace first two characters
265+
await userEvent.keyboard("5");
266+
await expect.element(t.hiddenInput).toHaveValue("5234");
267+
await expect.element(t.cells[0]).toHaveTextContent("5");
268+
await expect.element(t.cells[1]).toHaveAttribute("data-active");
269+
270+
await userEvent.keyboard("6");
271+
await expect.element(t.hiddenInput).toHaveValue("5634");
272+
await expect.element(t.cells[0]).toHaveTextContent("5");
273+
await expect.element(t.cells[1]).toHaveTextContent("6");
274+
await expect.element(t.cells[2]).toHaveTextContent("3");
275+
await expect.element(t.cells[3]).toHaveTextContent("4");
276+
});
277+
278+
it("should correctly replace characters when navigating back with ArrowLeft and typing", async () => {
279+
const t = setup();
280+
281+
await t.hiddenInput.click();
282+
await t.hiddenInput.fill("1234");
283+
await expect.element(t.hiddenInput).toHaveValue("1234");
284+
285+
// navigate back using ArrowLeft
286+
await userEvent.keyboard(kbd.ARROW_LEFT);
287+
await expect.element(t.cells[3]).toHaveAttribute("data-active");
288+
289+
await userEvent.keyboard(kbd.ARROW_LEFT);
290+
await expect.element(t.cells[2]).toHaveAttribute("data-active");
291+
292+
await userEvent.keyboard(kbd.ARROW_LEFT);
293+
await expect.element(t.cells[1]).toHaveAttribute("data-active");
294+
295+
await userEvent.keyboard(kbd.ARROW_LEFT);
296+
await expect.element(t.cells[0]).toHaveAttribute("data-active");
297+
298+
// type 5 and 6 - should replace first two characters
299+
await userEvent.keyboard("5");
300+
await expect.element(t.hiddenInput).toHaveValue("5234");
301+
await expect.element(t.cells[0]).toHaveTextContent("5");
302+
await expect.element(t.cells[1]).toHaveAttribute("data-active");
303+
304+
await userEvent.keyboard("6");
305+
await expect.element(t.hiddenInput).toHaveValue("5634");
306+
await expect.element(t.cells[0]).toHaveTextContent("5");
307+
await expect.element(t.cells[1]).toHaveTextContent("6");
308+
await expect.element(t.cells[2]).toHaveTextContent("3");
309+
await expect.element(t.cells[3]).toHaveTextContent("4");
310+
});
311+
312+
it("should correctly replace characters when navigating with ArrowUp and typing", async () => {
313+
const t = setup();
314+
315+
await t.hiddenInput.click();
316+
await t.hiddenInput.fill("1234");
317+
await expect.element(t.hiddenInput).toHaveValue("1234");
318+
319+
// navigate to beginning using ArrowUp (equivalent to Home)
320+
await userEvent.keyboard(kbd.ARROW_UP);
321+
await expect.element(t.cells[0]).toHaveAttribute("data-active");
322+
323+
// type 5 and 6 - should replace first two characters
324+
await userEvent.keyboard("5");
325+
await expect.element(t.hiddenInput).toHaveValue("5234");
326+
await expect.element(t.cells[0]).toHaveTextContent("5");
327+
await expect.element(t.cells[1]).toHaveAttribute("data-active");
328+
329+
await userEvent.keyboard("6");
330+
await expect.element(t.hiddenInput).toHaveValue("5634");
331+
await expect.element(t.cells[0]).toHaveTextContent("5");
332+
await expect.element(t.cells[1]).toHaveTextContent("6");
333+
await expect.element(t.cells[2]).toHaveTextContent("3");
334+
await expect.element(t.cells[3]).toHaveTextContent("4");
335+
});

0 commit comments

Comments
 (0)