mirror of https://github.com/sveltejs/svelte
commit
4e9cf14194
@ -0,0 +1,5 @@
|
||||
---
|
||||
"svelte": patch
|
||||
---
|
||||
|
||||
fix: don't clear date input on temporarily invalid value
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
"svelte": patch
|
||||
---
|
||||
|
||||
fix: use safe-equals comparison for `@const` tags in legacy mode
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
"svelte": patch
|
||||
---
|
||||
|
||||
fix: improve proxy effect dependency tracking
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
"svelte": patch
|
||||
---
|
||||
|
||||
fix: prevent window listeners from triggering events twice
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
"svelte": patch
|
||||
---
|
||||
|
||||
feat: allow dynamic `type` attribute with `bind:value`
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
"svelte": patch
|
||||
---
|
||||
|
||||
fix: improve event delegation with shadowed bindings
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
"svelte": patch
|
||||
---
|
||||
|
||||
feat: add reactive Date object to svelte/reactivity
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
"svelte": patch
|
||||
---
|
||||
|
||||
fix: correctly handle proxied signal writes before reads
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
"svelte": patch
|
||||
---
|
||||
|
||||
fix: improve deep_read performance
|
@ -0,0 +1,103 @@
|
||||
import { source } from '../internal/client/reactivity/sources.js';
|
||||
import { get, set } from '../internal/client/runtime.js';
|
||||
|
||||
/** @type {Array<keyof Date>} */
|
||||
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<keyof Date>} */
|
||||
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 };
|
@ -0,0 +1,29 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
// Test ensures that the `const` tag is coarse-grained in legacy mode (i.e. always fires an update when the array changes)
|
||||
export default test({
|
||||
html: `
|
||||
<button>Show</button>
|
||||
<p>0</p>
|
||||
<p>1</p>
|
||||
<p>2</p>
|
||||
<p>3</p>
|
||||
`,
|
||||
async test({ target, assert }) {
|
||||
const btn = target.querySelector('button');
|
||||
|
||||
btn?.click();
|
||||
await tick();
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`
|
||||
<button>Show</button>
|
||||
<p>0 show (v_item) show (item)</p>
|
||||
<p>1</p>
|
||||
<p>2 show (v_item) show (item)</p>
|
||||
<p>3</p>
|
||||
`
|
||||
);
|
||||
}
|
||||
});
|
@ -0,0 +1,21 @@
|
||||
<script>
|
||||
export let items = {0:{clicked:false},length:4};
|
||||
</script>
|
||||
|
||||
<button on:click={()=>{
|
||||
items[0].clicked=true;
|
||||
items[2]={clicked:true};
|
||||
}}>Show</button>
|
||||
|
||||
{#each items as item, i}
|
||||
{@const v_item=item}
|
||||
<p>
|
||||
{i}
|
||||
{#if v_item?.clicked}
|
||||
show (v_item)
|
||||
{/if}
|
||||
{#if item?.clicked}
|
||||
show (item)
|
||||
{/if}
|
||||
</p>
|
||||
{/each}
|
@ -0,0 +1,72 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test, ok } from '../../test';
|
||||
|
||||
export default test({
|
||||
html: `
|
||||
<input type=text>
|
||||
<input type=text>
|
||||
<p>x / y</p>
|
||||
|
||||
<button>change to text</button>
|
||||
<button>change to number</button>
|
||||
<button>change to range</button>
|
||||
`,
|
||||
ssrHtml: `
|
||||
<input type=text value=x>
|
||||
<input type=text value=y>
|
||||
<p>x / y</p>
|
||||
|
||||
<button>change to text</button>
|
||||
<button>change to number</button>
|
||||
<button>change to range</button>
|
||||
`,
|
||||
async test({ assert, target }) {
|
||||
const [in1, in2] = target.querySelectorAll('input');
|
||||
const [btn1, btn2, btn3] = target.querySelectorAll('button');
|
||||
const p = target.querySelector('p');
|
||||
ok(p);
|
||||
|
||||
in1.value = '0';
|
||||
in2.value = '1';
|
||||
in1.dispatchEvent(new window.Event('input', { bubbles: true }));
|
||||
in2.dispatchEvent(new window.Event('input', { bubbles: true }));
|
||||
await tick();
|
||||
btn2?.click();
|
||||
await tick();
|
||||
assert.htmlEqual(p.innerHTML, '0 / 1');
|
||||
|
||||
in1.stepUp();
|
||||
in1.dispatchEvent(new window.Event('input', { bubbles: true }));
|
||||
in2.stepUp();
|
||||
in2.dispatchEvent(new window.Event('input', { bubbles: true }));
|
||||
await tick();
|
||||
assert.htmlEqual(p.innerHTML, '1 / 2');
|
||||
|
||||
btn1?.click();
|
||||
await tick();
|
||||
try {
|
||||
in1.stepUp();
|
||||
assert.fail();
|
||||
} catch (e) {
|
||||
// expected
|
||||
}
|
||||
|
||||
btn3?.click();
|
||||
await tick();
|
||||
in1.stepUp();
|
||||
in1.dispatchEvent(new window.Event('input', { bubbles: true }));
|
||||
in2.stepUp();
|
||||
in2.dispatchEvent(new window.Event('input', { bubbles: true }));
|
||||
await tick();
|
||||
assert.htmlEqual(p.innerHTML, '2 / 3');
|
||||
|
||||
btn1?.click();
|
||||
await tick();
|
||||
in1.value = 'a';
|
||||
in2.value = 'b';
|
||||
in1.dispatchEvent(new window.Event('input', { bubbles: true }));
|
||||
in2.dispatchEvent(new window.Event('input', { bubbles: true }));
|
||||
await tick();
|
||||
assert.htmlEqual(p.innerHTML, 'a / b');
|
||||
}
|
||||
});
|
@ -0,0 +1,14 @@
|
||||
<script>
|
||||
let dynamic = $state('x');
|
||||
let spread = $state('y');
|
||||
let inputType = $state('text');
|
||||
let props = $derived({type: inputType});
|
||||
</script>
|
||||
|
||||
<input bind:value={dynamic} type={inputType}>
|
||||
<input bind:value={spread} {...props}>
|
||||
<p>{dynamic} / {spread}</p>
|
||||
|
||||
<button onclick={() => inputType = 'text'}>change to text</button>
|
||||
<button onclick={() => inputType = 'number'}>change to number</button>
|
||||
<button onclick={() => inputType = 'range'}>change to range</button>
|
@ -0,0 +1,37 @@
|
||||
import { flushSync } from '../../../../src/main/main-client';
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
html: `<div>getSeconds: 0</div><div>getMinutes: 0</div><div>getHours: 15</div><div>getTime: 1708700400000</div><div>toDateString: Fri Feb 23 2024</div><button>1 second</button><button>1 minute</button><button>1 hour</button>`,
|
||||
|
||||
test({ assert, target }) {
|
||||
const [btn, btn2, btn3] = target.querySelectorAll('button');
|
||||
|
||||
flushSync(() => {
|
||||
btn?.click();
|
||||
});
|
||||
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`<div>getSeconds: 1</div><div>getMinutes: 0</div><div>getHours: 15</div><div>getTime: 1708700401000</div><div>toDateString: Fri Feb 23 2024</div><button>1 second</button><button>1 minute</button><button>1 hour</button>`
|
||||
);
|
||||
|
||||
flushSync(() => {
|
||||
btn2?.click();
|
||||
});
|
||||
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`<div>getSeconds: 1</div><div>getMinutes: 1</div><div>getHours: 15</div><div>getTime: 1708700461000</div><div>toDateString: Fri Feb 23 2024</div><button>1 second</button><button>1 minute</button><button>1 hour</button>`
|
||||
);
|
||||
|
||||
flushSync(() => {
|
||||
btn3?.click();
|
||||
});
|
||||
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`<div>getSeconds: 1</div><div>getMinutes: 1</div><div>getHours: 16</div><div>getTime: 1708704061000</div><div>toDateString: Fri Feb 23 2024</div><button>1 second</button><button>1 minute</button><button>1 hour</button>`
|
||||
);
|
||||
}
|
||||
});
|
@ -0,0 +1,21 @@
|
||||
<script>
|
||||
import { Date } from 'svelte/reactivity';
|
||||
|
||||
let date = new Date('2024/02/23 15:00:00');
|
||||
</script>
|
||||
|
||||
<div>getSeconds: {date.getSeconds()}</div>
|
||||
<div>getMinutes: {date.getMinutes()}</div>
|
||||
<div>getHours: {date.getHours()}</div>
|
||||
<div>getTime: {date.getTime()}</div>
|
||||
<div>toDateString: {date.toDateString()}</div>
|
||||
|
||||
<button onclick={() => {
|
||||
date.setSeconds(date.getSeconds() + 1);
|
||||
}}>1 second</button>
|
||||
<button onclick={() => {
|
||||
date.setMinutes(date.getMinutes() + 1);
|
||||
}}>1 minute</button>
|
||||
<button onclick={() => {
|
||||
date.setHours(date.getHours() + 1);
|
||||
}}>1 hour</button>
|
@ -0,0 +1,16 @@
|
||||
import { test } from '../../test';
|
||||
import { log } from './log.js';
|
||||
|
||||
export default test({
|
||||
before_test() {
|
||||
log.length = 0;
|
||||
},
|
||||
|
||||
async test({ assert, target }) {
|
||||
const btn = target.querySelector('button');
|
||||
|
||||
btn?.click();
|
||||
await Promise.resolve();
|
||||
assert.deepEqual(log, ['method']);
|
||||
}
|
||||
});
|
@ -0,0 +1,2 @@
|
||||
/** @type {any[]} */
|
||||
export const log = [];
|
@ -0,0 +1,12 @@
|
||||
<script>
|
||||
import { log } from './log.js';
|
||||
|
||||
let method = $state('method');
|
||||
function submitPay() {
|
||||
log.push(method);
|
||||
}
|
||||
let methods = [{method:1}];
|
||||
</script>
|
||||
{#each methods as {method}}
|
||||
<button onclick={submitPay}>{method}</button>
|
||||
{/each}
|
@ -0,0 +1,12 @@
|
||||
<script>
|
||||
import { getContext } from 'svelte';
|
||||
|
||||
let context = getContext('container');
|
||||
|
||||
$effect(() => {
|
||||
context.register('test');
|
||||
return () => context.unregister('test');
|
||||
});
|
||||
</script>
|
||||
|
||||
<div>Item</div>
|
@ -0,0 +1,9 @@
|
||||
import { flushSync } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
test({ assert, target }) {
|
||||
flushSync();
|
||||
assert.htmlEqual(target.innerHTML, `<div>Item</div>`);
|
||||
}
|
||||
});
|
@ -0,0 +1,14 @@
|
||||
<script>
|
||||
import { setContext } from 'svelte';
|
||||
|
||||
import Item from './Item.svelte'
|
||||
|
||||
let items = $state({});
|
||||
|
||||
setContext('container', {
|
||||
register: (id) => items[id] = true,
|
||||
unregister: (id) => delete items[id]
|
||||
});
|
||||
</script>
|
||||
|
||||
<Item />
|
@ -1,14 +0,0 @@
|
||||
[
|
||||
{
|
||||
"code": "invalid-type-attribute",
|
||||
"message": "'type' attribute must be a static text value if input uses two-way binding",
|
||||
"start": {
|
||||
"line": 6,
|
||||
"column": 24
|
||||
},
|
||||
"end": {
|
||||
"line": 6,
|
||||
"column": 40
|
||||
}
|
||||
}
|
||||
]
|
@ -1,6 +0,0 @@
|
||||
<script>
|
||||
let foo;
|
||||
let inputType;
|
||||
</script>
|
||||
|
||||
<input bind:value={foo} type={inputType}>
|
Loading…
Reference in new issue