Repository: melt-ui/melt-ui
Pull request #1236 (2024-06-23 23:47:45)

fix(Calendar): sometimes first day of next month is missing
Solves https://github.com/melt-ui/melt-ui/issues/1213.

This was solved by me in https://github.com/radix-vue/radix-vue/pull/1029 but @epr3 suggest you might also need this fix. 

Root cause: when 6th week will start in new month then `startFrom` will be `undefined`. Currently `if (!startFrom)` was using first day of next month, but then in creation of extraDaysArray all dates were incremented which resulted in first day of new month being missed.

I am not familliar with Svelte so I do not have any test case for this. But you might want to cross check with how it **might be** tested in radix-vue: https://github.com/radix-vue/radix-vue/pull/1029/files#diff-a067dd33cc3ddceb2d881b1d1984509071d713079564a97d9ea182084284e2f8

At least using August 2024 instead of January 1980 might help if you are into this, but my conclusion is it most probably will not be worth it, especially from performance perspective. 

changeset-bot[bot]: ###  ⚠️  No Changeset found

Latest commit: 389aca7a17a36bbde269f1cb29e35e1be12701e2

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/xollaborator/melt-ui/new/develop?filename=.changeset/curly-mails-jump.md&value=---%0A%22%40melt-ui%2Fsvelte%22%3A%20patch%0A---%0A%0Afix(Calendar)%3A%20sometimes%20first%20day%20of%20next%20month%20is%20missing%0A)



Patch:
diff --git a/.changeset/clean-tomatoes-lie.md b/.changeset/clean-tomatoes-lie.md
new file mode 100644
index 00000000..79b5031a
--- /dev/null
+++ b/.changeset/clean-tomatoes-lie.md
@@ -0,0 +1,5 @@
+---
+"@melt-ui/svelte": minor
+---
+
+Added prop to createToaster() to pause all timers on hover
diff --git a/.changeset/curly-mails-jump.md b/.changeset/curly-mails-jump.md
new file mode 100644
index 00000000..8763b17c
--- /dev/null
+++ b/.changeset/curly-mails-jump.md
@@ -0,0 +1,5 @@
+---
+"@melt-ui/svelte": patch
+---
+
+fix(Calendar): sometimes first day of next month is missing
diff --git a/.changeset/fifty-suits-reflect.md b/.changeset/fifty-suits-reflect.md
new file mode 100644
index 00000000..4ba93a28
--- /dev/null
+++ b/.changeset/fifty-suits-reflect.md
@@ -0,0 +1,5 @@
+---
+'@melt-ui/svelte': patch
+---
+
+fix(slider): `onValueCommitted` called if the slider had an active state
diff --git a/.changeset/khaki-maps-relate.md b/.changeset/khaki-maps-relate.md
new file mode 100644
index 00000000..2a52dd91
--- /dev/null
+++ b/.changeset/khaki-maps-relate.md
@@ -0,0 +1,5 @@
+---
+"@melt-ui/svelte": minor
+---
+
+feat(tags-input): split on paste
diff --git a/.changeset/smooth-cooks-develop.md b/.changeset/smooth-cooks-develop.md
new file mode 100644
index 00000000..3fdb6bd8
--- /dev/null
+++ b/.changeset/smooth-cooks-develop.md
@@ -0,0 +1,5 @@
+---
+'@melt-ui/svelte': minor
+---
+
+add selection helpers to range calendar
diff --git a/.changeset/tasty-tools-accept.md b/.changeset/tasty-tools-accept.md
new file mode 100644
index 00000000..bc2d23fa
--- /dev/null
+++ b/.changeset/tasty-tools-accept.md
@@ -0,0 +1,5 @@
+---
+'@melt-ui/svelte': minor
+---
+
+add `name` prop to radio group builder hidden input element
diff --git a/package.json b/package.json
index 0eaaf289..95c5a84a 100644
--- a/package.json
+++ b/package.json
@@ -8,6 +8,7 @@
 		"dev": "vite dev",
 		"build": "vite build && npm run package",
 		"deploy": "vite build",
+		"prepare": "svelte-kit sync",
 		"preview": "vite preview",
 		"package": "svelte-kit sync && svelte-package && publint",
 		"prepublishOnly": "npm run package",
