Skip to content

Commit 7ce3c71

Browse files
authored
fix: ensure default values are set if spread props reassigned (#1401)
1 parent 6074255 commit 7ce3c71

File tree

13 files changed

+240
-34
lines changed

13 files changed

+240
-34
lines changed

.changeset/dirty-guests-kick.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"bits-ui": patch
3+
---
4+
5+
fix(Multiple Components): ensure default values are set if entire spread props object is reassigned outside the component

packages/bits-ui/src/lib/bits/accordion/components/accordion.svelte

+15-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import type { AccordionRootProps } from "../types.js";
55
import { useId } from "$lib/internal/use-id.js";
66
import { noop } from "$lib/internal/noop.js";
7+
import { watch } from "runed";
78
89
let {
910
disabled = false,
@@ -19,7 +20,20 @@
1920
...restProps
2021
}: AccordionRootProps = $props();
2122
22-
value === undefined && (value = type === "single" ? "" : []);
23+
function handleDefaultValue() {
24+
if (value !== undefined) return;
25+
value = type === "single" ? "" : [];
26+
}
27+
28+
// SSR
29+
handleDefaultValue();
30+
31+
watch.pre(
32+
() => value,
33+
() => {
34+
handleDefaultValue();
35+
}
36+
);
2337
2438
const rootState = useAccordionRoot({
2539
type,

packages/bits-ui/src/lib/bits/calendar/components/calendar.svelte

+26-5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { useId } from "$lib/internal/use-id.js";
77
import { noop } from "$lib/internal/noop.js";
88
import { getDefaultDate } from "$lib/internal/date-time/utils.js";
9+
import { watch } from "runed";
910
1011
let {
1112
child,
@@ -40,16 +41,36 @@
4041
defaultValue: value,
4142
});
4243
43-
if (placeholder === undefined) {
44+
function handleDefaultPlaceholder() {
45+
if (placeholder !== undefined) return;
4446
placeholder = defaultPlaceholder;
4547
}
4648
47-
if (value === undefined) {
48-
const defaultValue = type === "single" ? undefined : [];
49-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
50-
value = defaultValue as any;
49+
// SSR
50+
handleDefaultPlaceholder();
51+
52+
watch.pre(
53+
() => placeholder,
54+
() => {
55+
handleDefaultPlaceholder();
56+
}
57+
);
58+
59+
function handleDefaultValue() {
60+
if (value !== undefined) return;
61+
value = type === "single" ? undefined : [];
5162
}
5263
64+
// SSR
65+
handleDefaultValue();
66+
67+
watch.pre(
68+
() => value,
69+
() => {
70+
handleDefaultValue();
71+
}
72+
);
73+
5374
const rootState = useCalendarRoot({
5475
id: box.with(() => id),
5576
ref: box.with(

packages/bits-ui/src/lib/bits/combobox/components/combobox.svelte

+9
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import FloatingLayer from "$lib/bits/utilities/floating-layer/components/floating-layer.svelte";
66
import { useSelectRoot } from "$lib/bits/select/select.svelte.js";
77
import ListboxHiddenInput from "$lib/bits/select/components/select-hidden-input.svelte";
8+
import { watch } from "runed";
89
910
let {
1011
value = $bindable(),
@@ -27,6 +28,14 @@
2728
value = defaultValue;
2829
}
2930
31+
watch.pre(
32+
() => value,
33+
() => {
34+
if (value !== undefined) return;
35+
value = type === "single" ? "" : [];
36+
}
37+
);
38+
3039
const rootState = useSelectRoot({
3140
type,
3241
value: box.with(

packages/bits-ui/src/lib/bits/date-field/components/date-field.svelte

+19-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import type { DateFieldRootProps } from "../types.js";
66
import { noop } from "$lib/internal/noop.js";
77
import { getDefaultDate } from "$lib/internal/date-time/utils.js";
8+
import { watch } from "runed";
89
910
let {
1011
disabled = false,
@@ -27,7 +28,9 @@
2728
children,
2829
}: DateFieldRootProps = $props();
2930
30-
if (placeholder === undefined) {
31+
function handleDefaultPlaceholder() {
32+
if (placeholder !== undefined) return;
33+
3134
const defaultPlaceholder = getDefaultDate({
3235
granularity,
3336
defaultValue: value,
@@ -36,6 +39,21 @@
3639
placeholder = defaultPlaceholder;
3740
}
3841
42+
// SSR
43+
handleDefaultPlaceholder();
44+
45+
/**
46+
* Covers an edge case where when a spread props object is reassigned,
47+
* the props are reset to their default values, which would make placeholder
48+
* undefined which causes errors to be thrown.
49+
*/
50+
watch.pre(
51+
() => placeholder,
52+
() => {
53+
handleDefaultPlaceholder();
54+
}
55+
);
56+
3957
useDateFieldRoot({
4058
value: box.with(
4159
() => value,

packages/bits-ui/src/lib/bits/date-picker/components/date-picker.svelte

+18-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import { useDateFieldRoot } from "$lib/bits/date-field/date-field.svelte.js";
1010
import { FloatingLayer } from "$lib/bits/utilities/floating-layer/index.js";
1111
import { getDefaultDate } from "$lib/internal/date-time/utils.js";
12+
import { watch } from "runed";
1213
1314
let {
1415
open = $bindable(false),
@@ -50,10 +51,26 @@
5051
defaultValue: value,
5152
});
5253
53-
if (placeholder === undefined) {
54+
function handleDefaultPlaceholder() {
55+
if (placeholder !== undefined) return;
5456
placeholder = defaultPlaceholder;
5557
}
5658
59+
// SSR
60+
handleDefaultPlaceholder();
61+
62+
/**
63+
* Covers an edge case where when a spread props object is reassigned,
64+
* the props are reset to their default values, which would make placeholder
65+
* undefined which causes errors to be thrown.
66+
*/
67+
watch.pre(
68+
() => placeholder,
69+
() => {
70+
handleDefaultPlaceholder();
71+
}
72+
);
73+
5774
function onDateSelect() {
5875
if (closeOnDateSelect) {
5976
open = false;

packages/bits-ui/src/lib/bits/date-range-field/components/date-range-field.svelte

+31-8
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import { noop } from "$lib/internal/noop.js";
88
import type { DateRange } from "$lib/shared/index.js";
99
import { getDefaultDate } from "$lib/internal/date-time/utils.js";
10+
import { watch } from "runed";
1011
1112
let {
1213
id = useId(),
@@ -38,21 +39,43 @@
3839
let startValue = $state<DateValue | undefined>(value?.start);
3940
let endValue = $state<DateValue | undefined>(value?.end);
4041
41-
if (placeholder === undefined) {
42-
const defaultPlaceholder = getDefaultDate({
43-
granularity,
44-
defaultValue: value?.start,
45-
});
46-
42+
function handleDefaultPlaceholder() {
43+
if (placeholder !== undefined) return;
44+
const defaultPlaceholder = getDefaultDate({ granularity, defaultValue: value?.start });
4745
placeholder = defaultPlaceholder;
4846
}
4947
50-
if (value === undefined) {
51-
const defaultValue = { start: undefined, end: undefined };
48+
// SSR
49+
handleDefaultPlaceholder();
50+
51+
watch.pre(
52+
() => placeholder,
53+
() => {
54+
handleDefaultPlaceholder();
55+
}
56+
);
5257
58+
function handleDefaultValue() {
59+
if (value !== undefined) return;
60+
const defaultValue = { start: undefined, end: undefined };
5361
value = defaultValue;
5462
}
5563
64+
// SSR
65+
handleDefaultValue();
66+
67+
/**
68+
* Covers an edge case where when a spread props object is reassigned,
69+
* the props are reset to their default values, which would make value
70+
* undefined which causes errors to be thrown.
71+
*/
72+
watch.pre(
73+
() => value,
74+
() => {
75+
handleDefaultValue();
76+
}
77+
);
78+
5679
const rootState = useDateRangeFieldRoot({
5780
id: box.with(() => id),
5881
ref: box.with(

packages/bits-ui/src/lib/bits/date-range-picker/components/date-range-picker.svelte

+36-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import { useId } from "$lib/internal/use-id.js";
1111
import type { DateRange } from "$lib/shared/index.js";
1212
import { getDefaultDate } from "$lib/internal/date-time/utils.js";
13+
import { watch } from "runed";
1314
1415
let {
1516
open = $bindable(false),
@@ -54,18 +55,51 @@
5455
let startValue = $state<DateValue | undefined>(value?.start);
5556
let endValue = $state<DateValue | undefined>(value?.end);
5657
57-
if (value === undefined) {
58+
function handleDefaultValue() {
59+
if (value !== undefined) return;
5860
value = { start: undefined, end: undefined };
5961
}
62+
63+
// SSR
64+
handleDefaultValue();
65+
66+
/**
67+
* Covers an edge case where when a spread props object is reassigned,
68+
* the props are reset to their default values, which would make value
69+
* undefined which causes errors to be thrown.
70+
*/
71+
watch.pre(
72+
() => value,
73+
() => {
74+
handleDefaultValue();
75+
}
76+
);
77+
6078
const defaultPlaceholder = getDefaultDate({
6179
granularity,
6280
defaultValue: value?.start,
6381
});
6482
65-
if (placeholder === undefined) {
83+
function handleDefaultPlaceholder() {
84+
if (placeholder !== undefined) return;
6685
placeholder = defaultPlaceholder;
6786
}
6887
88+
// SSR
89+
handleDefaultPlaceholder();
90+
91+
/**
92+
* Covers an edge case where when a spread props object is reassigned,
93+
* the props are reset to their default values, which would make placeholder
94+
* undefined which causes errors to be thrown.
95+
*/
96+
watch.pre(
97+
() => placeholder,
98+
() => {
99+
handleDefaultPlaceholder();
100+
}
101+
);
102+
69103
function onRangeSelect() {
70104
if (closeOnRangeSelect) {
71105
open = false;

packages/bits-ui/src/lib/bits/range-calendar/components/range-calendar.svelte

+26-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { noop } from "$lib/internal/noop.js";
77
import { useId } from "$lib/internal/use-id.js";
88
import { getDefaultDate } from "$lib/internal/date-time/utils.js";
9+
import { watch } from "runed";
910
1011
let {
1112
children,
@@ -43,15 +44,36 @@
4344
defaultValue: value?.start,
4445
});
4546
46-
if (placeholder === undefined) {
47+
function handleDefaultPlaceholder() {
48+
if (placeholder !== undefined) return;
4749
placeholder = defaultPlaceholder;
4850
}
4951
50-
if (value === undefined) {
51-
const defaultValue = { start: undefined, end: undefined };
52-
value = defaultValue;
52+
// SSR
53+
handleDefaultPlaceholder();
54+
55+
watch.pre(
56+
() => placeholder,
57+
() => {
58+
handleDefaultPlaceholder();
59+
}
60+
);
61+
62+
function handleDefaultValue() {
63+
if (value !== undefined) return;
64+
value = { start: undefined, end: undefined };
5365
}
5466
67+
// SSR
68+
handleDefaultValue();
69+
70+
watch.pre(
71+
() => value,
72+
() => {
73+
handleDefaultValue();
74+
}
75+
);
76+
5577
const rootState = useRangeCalendarRoot({
5678
id: box.with(() => id),
5779
ref: box.with(

packages/bits-ui/src/lib/bits/select/components/select.svelte

+14-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import { useSelectRoot } from "../select.svelte.js";
66
import type { SelectRootProps } from "../types.js";
77
import SelectHiddenInput from "./select-hidden-input.svelte";
8+
import { watch } from "runed";
89
910
let {
1011
value = $bindable(),
@@ -22,12 +23,21 @@
2223
children,
2324
}: SelectRootProps = $props();
2425
25-
if (value === undefined) {
26-
const defaultValue = type === "single" ? "" : [];
27-
28-
value = defaultValue;
26+
function handleDefaultValue() {
27+
if (value !== undefined) return;
28+
value = type === "single" ? "" : [];
2929
}
3030
31+
// SSR
32+
handleDefaultValue();
33+
34+
watch.pre(
35+
() => value,
36+
() => {
37+
handleDefaultValue();
38+
}
39+
);
40+
3141
const rootState = useSelectRoot({
3242
type,
3343
value: box.with(

0 commit comments

Comments
 (0)