Repository: melt-ui/melt-ui
Pull request #990 (2024-02-16 16:33:15)

fix: toggle group focus issues
Closes: #810

Additional tests were added to support this as well.

changeset-bot[bot]: ###  🦋  Changeset detected

Latest commit: 8cef05908dd01fbd6b96fc15c828d700509218af

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 1 package</summary>

  | Name            | Type  |
| --------------- | ----- |
| @melt-ui/svelte | Patch |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/huntabyte/melt-ui/new/fix/810?filename=.changeset/dirty-donuts-tickle.md&value=---%0A%22%40melt-ui%2Fsvelte%22%3A%20patch%0A---%0A%0Afix%3A%20toggle%20group%20focus%20issues%0A)


github-actions[bot]: <!-- refined-cf-pages-action:deployment-summary:melt-ui -->

###### built with [Refined Cloudflare Pages Action](https://github.com/AdrianGonz97/refined-cf-pages-action)
## ⚡ Cloudflare Pages Deployment
| Name | Status | Preview | Last Commit |
| :--- | :----- | :------ | :---------- |
| **melt-ui** | 🔨 Building ([View Log](https://github.com/melt-ui/melt-ui/actions/runs/7933476780)) |  | 8cef05908dd01fbd6b96fc15c828d700509218af |


Patch:
diff --git a/.changeset/forty-beers-reply.md b/.changeset/forty-beers-reply.md
new file mode 100644
index 00000000..0aa3e0de
--- /dev/null
+++ b/.changeset/forty-beers-reply.md
@@ -0,0 +1,5 @@
+---
+"@melt-ui/svelte": patch
+---
+
+Dialog: fixed interact outside pointerup/mouseup issue where the dialog would close if you clicked and dragged outside of the dialog before releasing the press (Closes: #750)
diff --git a/.changeset/popular-dolls-attend.md b/.changeset/popular-dolls-attend.md
new file mode 100644
index 00000000..bfc013a7
--- /dev/null
+++ b/.changeset/popular-dolls-attend.md
@@ -0,0 +1,5 @@
+---
+'@melt-ui/svelte': patch
+---
+
+Toggle Group: fixed bug where `single` groups wouldn't focus (Closes #810)
diff --git a/.changeset/spotty-ants-kiss.md b/.changeset/spotty-ants-kiss.md
deleted file mode 100644
index 2ce2f903..00000000
--- a/.changeset/spotty-ants-kiss.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"@melt-ui/svelte": patch
----
-
-ToggleGroup: Fixes curr & next always bbeing the same value
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ca41b48d..0472ac1b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,15 @@
 # @melt-ui/svelte
 
+## 0.74.1
+
+### Patch Changes
+
+- Date Picker/Date Range Picker: disable trigger when picker is disabled (Closes #815) ([#989](https://github.com/melt-ui/melt-ui/pull/989))
+
+- Toast: return close element `type` attribute type as `"button"` instead of `string` ([#993](https://github.com/melt-ui/melt-ui/pull/993))
+
+- ToggleGroup: Fixes curr & next always bbeing the same value ([#983](https://github.com/melt-ui/melt-ui/pull/983))
+
 ## 0.74.0
 
 ### Minor Changes
diff --git a/package.json b/package.json
index 1e18afed..798f88d5 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "@melt-ui/svelte",
-	"version": "0.74.0",
+	"version": "0.74.1",
 	"license": "MIT",
 	"repository": "github:melt-ui/melt-ui",
 	"author": "Thomas G. Lopes",
diff --git a/src/lib/builders/date-picker/create.ts b/src/lib/builders/date-picker/create.ts
index 9e52e512..3adbfce7 100644
--- a/src/lib/builders/date-picker/create.ts
+++ b/src/lib/builders/date-picker/create.ts
@@ -76,12 +76,13 @@ export function createDatePicker(props?: CreateDatePickerProps) {
 	});
 
 	const trigger = makeElement('popover-trigger', {
-		stores: [popover.elements.trigger],
-		returned: ([$trigger]) => {
+		stores: [popover.elements.trigger, options.disabled],
+		returned: ([$trigger, $disabled]) => {
 			return {
 				...omit($trigger, 'action'),
 				'aria-label': 'Open date picker',
 				'data-segment': 'trigger',
+				disabled: $disabled ? true : undefined,
 			};
 		},
 		action: (node: HTMLElement): MeltActionReturn<DatePickerEvents['trigger']> => {
diff --git a/src/lib/builders/date-range-picker/create.ts b/src/lib/builders/date-range-picker/create.ts
index 6c89c640..5f046587 100644
--- a/src/lib/builders/date-range-picker/create.ts
+++ b/src/lib/builders/date-range-picker/create.ts
@@ -88,12 +88,13 @@ export function createDateRangePicker(props?: CreateDateRangePickerProps) {
 	const placeholder = dateStore(rfPlaceholder, withDefaults.defaultPlaceholder ?? defaultDate);
 
 	const trigger = makeElement('popover-trigger', {
-		stores: [popover.elements.trigger],
-		returned: ([$trigger]) => {
+		stores: [popover.elements.trigger, options.disabled],
+		returned: ([$trigger, $disabled]) => {
 			return {
 				...omit($trigger, 'action'),
 				'aria-label': 'Open date picker',
 				'data-segment': 'trigger',
+				disabled: $disabled ? true : undefined,
 			};
 		},
 		action: (node: HTMLElement): MeltActionReturn<DateRangePickerEvents['trigger']> => {
diff --git a/src/lib/builders/toast/create.ts b/src/lib/builders/toast/create.ts
index cf3b30a0..fd7d104a 100644
--- a/src/lib/builders/toast/create.ts
+++ b/src/lib/builders/toast/create.ts
@@ -192,7 +192,7 @@ export function createToaster<T = object>(props?: CreateToasterProps) {
 	const close = makeElement(name('close'), {
 		returned: () => {
 			return (id: string) => ({
-				type: 'button',
+				type: 'button' as const,
 				'data-id': id,
 			});
 		},
diff --git a/src/lib/builders/toggle-group/create.ts b/src/lib/builders/toggle-group/create.ts
index 5fe06c9c..59017dc8 100644
--- a/src/lib/builders/toggle-group/create.ts
+++ b/src/lib/builders/toggle-group/create.ts
@@ -41,7 +41,7 @@ export const createToggleGroup = <T extends ToggleGroupType = 'single'>(
 	const defaultValue = withDefaults.defaultValue
 		? withDefaults.defaultValue
 		: withDefaults.type === 'single'
-		? 'undefined'
+		? undefined
 		: [];
 
 	const valueWritable = withDefaults.value ?? writable(defaultValue);
@@ -90,7 +90,7 @@ export const createToggleGroup = <T extends ToggleGroupType = 'single'>(
 
 			const items = Array.from(parentGroup.querySelectorAll(selector('item')));
 			const $value = value.get();
-			const anyPressed = Array.isArray($value) ? $value.length > 0 : $value !== null;
+			const anyPressed = Array.isArray($value) ? $value.length > 0 : $value ? true : false;
 
 			if (!anyPressed && items[0] === node) {
 				node.tabIndex = 0;
diff --git a/src/lib/internal/actions/interact-outside/action.ts b/src/lib/internal/actions/interact-outside/action.ts
index 9f6f6712..4fcdbb2d 100644
--- a/src/lib/internal/actions/interact-outside/action.ts
+++ b/src/lib/internal/actions/interact-outside/action.ts
@@ -9,19 +9,26 @@ import type { InteractOutsideConfig, InteractOutsideEvent } from './types.js';
 export function useInteractOutside(node: HTMLElement, config: InteractOutsideConfig) {
 	let unsub = noop;
 
+	let isPointerDown = false;
+	let isPointerDownInside = false;
+	let ignoreEmulatedMouseEvents = false;
+
 	function update(config: InteractOutsideConfig) {
 		unsub();
 		const { onInteractOutside, onInteractOutsideStart, enabled } = config;
 
 		if (!enabled) return;
 
-		let isPointerDown = false;
-		let ignoreEmulatedMouseEvents = false;
-
 		function onPointerDown(e: PointerEvent | MouseEvent | TouchEvent) {
 			if (onInteractOutside && isValidEvent(e, node)) {
 				onInteractOutsideStart?.(e);
 			}
+			const target = e.target;
+
+			if (isElement(target) && isOrContainsTarget(node, target)) {
+				isPointerDownInside = true;
+			}
+
 			isPointerDown = true;
 		}
 
@@ -34,10 +41,10 @@ export function useInteractOutside(node: HTMLElement, config: InteractOutsideCon
 		// Use pointer events if available, otherwise use mouse/touch events
 		if (typeof PointerEvent !== 'undefined') {
 			const onPointerUp = (e: PointerEvent) => {
-				if (isPointerDown && isValidEvent(e, node)) {
+				if (shouldTriggerInteractOutside(e)) {
 					triggerInteractOutside(e);
 				}
-				isPointerDown = false;
+				resetPointerState();
 			};
 
 			unsub = executeCallbacks(
@@ -48,18 +55,18 @@ export function useInteractOutside(node: HTMLElement, config: InteractOutsideCon
 			const onMouseUp = (e: MouseEvent) => {
 				if (ignoreEmulatedMouseEvents) {
 					ignoreEmulatedMouseEvents = false;
-				} else if (isPointerDown && isValidEvent(e, node)) {
+				} else if (shouldTriggerInteractOutside(e)) {
 					triggerInteractOutside(e);
 				}
-				isPointerDown = false;
+				resetPointerState();
 			};
 
 			const onTouchEnd = (e: TouchEvent) => {
 				ignoreEmulatedMouseEvents = true;
-				if (isPointerDown && isValidEvent(e, node)) {
+				if (shouldTriggerInteractOutside(e)) {
 					triggerInteractOutside(e);
 				}
-				isPointerDown = false;
+				resetPointerState();
 			};
 
 			unsub = executeCallbacks(
@@ -71,6 +78,18 @@ export function useInteractOutside(node: HTMLElement, config: InteractOutsideCon
 		}
 	}
 
+	function shouldTriggerInteractOutside(e: InteractOutsideEvent) {
+		if (isPointerDown && !isPointerDownInside && isValidEvent(e, node)) {
+			return true;
+		}
+		return false;
+	}
+
+	function resetPointerState() {
+		isPointerDown = false;
+		isPointerDownInside = false;
+	}
+
 	update(config);
 
 	return {
@@ -90,7 +109,11 @@ function isValidEvent(e: InteractOutsideEvent, node: HTMLElement): boolean {
 		return false;
 	}
 
-	return node && !node.contains(target);
+	return node && !isOrContainsTarget(node, target);
+}
+
+function isOrContainsTarget(node: HTMLElement, target: Element) {
+	return node === target || node.contains(target);
 }
 
 function getOwnerDocument(el: Element | null | undefined) {


Test patch:
diff --git a/src/tests/date-picker/DatePicker.spec.ts b/src/tests/date-picker/DatePicker.spec.ts
index 821fea42..2ff9089e 100644
--- a/src/tests/date-picker/DatePicker.spec.ts
+++ b/src/tests/date-picker/DatePicker.spec.ts
@@ -552,4 +552,12 @@ describe('DatePicker', () => {
 		await user.click(outside);
 		await waitFor(() => expect(content).toBeVisible());
 	});
+
+	test('trigger is disabled when `disabled` prop is true', async () => {
+		const { trigger } = setup({
+			disabled: true,
+		});
+
+		expect(trigger).toBeDisabled();
+	});
 });
diff --git a/src/tests/dialog/Dialog.spec.ts b/src/tests/dialog/Dialog.spec.ts
index 2fd27dc6..c9dedae1 100644
--- a/src/tests/dialog/Dialog.spec.ts
+++ b/src/tests/dialog/Dialog.spec.ts
@@ -247,4 +247,34 @@ describe('Dialog', () => {
 		expect(title.id).toBe(ids.title);
 		expect(description.id).toBe(ids.description);
 	});
+
+	it("Doesn't close on pointerup if the previous pointerdown didn't occur inside the dialog", async () => {
+		const { getByTestId, user, trigger } = setup();
+		const overlay = getByTestId('overlay');
+		const content = getByTestId('content');
+
+		expect(content).not.toBeVisible();
+		await user.click(trigger);
+		expect(content).toBeVisible();
+		await sleep(100);
+		expect(overlay).toBeVisible();
+		await user.pointer({ target: content, offset: 2, keys: '[MouseLeft>]' });
+		await user.pointer({ target: overlay, offset: 2, keys: '[/MouseLeft]' });
+		expect(content).toBeVisible();
+	});
+
+	it('Closes on pointerup if the previous pointerdown occurred outside the dialog', async () => {
+		const { getByTestId, user, trigger } = setup();
+		const overlay = getByTestId('overlay');
+		const content = getByTestId('content');
+
+		expect(content).not.toBeVisible();
+		await user.click(trigger);
+		expect(content).toBeVisible();
+		await sleep(100);
+		expect(overlay).toBeVisible();
+		await user.pointer({ target: overlay, offset: 2, keys: '[MouseLeft>]' });
+		await user.pointer({ target: overlay, offset: 2, keys: '[/MouseLeft]' });
+		expect(content).not.toBeVisible();
+	});
 });
diff --git a/src/tests/toggle-group/ToggleGroup.spec.ts b/src/tests/toggle-group/ToggleGroup.spec.ts
index 1016d6e1..42bbeac3 100644
--- a/src/tests/toggle-group/ToggleGroup.spec.ts
+++ b/src/tests/toggle-group/ToggleGroup.spec.ts
@@ -1,4 +1,3 @@
-import { kbd } from '$lib/internal/helpers/keyboard.js';
 import { render } from '@testing-library/svelte';
 import { userEvent } from '@testing-library/user-event';
 import { axe } from 'jest-axe';
@@ -6,8 +5,25 @@ import { describe } from 'vitest';
 import ToggleGroupTest from './ToggleGroupTest.svelte';
 import { tick } from 'svelte';
 import { writable } from 'svelte/store';
+import type { CreateToggleGroupProps } from '$lib/index.js';
+import { testKbd as kbd } from '../utils.js';
 
 const items = ['item-1', 'item-2', 'item-3'];
+const defaults: { items: string[]; type: 'single' | 'multiple' } = {
+	items,
+	type: 'single',
+};
+
+function setup<T extends 'single' | 'multiple'>(
+	props?: CreateToggleGroupProps<T> & { items?: string[]; type: 'single' | 'multiple' }
+) {
+	const withDefaults = { ...defaults, ...props };
+	const user = userEvent.setup();
+	const result = render(ToggleGroupTest, withDefaults);
+	const tabButton = result.getByTestId('tab-btn');
+	const root = result.getByTestId('root');
+	return { user, ...result, tabButton, root };
+}
 
 describe('Toggle Group', () => {
 	test('has no accessibility violations', async () => {
@@ -17,11 +33,7 @@ describe('Toggle Group', () => {
 	});
 
 	test('tab does not navigate between items', async () => {
-		const user = userEvent.setup();
-		const { getByTestId } = render(ToggleGroupTest, {
-			type: 'single',
-			items,
-		});
+		const { user, getByTestId } = setup();
 
 		getByTestId(items[0]).focus();
 		expect(getByTestId(items[0])).toHaveFocus();
@@ -34,30 +46,27 @@ describe('Toggle Group', () => {
 	});
 
 	test('keyboard navigation between items', async () => {
-		const user = userEvent.setup();
-		const { getByTestId } = render(ToggleGroupTest, {
+		const { getByTestId, user } = setup({
 			type: 'single',
-			items,
+			loop: false,
 		});
 
 		getByTestId(items[0]).focus();
 
 		for (const item of items) {
 			expect(getByTestId(item)).toHaveFocus();
-			await user.keyboard(`{${kbd.ARROW_RIGHT}}`);
+			await user.keyboard(kbd.ARROW_RIGHT);
 		}
 
 		for (const item of [...items].reverse()) {
 			expect(getByTestId(item)).toHaveFocus();
-			await user.keyboard(`{${kbd.ARROW_LEFT}}`);
+			await user.keyboard(kbd.ARROW_LEFT);
 		}
 	});
 
 	test('keyboard navigation between items - loop', async () => {
-		const user = userEvent.setup();
-		const { getByTestId } = render(ToggleGroupTest, {
+		const { getByTestId, user } = setup({
 			type: 'single',
-			items,
 			loop: true,
 		});
 
@@ -65,30 +74,29 @@ describe('Toggle Group', () => {
 
 		for (const item of items) {
 			expect(getByTestId(item)).toHaveFocus();
-			await user.keyboard(`{${kbd.ARROW_RIGHT}}`);
+			await user.keyboard(kbd.ARROW_RIGHT);
 		}
 
 		for (const item of items) {
 			expect(getByTestId(item)).toHaveFocus();
-			await user.keyboard(`{${kbd.ARROW_RIGHT}}`);
+			await user.keyboard(kbd.ARROW_RIGHT);
 		}
 	});
 
 	test('only one item can be selected in single mode', async () => {
-		const user = userEvent.setup();
-		const { getByTestId } = render(ToggleGroupTest, {
+		const { getByTestId, user } = setup({
 			type: 'single',
 			items,
 		});
 
 		getByTestId(items[0]).focus();
 		expect(getByTestId(items[0])).toHaveFocus();
-		await user.keyboard(`{${kbd.SPACE}}`);
+		await user.keyboard(kbd.SPACE);
 		expect(getByTestId(items[0])).toHaveAttribute('aria-checked', 'true');
 
-		await user.keyboard(`{${kbd.ARROW_RIGHT}}`);
+		await user.keyboard(kbd.ARROW_RIGHT);
 		expect(getByTestId(items[1])).toHaveFocus();
-		await user.keyboard(`{${kbd.SPACE}}`);
+		await user.keyboard(kbd.SPACE);
 
 		const root = getByTestId('root');
 		const checkedItems = root.querySelectorAll<HTMLElement>('[aria-checked="true"]');
@@ -96,20 +104,18 @@ describe('Toggle Group', () => {
 	});
 
 	test('multiple items can be selected in multiple mode', async () => {
-		const user = userEvent.setup();
-		const { getByTestId } = render(ToggleGroupTest, {
+		const { getByTestId, user } = setup({
 			type: 'multiple',
-			items,
 		});
 
 		getByTestId(items[0]).focus();
 		expect(getByTestId(items[0])).toHaveFocus();
-		await user.keyboard(`{${kbd.SPACE}}`);
+		await user.keyboard(kbd.SPACE);
 		expect(getByTestId(items[0])).toHaveAttribute('aria-pressed', 'true');
 
-		await user.keyboard(`{${kbd.ARROW_RIGHT}}`);
+		await user.keyboard(kbd.ARROW_RIGHT);
 		expect(getByTestId(items[1])).toHaveFocus();
-		await user.keyboard(`{${kbd.SPACE}}`);
+		await user.keyboard(kbd.SPACE);
 		expect(getByTestId(items[1])).toHaveAttribute('aria-pressed', 'true');
 
 		const root = getByTestId('root');
@@ -118,39 +124,35 @@ describe('Toggle Group', () => {
 	});
 
 	test('can uncheck items in single mode', async () => {
-		const user = userEvent.setup();
-		const { getByTestId } = render(ToggleGroupTest, {
+		const { getByTestId, user } = setup({
 			type: 'single',
-			items,
 		});
 
 		getByTestId(items[0]).focus();
 		expect(getByTestId(items[0])).toHaveFocus();
-		await user.keyboard(`{${kbd.SPACE}}`);
+		await user.keyboard(kbd.SPACE);
 		expect(getByTestId(items[0])).toHaveAttribute('aria-checked', 'true');
-		await user.keyboard(`{${kbd.SPACE}}`);
+		await user.keyboard(kbd.SPACE);
 		expect(getByTestId(items[0])).toHaveAttribute('aria-checked', 'false');
 	});
 
 	test('can uncheck items in multiple mode', async () => {
-		const user = userEvent.setup();
-		const { getByTestId } = render(ToggleGroupTest, {
+		const { getByTestId, user } = setup({
 			type: 'multiple',
 			items,
 		});
 
 		getByTestId(items[0]).focus();
 		expect(getByTestId(items[0])).toHaveFocus();
-		await user.keyboard(`{${kbd.SPACE}}`);
+		await user.keyboard(kbd.SPACE);
 		expect(getByTestId(items[0])).toHaveAttribute('aria-pressed', 'true');
-		await user.keyboard(`{${kbd.SPACE}}`);
+		await user.keyboard(kbd.SPACE);
 		expect(getByTestId(items[0])).toHaveAttribute('aria-pressed', 'false');
 	});
 
 	test('defaultValue is respected in single mode', async () => {
-		const { getByTestId } = render(ToggleGroupTest, {
+		const { getByTestId } = setup({
 			type: 'single',
-			items,
 			defaultValue: items[1],
 		});
 
@@ -160,9 +162,8 @@ describe('Toggle Group', () => {
 	});
 
 	test('defaultValue is respected in multiple mode', async () => {
-		const { getByTestId } = render(ToggleGroupTest, {
+		const { getByTestId } = setup({
 			type: 'multiple',
-			items,
 			defaultValue: [items[1], items[2]],
 		});
 
@@ -173,9 +174,8 @@ describe('Toggle Group', () => {
 
 	test('value prop overrides defaultValue - single', async () => {
 		const valueStore = writable<string>(items[1]);
-		const { getByTestId } = render(ToggleGroupTest, {
+		const { getByTestId } = setup({
 			type: 'single',
-			items,
 			defaultValue: items[0],
 			value: valueStore,
 		});
@@ -187,9 +187,8 @@ describe('Toggle Group', () => {
 
 	test('value prop overrides defaultValue - multiple', async () => {
 		const valueStore = writable<string[]>([items[1], items[2]]);
-		const { getByTestId } = render(ToggleGroupTest, {
+		const { getByTestId } = setup({
 			type: 'multiple',
-			items,
 			defaultValue: [items[0]],
 			value: valueStore,
 		});
@@ -201,9 +200,8 @@ describe('Toggle Group', () => {
 
 	test('value prop programmatically updates - single', async () => {
 		const valueStore = writable<string>(items[1]);
-		const { getByTestId } = render(ToggleGroupTest, {
+		const { getByTestId } = setup({
 			type: 'single',
-			items,
 			value: valueStore,
 		});
 
@@ -221,9 +219,8 @@ describe('Toggle Group', () => {
 
 	test('value prop programmatically updates - multiple', async () => {
 		const valueStore = writable<string[]>([items[1], items[2]]);
-		const { getByTestId } = render(ToggleGroupTest, {
+		const { getByTestId } = setup({
 			type: 'multiple',
-			items,
 			value: valueStore,
 		});
 
@@ -240,10 +237,8 @@ describe('Toggle Group', () => {
 	});
 
 	test('change function is respected - single', async () => {
-		const user = userEvent.setup();
-		const { getByTestId } = render(ToggleGroupTest, {
+		const { getByTestId, user } = setup({
 			type: 'single',
-			items,
 			onValueChange: () => items[0],
 		});
 
@@ -258,10 +253,8 @@ describe('Toggle Group', () => {
 	});
 
 	test('change function is respected - multiple', async () => {
-		const user = userEvent.setup();
-		const { getByTestId } = render(ToggleGroupTest, {
+		const { user, getByTestId } = setup({
 			type: 'multiple',
-			items,
 			onValueChange: () => [items[0], items[1]],
 		});
 
@@ -275,4 +268,22 @@ describe('Toggle Group', () => {
 		expect(getByTestId(items[0])).toHaveAttribute('aria-pressed', 'true');
 		expect(getByTestId(items[1])).toHaveAttribute('aria-pressed', 'true');
 	});
+
+	test('toggle group focusable when in single mode', async () => {
+		const { user, tabButton, getByTestId } = setup();
+
+		tabButton.focus();
+		expect(tabButton).toHaveFocus();
+		await user.keyboard(kbd.TAB);
+		expect(getByTestId(items[0])).toHaveFocus();
+	});
+
+	test('toggle group focusable when in multiple mode', async () => {
+		const { user, tabButton, getByTestId } = setup({ type: 'multiple', items });
+
+		tabButton.focus();
+		expect(tabButton).toHaveFocus();
+		await user.keyboard(kbd.TAB);
+		expect(getByTestId(items[0])).toHaveFocus();
+	});
 });
diff --git a/src/tests/toggle-group/ToggleGroupTest.svelte b/src/tests/toggle-group/ToggleGroupTest.svelte
index 801dee21..c70df696 100644
--- a/src/tests/toggle-group/ToggleGroupTest.svelte
+++ b/src/tests/toggle-group/ToggleGroupTest.svelte
@@ -1,7 +1,11 @@
-<script lang="ts">
-	import { createToggleGroup, melt, type CreateToggleGroupProps } from '$lib/index.js';
+<script lang="ts" context="module">
+	type T = unknown;
+</script>
 
-	type T = $$Generic<'single' | 'multiple'>;
+<script lang="ts" generics="T extends 'single' | 'multiple'">
+	import { removeUndefined } from '../utils.js';
+
+	import { createToggleGroup, melt, type CreateToggleGroupProps } from '$lib/index.js';
 
 	export let type: T;
 	export let defaultValue: CreateToggleGroupProps<T>['defaultValue'] = undefined;
@@ -12,20 +16,29 @@
 	export let items: string[] = ['item-1', 'item-2', 'item-3'];
 	export let value: CreateToggleGroupProps<T>['value'] = undefined;
 
+	type $$Props = CreateToggleGroupProps<T> & {
+		items: string[];
+		type: T;
+	};
+
 	const {
 		elements: { root, item },
 	} = createToggleGroup<T>({
-		type,
-		defaultValue,
-		disabled,
-		loop,
-		onValueChange,
-		orientation,
-		value,
+		...removeUndefined({
+			type,
+			defaultValue,
+			disabled,
+			loop,
+			onValueChange,
+			orientation,
+			value,
+			...$$restProps,
+		}),
 	});
 </script>
 
 <main>
+	<button data-testid="tab-btn">Tab Focus Step button</button>
 	<div use:melt={$root} aria-label="root-1" data-testid="root">
 		{#each items as tItem}
 			<button use:melt={$item(tItem)} data-testid={tItem}>