diff --git a/src/docs/data/builders/combobox.ts b/src/docs/data/builders/combobox.ts
index 7e057994..39123596 100644
--- a/src/docs/data/builders/combobox.ts
+++ b/src/docs/data/builders/combobox.ts
@@ -31,6 +31,11 @@ const OPTION_PROPS = [
 		description:
 			'When true, hovering an option will update the `highlightedItem` store, and when the cursor leaves an option the store will be set to `null`',
 	},
+	{
+		name: 'name',
+		type: 'string',
+		description: 'The name to be used for the hidden input.',
+	},
 ];
 
 const BUILDER_NAME = 'combobox';
diff --git a/src/docs/data/builders/radio-group.ts b/src/docs/data/builders/radio-group.ts
index 66262751..9b9165cf 100644
--- a/src/docs/data/builders/radio-group.ts
+++ b/src/docs/data/builders/radio-group.ts
@@ -17,6 +17,11 @@ const OPTION_PROPS = [
 		default: '"vertical"',
 		description: 'The orientation of the radio group.',
 	},
+	{
+		name: 'name',
+		type: 'string',
+		description: 'The name to be used for the hidden input.',
+	},
 ];
 const BUILDER_NAME = 'radio group';
 
diff --git a/src/docs/data/builders/range-calendar.ts b/src/docs/data/builders/range-calendar.ts
index 3c749aae..d9f529f6 100644
--- a/src/docs/data/builders/range-calendar.ts
+++ b/src/docs/data/builders/range-calendar.ts
@@ -221,10 +221,20 @@ const builder = builderSchema(BUILDER_NAME, {
 			type: 'Readable<(date: DateValue) => boolean>',
 		},
 		{
-			name: 'isDateSelected',
+			name: 'isSelected',
 			description: 'A function that returns whether the given date is selected.',
 			type: 'Readable<(date: DateValue) => boolean>',
 		},
+		{
+			name: 'isSeletionStart',
+			description:
+				'A function that returns whether the given date is the date at the start of the selection range.',
+		},
+		{
+			name: 'isSeletionEnd',
+			description:
+				'A function that returns whether the given date is the date at the end of the selection range.',
+		},
 	],
 });
 
diff --git a/src/docs/data/builders/toast.ts b/src/docs/data/builders/toast.ts
index 04f445dd..0653a39a 100644
--- a/src/docs/data/builders/toast.ts
+++ b/src/docs/data/builders/toast.ts
@@ -20,6 +20,12 @@ const OPTION_PROPS = [
 		default: "'foreground'",
 		description: 'The sensitivity of the toast for accessibility purposes.',
 	},
+	{
+		name: 'hover',
+		type: ["'pause'", "'pause-all'", 'null'],
+		default: "'pause'",
+		description: 'The behavior when a toast is hovered.',
+	},
 ];
 
 const BUILDER_NAME = 'toast';
@@ -129,7 +135,7 @@ const keyboard: KeyboardSchema = [
 ];
 
 const schemas = [builder, content, title, description, close];
