From df893edd75335a67b5ee28f9f81d796049af2c41 Mon Sep 17 00:00:00 2001 From: Carlos Ivanchuk Date: Mon, 17 Nov 2025 15:04:14 -0400 Subject: [PATCH 1/4] fix: allow form submission when pressing enter on a checkbox --- .../src/lib/bits/checkbox/checkbox.svelte.ts | 7 ++++++- .../src/tests/checkbox/checkbox.browser.test.ts | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/bits-ui/src/lib/bits/checkbox/checkbox.svelte.ts b/packages/bits-ui/src/lib/bits/checkbox/checkbox.svelte.ts index fd4a946e2..400bf45c5 100644 --- a/packages/bits-ui/src/lib/bits/checkbox/checkbox.svelte.ts +++ b/packages/bits-ui/src/lib/bits/checkbox/checkbox.svelte.ts @@ -194,7 +194,12 @@ export class CheckboxRootState { onkeydown(e: BitsKeyboardEvent) { if (this.trueDisabled || this.trueReadonly) return; - if (e.key === kbd.ENTER) e.preventDefault(); + if (e.key === kbd.ENTER) { + e.preventDefault(); + const form = e.currentTarget.closest("form"); + form?.requestSubmit(); + return; + } if (e.key === kbd.SPACE) { e.preventDefault(); this.#toggle(); diff --git a/tests/src/tests/checkbox/checkbox.browser.test.ts b/tests/src/tests/checkbox/checkbox.browser.test.ts index e47acd015..ac767e9e2 100644 --- a/tests/src/tests/checkbox/checkbox.browser.test.ts +++ b/tests/src/tests/checkbox/checkbox.browser.test.ts @@ -280,6 +280,23 @@ describe("Checkbox Group", () => { expect(submittedValues).toEqual(["a", "b"]); }); + it("should submit the form when `Enter` is pressed on a checkbox", async () => { + let submittedValues: string[] | undefined; + const t = setupGroup({ + name: "myGroup", + onFormSubmit: (fd) => { + submittedValues = fd.getAll("myGroup") as string[]; + }, + }); + const [a] = t.checkboxes; + await a.click(); + await expectChecked(a); + (a.element() as HTMLElement).focus(); + await userEvent.keyboard(kbd.ENTER); + expect(submittedValues).toEqual(["a"]); + await expectChecked(a); + }); + it("should handle binding value", async () => { const t = setupGroup(); const [a, b, _, d] = t.checkboxes; From a92810f03845667eefb7fc7663821e6b5a5a0f70 Mon Sep 17 00:00:00 2001 From: Carlos Ivanchuk Date: Thu, 20 Nov 2025 11:00:37 -0400 Subject: [PATCH 2/4] Submit only when type="submit" --- packages/bits-ui/src/lib/bits/checkbox/checkbox.svelte.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/bits-ui/src/lib/bits/checkbox/checkbox.svelte.ts b/packages/bits-ui/src/lib/bits/checkbox/checkbox.svelte.ts index 400bf45c5..114b18f61 100644 --- a/packages/bits-ui/src/lib/bits/checkbox/checkbox.svelte.ts +++ b/packages/bits-ui/src/lib/bits/checkbox/checkbox.svelte.ts @@ -196,8 +196,10 @@ export class CheckboxRootState { if (this.trueDisabled || this.trueReadonly) return; if (e.key === kbd.ENTER) { e.preventDefault(); - const form = e.currentTarget.closest("form"); - form?.requestSubmit(); + if (this.opts.type.current === "submit") { + const form = e.currentTarget.closest("form"); + form?.requestSubmit(); + } return; } if (e.key === kbd.SPACE) { From 708e1aeea130d7758fcd7669ba45b54bdb2f6cee Mon Sep 17 00:00:00 2001 From: Carlos Ivanchuk Date: Thu, 20 Nov 2025 12:02:08 -0400 Subject: [PATCH 3/4] add more tests --- .../tests/checkbox/checkbox-group-test.svelte | 3 ++ tests/src/tests/checkbox/checkbox-test.svelte | 42 ++++++++++----- .../tests/checkbox/checkbox.browser.test.ts | 52 ++++++++++++++++++- 3 files changed, 83 insertions(+), 14 deletions(-) diff --git a/tests/src/tests/checkbox/checkbox-group-test.svelte b/tests/src/tests/checkbox/checkbox-group-test.svelte index 1fba9540b..92410d71a 100644 --- a/tests/src/tests/checkbox/checkbox-group-test.svelte +++ b/tests/src/tests/checkbox/checkbox-group-test.svelte @@ -6,6 +6,7 @@ items = [], disabledItems = [], readonlyItems = [], + type, onFormSubmit, getValue: getValueProp, setValue: setValueProp, @@ -17,6 +18,7 @@ items?: string[]; disabledItems?: string[]; readonlyItems?: string[]; + type?: "submit"; onFormSubmit?: (fd: FormData) => void; setValue?: (value: string[]) => void; getValue?: () => string[]; @@ -31,6 +33,7 @@ value={itemValue} disabled={disabledItems?.includes(itemValue)} readonly={readonlyItems?.includes(itemValue)} + {type} > {#snippet children({ checked, indeterminate })} diff --git a/tests/src/tests/checkbox/checkbox-test.svelte b/tests/src/tests/checkbox/checkbox-test.svelte index b63dc804c..94d37fdc1 100644 --- a/tests/src/tests/checkbox/checkbox-test.svelte +++ b/tests/src/tests/checkbox/checkbox-test.svelte @@ -1,20 +1,36 @@
-

{checked}

- - {#snippet children({ checked, indeterminate })} - - {#if indeterminate} - indeterminate - {:else} - {checked} - {/if} - - {/snippet} - +
{ + e.preventDefault(); + const formData = new FormData(e.currentTarget); + onFormSubmit?.(formData); + }} + > +

{checked}

+ + {#snippet children({ checked, indeterminate })} + + {#if indeterminate} + indeterminate + {:else} + {checked} + {/if} + + {/snippet} + + +
diff --git a/tests/src/tests/checkbox/checkbox.browser.test.ts b/tests/src/tests/checkbox/checkbox.browser.test.ts index ac767e9e2..f07bb5471 100644 --- a/tests/src/tests/checkbox/checkbox.browser.test.ts +++ b/tests/src/tests/checkbox/checkbox.browser.test.ts @@ -148,6 +148,38 @@ describe("Single Checkbox", () => { await expect.element(indicator).not.toHaveTextContent("indeterminate"); await expect.element(page.getByRole("checkbox")).not.toBeChecked(); }); + + it("should submit the form when the `Enter` key is pressed with type='submit'", async () => { + let submittedValues: FormDataEntryValue[] | undefined; + const t = setup({ + type: "submit", + onFormSubmit: (fd) => { + submittedValues = fd.getAll("terms"); + }, + }); + await t.root.click(); + await expectChecked(t.root); + (t.root.element() as HTMLElement).focus(); + await userEvent.keyboard(kbd.ENTER); + expect(submittedValues).toEqual(["on"]); + await expectChecked(t.root); + }); + + it("should not toggle or submit the form when `Enter` is pressed on a checkbox with type='button'", async () => { + let submittedValues: FormDataEntryValue[] | undefined; + const t = setup({ + type: "button", + onFormSubmit: (fd) => { + submittedValues = fd.getAll("terms"); + }, + }); + await t.root.click(); + await expectChecked(t.root); + (t.root.element() as HTMLElement).focus(); + await userEvent.keyboard(kbd.ENTER); + expect(submittedValues).toBeUndefined(); + await expectChecked(t.root); + }); }); describe("Props and Events", () => { @@ -280,10 +312,11 @@ describe("Checkbox Group", () => { expect(submittedValues).toEqual(["a", "b"]); }); - it("should submit the form when `Enter` is pressed on a checkbox", async () => { + it("should submit the form when `Enter` is pressed on a checkbox with type='submit'", async () => { let submittedValues: string[] | undefined; const t = setupGroup({ name: "myGroup", + type: "submit", onFormSubmit: (fd) => { submittedValues = fd.getAll("myGroup") as string[]; }, @@ -297,6 +330,23 @@ describe("Checkbox Group", () => { await expectChecked(a); }); + it("should not toggle or submit the form when `Enter` is pressed on a checkbox with default type", async () => { + let submittedValues: string[] | undefined; + const t = setupGroup({ + name: "myGroup", + onFormSubmit: (fd) => { + submittedValues = fd.getAll("myGroup") as string[]; + }, + }); + const [a] = t.checkboxes; + await a.click(); + await expectChecked(a); + (a.element() as HTMLElement).focus(); + await userEvent.keyboard(kbd.ENTER); + expect(submittedValues).toBeUndefined(); + await expectChecked(a); + }); + it("should handle binding value", async () => { const t = setupGroup(); const [a, b, _, d] = t.checkboxes; From 0d8d2c88c16c7fd4a02965e16598f3bbe9bbc809 Mon Sep 17 00:00:00 2001 From: Carlos Ivanchuk Date: Thu, 20 Nov 2025 12:29:28 -0400 Subject: [PATCH 4/4] fix svelte check --- tests/src/tests/checkbox/checkbox.browser.test.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/src/tests/checkbox/checkbox.browser.test.ts b/tests/src/tests/checkbox/checkbox.browser.test.ts index f07bb5471..9e884b90e 100644 --- a/tests/src/tests/checkbox/checkbox.browser.test.ts +++ b/tests/src/tests/checkbox/checkbox.browser.test.ts @@ -2,7 +2,6 @@ import { page, userEvent, type Locator } from "@vitest/browser/context"; import { describe, expect, it, vi } from "vitest"; import { render } from "vitest-browser-svelte"; import { type ComponentProps, tick } from "svelte"; -import type { Checkbox } from "bits-ui"; import { getTestKbd } from "../utils.js"; import CheckboxTest from "./checkbox-test.svelte"; import CheckboxGroupTest from "./checkbox-group-test.svelte"; @@ -12,7 +11,7 @@ const kbd = getTestKbd(); const groupItems = ["a", "b", "c", "d"]; -function setup(props?: Checkbox.RootProps) { +function setup(props?: ComponentProps) { const returned = render(CheckboxTest, props); const root = page.getByTestId("root"); return { ...returned, root }; @@ -153,7 +152,7 @@ describe("Single Checkbox", () => { let submittedValues: FormDataEntryValue[] | undefined; const t = setup({ type: "submit", - onFormSubmit: (fd) => { + onFormSubmit: (fd: FormData) => { submittedValues = fd.getAll("terms"); }, }); @@ -169,7 +168,7 @@ describe("Single Checkbox", () => { let submittedValues: FormDataEntryValue[] | undefined; const t = setup({ type: "button", - onFormSubmit: (fd) => { + onFormSubmit: (fd: FormData) => { submittedValues = fd.getAll("terms"); }, }); @@ -298,7 +297,7 @@ describe("Checkbox Group", () => { let submittedValues: string[] | undefined; const t = setupGroup({ name: "myGroup", - onFormSubmit: (fd) => { + onFormSubmit: (fd: FormData) => { submittedValues = fd.getAll("myGroup") as string[]; }, }); @@ -317,7 +316,7 @@ describe("Checkbox Group", () => { const t = setupGroup({ name: "myGroup", type: "submit", - onFormSubmit: (fd) => { + onFormSubmit: (fd: FormData) => { submittedValues = fd.getAll("myGroup") as string[]; }, }); @@ -334,7 +333,7 @@ describe("Checkbox Group", () => { let submittedValues: string[] | undefined; const t = setupGroup({ name: "myGroup", - onFormSubmit: (fd) => { + onFormSubmit: (fd: FormData) => { submittedValues = fd.getAll("myGroup") as string[]; }, });