Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/track-sveltedate-in-snapshot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: track `SvelteDate` when it is read through `$state.snapshot`
5 changes: 4 additions & 1 deletion packages/svelte/src/internal/shared/clone.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,10 @@ function clone(value, cloned, path, paths, original = null, no_tojson = false) {
}

if (value instanceof Date) {
return /** @type {Snapshot<T>} */ (structuredClone(value));
// Read the time through the prototype rather than letting
// `structuredClone` read the internal slot, so that a reactive
// subclass like `SvelteDate` registers a dependency
return /** @type {Snapshot<T>} */ (new Date(value.getTime()));
}

if (typeof (/** @type {T & { toJSON?: any } } */ (value).toJSON) === 'function' && !no_tojson) {
Expand Down
30 changes: 29 additions & 1 deletion packages/svelte/src/reactivity/date.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { render_effect, effect_root } from '../internal/client/reactivity/effect
import { flushSync } from '../index-client.js';
import { SvelteDate } from './date.js';
import { assert, test } from 'vitest';
import { derived, get } from 'svelte/internal/client';
import { derived, get, snapshot } from 'svelte/internal/client';

const initial_date = new Date(2023, 0, 2, 0, 0, 0, 0);
const a = new Date(2024, 1, 3, 1, 1, 1, 1);
Expand Down Expand Up @@ -672,3 +672,31 @@ test('Date methods shared between deriveds', () => {

cleanup();
});

test('date is reactive when read through $state.snapshot', () => {
const date = new SvelteDate(initial_date);
const log: any = [];

const cleanup = effect_root(() => {
render_effect(() => {
log.push(snapshot(date).toISOString());
});
});

flushSync(() => {
date.setFullYear(a.getFullYear());
});

flushSync(() => {
date.setFullYear(b.getFullYear());
});

const after_a = new Date(initial_date);
after_a.setFullYear(a.getFullYear());
const after_b = new Date(initial_date);
after_b.setFullYear(b.getFullYear());

assert.deepEqual(log, [initial_date.toISOString(), after_a.toISOString(), after_b.toISOString()]);

cleanup();
});