-const features = ['Automatically closes', 'Resets closing on hover'];
+const features = ['Automatically closes', 'Pause closing on hover (single, all, none)'];
 
 export const toastData: BuilderData = {
 	schemas,
diff --git a/src/docs/previews/toast/progress/tailwind/index.svelte b/src/docs/previews/toast/progress/tailwind/index.svelte
index e659ddda..58473fa8 100644
--- a/src/docs/previews/toast/progress/tailwind/index.svelte
+++ b/src/docs/previews/toast/progress/tailwind/index.svelte
@@ -7,16 +7,20 @@
 </script>
 
 <script lang="ts">
-	import { createToaster } from '$lib/index.js';
+	import { writable, type Writable } from 'svelte/store';
+	import { createToaster, type CreateToasterProps } from '$lib/index.js';
 	import { flip } from 'svelte/animate';
 	import Toast from './toast.svelte';
+	import SelectHover from './select-hover.svelte';
 
-	const {
+	const hover: Writable<CreateToasterProps['hover']> = writable('pause');
+
+	$: ({
 		elements,
 		helpers: { addToast },
 		states: { toasts },
 		actions: { portal },
-	} = createToaster<ToastData>();
+	} = createToaster<ToastData>({ hover: $hover }));
 
 	const toastData: ToastData[] = [
 		{
@@ -41,6 +45,8 @@
 	}
 </script>
 
+<SelectHover bind:value={$hover} />
+
 <button
 	class="inline-flex items-center justify-center rounded-xl bg-white px-4 py-3
 	font-medium leading-none text-magnum-700 shadow hover:opacity-75"
diff --git a/src/docs/previews/toast/progress/tailwind/select-hover.svelte b/src/docs/previews/toast/progress/tailwind/select-hover.svelte
new file mode 100644
index 00000000..104da266
--- /dev/null
+++ b/src/docs/previews/toast/progress/tailwind/select-hover.svelte
@@ -0,0 +1,82 @@
+<script lang="ts">
+	import { Check, ChevronDown } from '$icons/index.js';
+	import { createSelect, melt, type CreateToasterProps } from '$lib/index.js';
+	import { fade } from 'svelte/transition';
+
+	const options: { value: CreateToasterProps['hover']; label: string }[] = [
+		{ value: 'pause', label: 'pause' },
+		{ value: 'pause-all', label: 'pause-all' },
+		{ value: null, label: 'null' },
+	];
+
+	const {
+		elements: { trigger, menu, option, label },
+		states: { selected, selectedLabel, open },
+		helpers: { isSelected },
+	} = createSelect<CreateToasterProps['hover']>({
+		forceVisible: true,
+		positioning: {
+			placement: 'bottom',
+			fitViewport: true,
+			sameWidth: true,
+		},
+		defaultSelected: { value: 'pause', label: 'pause' },
+	});
+
+	export let value: CreateToasterProps['hover'];
+
+	$: value = $selected?.value;
+</script>
+
+<div class="absolute left-4 top-4">
+	<div class="flex flex-col gap-1">
+		<!-- svelte-ignore a11y-label-has-associated-control - $label contains the 'for' attribute -->
+		<label class="block text-white" use:melt={$label}> Hover behavior : </label>
+		<button
+			class="flex h-10 min-w-[220px] items-center justify-between rounded-lg bg-white px-3 py-2
+	text-magnum-700 shadow transition-opacity hover:opacity-90"
+			use:melt={$trigger}
+			aria-label="Hover behavior"
+		>
+			{$selectedLabel || 'Select hover behavior'}
+			<ChevronDown class="size-5" />
+		</button>
+		{#if $open}
+			<div
+				class="force-dark z-10 flex max-h-[300px] flex-col
+		overflow-y-auto rounded-lg bg-white p-1
+		shadow focus:!ring-0"
+				use:melt={$menu}
+				transition:fade={{ duration: 150 }}
+			>
+				{#each options as { value, label }}
+					<div
+						class="relative cursor-pointer rounded-lg py-1 pl-8 pr-4 text-neutral-800
+							hover:bg-magnum-100 focus:z-10
+							focus:text-magnum-700
+							data-[highlighted]:bg-magnum-200 data-[highlighted]:text-magnum-900
+							data-[disabled]:opacity-50"
+						use:melt={$option({ value, label })}
+					>
+						<div class="check {$isSelected(value) ? 'block' : 'hidden'}">
+							<Check class="size-4" />
+						</div>
+
+						{label}
+					</div>
+				{/each}
+			</div>
+		{/if}
+	</div>
+</div>
+
+<style lang="postcss">
+	.check {
+		position: absolute;
+		left: theme(spacing.2);
+		top: 50%;
+		z-index: theme(zIndex.20);
+		translate: 0 calc(-50% + 1px);
+		color: theme(colors.magnum.500);
+	}
+</style>
diff --git a/src/lib/builders/radio-group/create.ts b/src/lib/builders/radio-group/create.ts
index 331a5fdf..0b19c758 100644
--- a/src/lib/builders/radio-group/create.ts
+++ b/src/lib/builders/radio-group/create.ts
@@ -16,7 +16,7 @@ import {
 } from '$lib/internal/helpers/index.js';
 import { safeOnMount } from '$lib/internal/helpers/lifecycle.js';
 import type { Defaults, MeltActionReturn } from '$lib/internal/types.js';
-import { derived, writable } from 'svelte/store';
+import { derived, readonly, writable } from 'svelte/store';
 import { createHiddenInput } from '../hidden-input/create.js';
 import type { RadioGroupEvents } from './events.js';
 import type { CreateRadioGroupProps, RadioGroupItemProps } from './types.js';
@@ -27,6 +27,7 @@ const defaults = {
 	disabled: false,
 	required: false,
 	defaultValue: undefined,
+	name: undefined,
 } satisfies Defaults<CreateRadioGroupProps>;
 
 type RadioGroupParts = 'item' | 'hidden-input';
@@ -38,7 +39,7 @@ export function createRadioGroup(props?: CreateRadioGroupProps) {
 
 	// options
 	const options = toWritableStores(omit(withDefaults, 'value'));
-	const { disabled, required, loop, orientation } = options;
+	const { disabled, required, loop, orientation, name: nameProp } = options;
 
 	const valueWritable = withDefaults.value ?? writable(withDefaults.defaultValue);
 	const value = overridable(valueWritable, withDefaults?.onValueChange);
@@ -177,6 +178,7 @@ export function createRadioGroup(props?: CreateRadioGroupProps) {
 
 	const hiddenInput = createHiddenInput({
 		value,
+		name: readonly(nameProp),
 		disabled,
 		required,
 	});
diff --git a/src/lib/builders/radio-group/types.ts b/src/lib/builders/radio-group/types.ts
index f41ae499..6a743655 100644
--- a/src/lib/builders/radio-group/types.ts
+++ b/src/lib/builders/radio-group/types.ts
@@ -20,6 +20,8 @@ export type CreateRadioGroupProps = {
 	 */
 	required?: boolean;
 
+	name?: string;
+
 	/**
 	 * Whether or not the radio group should loop around when the end
 	 * is reached.
diff --git a/src/lib/builders/range-calendar/create.ts b/src/lib/builders/range-calendar/create.ts
index 1353d0b5..30c4f41c 100644
--- a/src/lib/builders/range-calendar/create.ts
+++ b/src/lib/builders/range-calendar/create.ts
@@ -1096,6 +1096,9 @@ export function createRangeCalendar<T extends DateValue = DateValue>(
 			setMonth,
 			isDateDisabled: _isDateDisabled,
 			isDateUnavailable,
+			isSelectionStart,
+			isSelectionEnd,
+			isSelected
 		},
 		options,
 		ids,
diff --git a/src/lib/builders/slider/create.ts b/src/lib/builders/slider/create.ts
index 459a9407..1046870f 100644
--- a/src/lib/builders/slider/create.ts
+++ b/src/lib/builders/slider/create.ts
@@ -521,11 +521,11 @@ export const createSlider = (props?: CreateSliderProps) => {
 			};
 
 			const pointerUp = () => {
-				isActive.set(false);
-
-				if (withDefaults?.onValueCommitted) {
+				if (withDefaults?.onValueCommitted && isActive.get()) {
 					withDefaults.onValueCommitted(value.get());
 				}
+
+				isActive.set(false);
 			};
 
 			const unsub = executeCallbacks(
diff --git a/src/lib/builders/tags-input/create.ts b/src/lib/builders/tags-input/create.ts
index ec7b0ba4..35741a91 100644
--- a/src/lib/builders/tags-input/create.ts
+++ b/src/lib/builders/tags-input/create.ts
@@ -339,11 +339,17 @@ export function createTagsInput(props?: CreateTagsInputProps) {
 					if (!addOnPaste.get()) return;
 					e.preventDefault();
 
-					// Update value with the pasted text or set invalid
-					if (isInputValid(pastedText) && (await addTag(pastedText))) {
-						node.value = '';
-					} else {
-						inputInvalid.set(true);
+					const newTags = pastedText.split(',')
+					addTag: for (let i = 0; i < newTags.length; i++) {
+						const newTag = newTags[i];
+						// Update value with the pasted tag or set invalid
+						if (isInputValid(newTag) && (await addTag(newTag))) {
+							continue addTag;
+						} else {
+							node.value = newTags.slice(i).join(',');
+							inputInvalid.set(true);
+							return;
+						}
 					}
 				}),
 				addMeltEventListener(node, 'keydown', async (e) => {
@@ -498,11 +504,11 @@ export function createTagsInput(props?: CreateTagsInputProps) {
 					tabindex: -1,
 					style: editing
 						? styleToString({
-								position: 'absolute',
-								opacity: 0,
-								'pointer-events': 'none',
-								margin: 0,
-						  })
+							position: 'absolute',
+							opacity: 0,
+							'pointer-events': 'none',
+							margin: 0,
+						})
 						: undefined,
 				} as const;
 			};
@@ -638,11 +644,11 @@ export function createTagsInput(props?: CreateTagsInputProps) {
 					tabindex: -1,
 					style: !editing
 						? styleToString({
-								position: 'absolute',
-								opacity: 0,
-								'pointer-events': 'none',
-								margin: 0,
-						  })
+							position: 'absolute',
+							opacity: 0,
+							'pointer-events': 'none',
+							margin: 0,
+						})
 						: undefined,
 				} as const;
 			};
diff --git a/src/lib/builders/toast/create.ts b/src/lib/builders/toast/create.ts
index 2576e73a..640ce466 100644
--- a/src/lib/builders/toast/create.ts
+++ b/src/lib/builders/toast/create.ts
@@ -21,13 +21,14 @@ const { name } = createElHelpers<ToastParts>('toast');
 const defaults = {
 	closeDelay: 5000,
 	type: 'foreground',
+	hover: 'pause',
 } satisfies CreateToasterProps;
 
 export function createToaster<T = object>(props?: CreateToasterProps) {
 	const withDefaults = { ...defaults, ...props } satisfies CreateToasterProps;
 
 	const options = toWritableStores(withDefaults);
-	const { closeDelay, type } = options;
+	const { closeDelay, type, hover } = options;
 
 	const toastsMap = writable(new Map<string, Toast<T>>());
 
@@ -98,6 +99,23 @@ export function createToaster<T = object>(props?: CreateToasterProps) {
 		});
 	};
 
+	const pauseToastTimer = (currentToast: Toast<T>) => {
+		if (currentToast.timeout !== null) {
+			window.clearTimeout(currentToast.timeout);
+		}
+		currentToast.pausedAt = performance.now();
+	};
+	const restartToastTimer = (currentToast: Toast<T>) => {
+		const pausedAt = currentToast.pausedAt ?? currentToast.createdAt;
+		const elapsed = pausedAt - currentToast.createdAt - currentToast.pauseDuration;
+		const remaining = currentToast.closeDelay - elapsed;
+		currentToast.timeout = window.setTimeout(() => {
+			removeToast(currentToast.id);
+		}, remaining);
+		currentToast.pauseDuration += performance.now() - pausedAt;
+		currentToast.pausedAt = undefined;
+	};
+
 	const content = makeElement(name('content'), {
 		stores: toastsMap,
 		returned: ($toasts) => {
@@ -123,31 +141,42 @@ export function createToaster<T = object>(props?: CreateToasterProps) {
 				addMeltEventListener(node, 'pointerenter', (e) => {
 					if (isTouch(e)) return;
 					toastsMap.update((currentMap) => {
-						const currentToast = currentMap.get(node.id);
-						if (!currentToast || currentToast.closeDelay === 0) return currentMap;
-
-						if (currentToast.timeout !== null) {
-							window.clearTimeout(currentToast.timeout);
+						switch (hover.get()) {
+							case 'pause': {
+								const currentToast = currentMap.get(node.id);
+								if (!currentToast || currentToast.closeDelay === 0) return currentMap;
+								pauseToastTimer(currentToast);
+								break;
+							}
+							case 'pause-all':
+								for (const [, currentToast] of currentMap) {
+									if (!currentToast || currentToast.closeDelay === 0) continue;
+									pauseToastTimer(currentToast);
+								}
+								break;
 						}
-						currentToast.pausedAt = performance.now();
 						return new Map(currentMap);
 					});
 				}),
 				addMeltEventListener(node, 'pointerleave', (e) => {
 					if (isTouch(e)) return;
 					toastsMap.update((currentMap) => {
-						const currentToast = currentMap.get(node.id);
-						if (!currentToast || currentToast.closeDelay === 0) return currentMap;
-
-						const pausedAt = currentToast.pausedAt ?? currentToast.createdAt;
-						const elapsed = pausedAt - currentToast.createdAt - currentToast.pauseDuration;
-						const remaining = currentToast.closeDelay - elapsed;
-						currentToast.timeout = window.setTimeout(() => {
-							removeToast(node.id);
-						}, remaining);
-
-						currentToast.pauseDuration += performance.now() - pausedAt;
-						currentToast.pausedAt = undefined;
+						switch (hover.get()) {
+							case 'pause': {
+								const currentToast = currentMap.get(node.id);
+								if (!currentToast || currentToast.closeDelay === 0) return currentMap;
+								restartToastTimer(currentToast);
+								break;
+							}
+
+							case 'pause-all':
+								for (const [, currentToast] of currentMap) {
+									if (!currentToast || currentToast.closeDelay === 0) continue;
+									restartToastTimer(currentToast);
+								}
+								break;
+						}
+
 						return new Map(currentMap);
 					});
 				}),
diff --git a/src/lib/builders/toast/types.ts b/src/lib/builders/toast/types.ts
index 82af1ec8..fd56622b 100644
--- a/src/lib/builders/toast/types.ts
+++ b/src/lib/builders/toast/types.ts
@@ -8,9 +8,10 @@ export type CreateToasterProps = {
 	// If set to 0, the toast will not be automatically closed.
 	closeDelay?: number;
 	type?: 'foreground' | 'background';
+	hover?: 'pause' | 'pause-all' | null;
 };
 
-export type AddToastProps<T = object> = CreateToasterProps & {
+export type AddToastProps<T = object> = Omit<CreateToasterProps, 'hover'> & {
 	data: T;
 };
 
diff --git a/src/lib/internal/helpers/date/calendar.ts b/src/lib/internal/helpers/date/calendar.ts
index 4e056364..532cf6c8 100644
--- a/src/lib/internal/helpers/date/calendar.ts
+++ b/src/lib/internal/helpers/date/calendar.ts
@@ -94,7 +94,7 @@ function createMonth(props: CreateMonthProps): Month<DateValue> {
 		let startFrom = nextMonthDays[nextMonthDays.length - 1];
 
 		if (!startFrom) {
-			startFrom = dateObj.add({ months: 1 }).set({ day: 1 });
+			startFrom = endOfMonth(dateObj);
 		}
 
 		const extraDaysArray = Array.from({ length: extraDays }, (_, i) => {


Test patch:
diff --git a/src/tests/radio-group/RadioGroup.spec.ts b/src/tests/radio-group/RadioGroup.spec.ts
index deab96dd..29018ee2 100644
--- a/src/tests/radio-group/RadioGroup.spec.ts
+++ b/src/tests/radio-group/RadioGroup.spec.ts
@@ -84,4 +84,9 @@ describe('Radio Group', () => {
 		expect(els[2]).toHaveFocus();
 		expect(els[1]).not.toHaveFocus();
 	});
+
+	it('has specified name on hidden input', async () => {
+		const { getByTestId } = setup({ name: 'name' });
+		expect(getByTestId('input')).toHaveAttribute('name', 'name');
+	});
 });
diff --git a/src/tests/radio-group/RadioGroupTest.svelte b/src/tests/radio-group/RadioGroupTest.svelte
index 111c1785..b1acb7ab 100644
--- a/src/tests/radio-group/RadioGroupTest.svelte
+++ b/src/tests/radio-group/RadioGroupTest.svelte
@@ -16,6 +16,7 @@
 	export let disabled: CreateRadioGroupProps['disabled'] = undefined;
 	export let onValueChange: CreateRadioGroupProps['onValueChange'] = undefined;
 	export let required: CreateRadioGroupProps['required'] = undefined;
+	export let name: CreateRadioGroupProps['name'] = undefined;
 	export let items: Item[] = [
 		{ value: 'a', disabled: false },
 		{ value: 'b', disabled: false },
@@ -24,7 +25,7 @@
 	];
 
 	const {
-		elements: { root, item },
+		elements: { root, item, hiddenInput },
 		states: { value: localValue },
 	} = createRadioGroup({
 		value,
@@ -32,6 +33,7 @@
 		onValueChange,
 		disabled,
 		required,
+		name,
 		...removeUndefined($$restProps),
 	});
 </script>
@@ -39,6 +41,7 @@
 <main>
 	<div data-testid="value">{$localValue}</div>
 	<form>
+		<input use:melt={$hiddenInput} data-testid="input" />
 		<label id="radio-group-label" for="radio-group" data-testid="label"> Airplane mode </label>
 		<div use:melt={$root} data-testid="root">
 			{#each items as radioItem}