From f658ca0d548cc0e4eb0e33f1c4a7586f3dd8ccec Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Fri, 23 Feb 2024 18:28:56 +0000 Subject: [PATCH] feat: add reactive Date object to svelte/reactivity (#10622) * feat: add reactive Date object to svelte/reactivity * add type safety, fix revealed typos --------- Co-authored-by: Rich Harris --- .changeset/new-rabbits-flow.md | 5 + packages/svelte/package.json | 4 + packages/svelte/src/reactivity/index.js | 103 ++++++++++++++++++ .../runtime-runes/samples/date/_config.js | 37 +++++++ .../runtime-runes/samples/date/main.svelte | 21 ++++ 5 files changed, 170 insertions(+) create mode 100644 .changeset/new-rabbits-flow.md create mode 100644 packages/svelte/src/reactivity/index.js create mode 100644 packages/svelte/tests/runtime-runes/samples/date/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/date/main.svelte diff --git a/.changeset/new-rabbits-flow.md b/.changeset/new-rabbits-flow.md new file mode 100644 index 0000000000..273496b2ae --- /dev/null +++ b/.changeset/new-rabbits-flow.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +feat: add reactive Date object to svelte/reactivity diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 2d8d9f0786..490ba7249f 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -62,6 +62,10 @@ "types": "./types/index.d.ts", "default": "./src/motion/index.js" }, + "./reactivity": { + "types": "./types/index.d.ts", + "default": "./src/reactivity/index.js" + }, "./server": { "types": "./types/index.d.ts", "default": "./src/server/index.js" diff --git a/packages/svelte/src/reactivity/index.js b/packages/svelte/src/reactivity/index.js new file mode 100644 index 0000000000..ce865c5a3e --- /dev/null +++ b/packages/svelte/src/reactivity/index.js @@ -0,0 +1,103 @@ +import { source } from '../internal/client/reactivity/sources'; +import { get, set } from '../internal/client/runtime'; + +/** @type {Array} */ +const read = [ + 'getDate', + 'getDay', + 'getFullYear', + 'getHours', + 'getMilliseconds', + 'getMinutes', + 'getMonth', + 'getSeconds', + 'getTime', + 'getTimezoneOffset', + 'getUTCDate', + 'getUTCDay', + 'getUTCFullYear', + 'getUTCHours', + 'getUTCMilliseconds', + 'getUTCMinutes', + 'getUTCMonth', + 'getUTCSeconds', + // @ts-expect-error this is deprecated + 'getYear', + 'toDateString', + 'toISOString', + 'toJSON', + 'toLocaleDateString', + 'toLocaleString', + 'toLocaleTimeString', + 'toString', + 'toTimeString', + 'toUTCString' +]; + +/** @type {Array} */ +const write = [ + 'setDate', + 'setFullYear', + 'setHours', + 'setMilliseconds', + 'setMinutes', + 'setMonth', + 'setSeconds', + 'setTime', + 'setUTCDate', + 'setUTCFullYear', + 'setUTCHours', + 'setUTCMilliseconds', + 'setUTCMinutes', + 'setUTCMonth', + 'setUTCSeconds', + // @ts-expect-error this is deprecated + 'setYear' +]; + +class ReactiveDate extends Date { + #raw_time = source(super.getTime()); + static #inited = false; + + // We init as part of the first instance so that we can treeshake this class + #init() { + if (!ReactiveDate.#inited) { + ReactiveDate.#inited = true; + const proto = ReactiveDate.prototype; + const date_proto = Date.prototype; + + for (const method of read) { + // @ts-ignore + proto[method] = function () { + get(this.#raw_time); + // @ts-ignore + return date_proto[method].call(this); + }; + } + + for (const method of write) { + // @ts-ignore + proto[method] = function (/** @type {any} */ ...args) { + // @ts-ignore + const v = date_proto[method].apply(this, args); + const time = date_proto.getTime.call(this); + if (time !== this.#raw_time.v) { + set(this.#raw_time, time); + } + return v; + }; + } + } + } + + /** + * @param {any[]} values + */ + constructor(...values) { + // @ts-ignore + super(...values); + this.#init(); + } +} + +export { ReactiveDate as Date }; diff --git a/packages/svelte/tests/runtime-runes/samples/date/_config.js b/packages/svelte/tests/runtime-runes/samples/date/_config.js new file mode 100644 index 0000000000..8bd2327683 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/date/_config.js @@ -0,0 +1,37 @@ +import { flushSync } from '../../../../src/main/main-client'; +import { test } from '../../test'; + +export default test({ + html: `
getSeconds: 0
getMinutes: 0
getHours: 15
getTime: 1708700400000
toDateString: Fri Feb 23 2024
`, + + test({ assert, target }) { + const [btn, btn2, btn3] = target.querySelectorAll('button'); + + flushSync(() => { + btn?.click(); + }); + + assert.htmlEqual( + target.innerHTML, + `
getSeconds: 1
getMinutes: 0
getHours: 15
getTime: 1708700401000
toDateString: Fri Feb 23 2024
` + ); + + flushSync(() => { + btn2?.click(); + }); + + assert.htmlEqual( + target.innerHTML, + `
getSeconds: 1
getMinutes: 1
getHours: 15
getTime: 1708700461000
toDateString: Fri Feb 23 2024
` + ); + + flushSync(() => { + btn3?.click(); + }); + + assert.htmlEqual( + target.innerHTML, + `
getSeconds: 1
getMinutes: 1
getHours: 16
getTime: 1708704061000
toDateString: Fri Feb 23 2024
` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/date/main.svelte b/packages/svelte/tests/runtime-runes/samples/date/main.svelte new file mode 100644 index 0000000000..bbb6ae5bbe --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/date/main.svelte @@ -0,0 +1,21 @@ + + +
getSeconds: {date.getSeconds()}
+
getMinutes: {date.getMinutes()}
+
getHours: {date.getHours()}
+
getTime: {date.getTime()}
+
toDateString: {date.toDateString()}
+ + + +