From 1770dc9e47a05f30423f2b530f067d7b14cbdbdd Mon Sep 17 00:00:00 2001 From: Vaibhav Rai Date: Sun, 21 Apr 2024 13:31:36 +0530 Subject: [PATCH 1/6] Feat: Add readonly binding focused --- .../3-transform/client/visitors/template.js | 4 +- .../svelte/src/compiler/phases/bindings.js | 1 + .../client/dom/elements/bindings/universal.js | 28 ++++++++ .../internal/client/dom/elements/events.js | 2 - packages/svelte/src/internal/client/index.js | 2 +- .../samples/binding-focused/_config.js | 70 +++++++++++++++++++ .../samples/binding-focused/main.svelte | 9 +++ 7 files changed, 112 insertions(+), 4 deletions(-) create mode 100644 packages/svelte/tests/runtime-legacy/samples/binding-focused/_config.js create mode 100644 packages/svelte/tests/runtime-legacy/samples/binding-focused/main.svelte diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js index fa711c324f12..e9d3b519c724 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js @@ -2727,7 +2727,9 @@ export const template_visitors = { case 'checked': call_expr = b.call(`$.bind_checked`, state.node, getter, setter); break; - + case 'focused': + call_expr = b.call(`$.bind_focused`, state.node, setter); + break; case 'group': { /** @type {import('estree').CallExpression[]} */ const indexes = []; diff --git a/packages/svelte/src/compiler/phases/bindings.js b/packages/svelte/src/compiler/phases/bindings.js index e3fdd7eb8c07..9a6f1c316b2f 100644 --- a/packages/svelte/src/compiler/phases/bindings.js +++ b/packages/svelte/src/compiler/phases/bindings.js @@ -21,6 +21,7 @@ export const binding_properties = { event: 'durationchange', omit_in_ssr: true }, + focused: {}, paused: { valid_elements: ['audio', 'video'], omit_in_ssr: true diff --git a/packages/svelte/src/internal/client/dom/elements/bindings/universal.js b/packages/svelte/src/internal/client/dom/elements/bindings/universal.js index 8c40c04da5e3..dc1ac7fc0da9 100644 --- a/packages/svelte/src/internal/client/dom/elements/bindings/universal.js +++ b/packages/svelte/src/internal/client/dom/elements/bindings/universal.js @@ -67,3 +67,31 @@ export function bind_property(property, event_name, type, element, get_value, up } }); } + +/** + * @param {HTMLElement} element + * @param {(value: unknown) => void} update + * @returns {void} + */ +export function bind_focused(element, update) { + var focus_handler = () => { + update(true); + } + var blur_handler = () => { + update(false); + } + + element.addEventListener('focus', focus_handler); + element.addEventListener('blur', blur_handler); + + /** @type {ReturnType} */ + + render_effect(() => { + if(element === document.activeElement) { + update(true) + } else { + update(false) + } + + }); +} diff --git a/packages/svelte/src/internal/client/dom/elements/events.js b/packages/svelte/src/internal/client/dom/elements/events.js index 330bcc266128..23166ef5e6c2 100644 --- a/packages/svelte/src/internal/client/dom/elements/events.js +++ b/packages/svelte/src/internal/client/dom/elements/events.js @@ -25,7 +25,6 @@ export function event(event_name, dom, handler, capture, passive) { return handler.call(this, event); } } - dom.addEventListener(event_name, target_handler, options); // @ts-ignore @@ -149,7 +148,6 @@ export function handle_event_propagation(handler_element, event) { current_target = parent_element; } - // @ts-expect-error is used above event.__root = handler_element; // @ts-expect-error is used above diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index 2362698ab183..25ad438ccbf0 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -44,7 +44,7 @@ export { bind_prop } from './dom/elements/bindings/props.js'; export { bind_select_value, init_select, select_option } from './dom/elements/bindings/select.js'; export { bind_element_size, bind_resize_observer } from './dom/elements/bindings/size.js'; export { bind_this } from './dom/elements/bindings/this.js'; -export { bind_content_editable, bind_property } from './dom/elements/bindings/universal.js'; +export { bind_content_editable, bind_property, bind_focused } from './dom/elements/bindings/universal.js'; export { bind_window_scroll, bind_window_size } from './dom/elements/bindings/window.js'; export { once, diff --git a/packages/svelte/tests/runtime-legacy/samples/binding-focused/_config.js b/packages/svelte/tests/runtime-legacy/samples/binding-focused/_config.js new file mode 100644 index 000000000000..bc001e8658c6 --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/binding-focused/_config.js @@ -0,0 +1,70 @@ +import { tick } from 'svelte'; + +import { ok, test } from '../../test'; + +export default test({ + html: ` +
false
+
false
+ + + + `, + + ssrHtml: ` +
+
+ + + + `, + + async test({ assert, component, target, window }) { + const [d1, d2] = target.querySelectorAll('div'); + const [in1, in2] = target.querySelectorAll('input'); + const button = target.querySelector('button'); + ok(in1); + ok(in2); + ok(button); + ok(d1); + ok(d2); + assert.equal(d1.textContent, 'false'); + assert.equal(d2.textContent, 'false'); + const event1 = new window.MouseEvent('click', { bubbles: true }); + in1.value = '1'; + await in1.dispatchEvent(event1); + await tick(); + assert.equal(window.document.activeElement, in1); + assert.equal(component.a, true); + assert.equal(component.b, false); + assert.equal(d1.textContent, 'true'); + assert.equal(d2.textContent, 'false'); + + in2.value = '1'; + const event2 = new window.MouseEvent('click', { bubbles: true }); + await in2.dispatchEvent(event2); + await tick(); + assert.equal(component.a, false); + assert.equal(component.b, true); + assert.equal(d1.textContent, 'false'); + assert.equal(d2.textContent, 'true'); + + const event3 = new window.MouseEvent('click', { bubbles: true }); + await button.dispatchEvent(event3); + await tick(); + + assert.equal(d1.textContent, 'false'); + assert.equal(d2.textContent, 'false'); + + assert.htmlEqual( + target.innerHTML, + ` +
false
+
false
+ + + + ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-legacy/samples/binding-focused/main.svelte b/packages/svelte/tests/runtime-legacy/samples/binding-focused/main.svelte new file mode 100644 index 000000000000..86eb2a984e8e --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/binding-focused/main.svelte @@ -0,0 +1,9 @@ + +
{a}
+
{b}
+ + + From 412e3c073c91919748353ba8e31f19b025dc7264 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 21 Apr 2024 18:52:44 -0400 Subject: [PATCH 2/6] prettier --- .../client/dom/elements/bindings/universal.js | 11 +++++------ packages/svelte/src/internal/client/index.js | 6 +++++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/elements/bindings/universal.js b/packages/svelte/src/internal/client/dom/elements/bindings/universal.js index dc1ac7fc0da9..d7ad0c790909 100644 --- a/packages/svelte/src/internal/client/dom/elements/bindings/universal.js +++ b/packages/svelte/src/internal/client/dom/elements/bindings/universal.js @@ -76,10 +76,10 @@ export function bind_property(property, event_name, type, element, get_value, up export function bind_focused(element, update) { var focus_handler = () => { update(true); - } + }; var blur_handler = () => { update(false); - } + }; element.addEventListener('focus', focus_handler); element.addEventListener('blur', blur_handler); @@ -87,11 +87,10 @@ export function bind_focused(element, update) { /** @type {ReturnType} */ render_effect(() => { - if(element === document.activeElement) { - update(true) + if (element === document.activeElement) { + update(true); } else { - update(false) + update(false); } - }); } diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index 25ad438ccbf0..6e6488c0019f 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -44,7 +44,11 @@ export { bind_prop } from './dom/elements/bindings/props.js'; export { bind_select_value, init_select, select_option } from './dom/elements/bindings/select.js'; export { bind_element_size, bind_resize_observer } from './dom/elements/bindings/size.js'; export { bind_this } from './dom/elements/bindings/this.js'; -export { bind_content_editable, bind_property, bind_focused } from './dom/elements/bindings/universal.js'; +export { + bind_content_editable, + bind_property, + bind_focused +} from './dom/elements/bindings/universal.js'; export { bind_window_scroll, bind_window_size } from './dom/elements/bindings/window.js'; export { once, From 4540c267df14a591ece7db4e3fee8d7c6f98e4fe Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 21 Apr 2024 19:00:38 -0400 Subject: [PATCH 3/6] simplify test --- .../samples/binding-focused/_config.js | 63 ++----------------- .../samples/binding-focused/main.svelte | 8 +-- 2 files changed, 9 insertions(+), 62 deletions(-) diff --git a/packages/svelte/tests/runtime-legacy/samples/binding-focused/_config.js b/packages/svelte/tests/runtime-legacy/samples/binding-focused/_config.js index bc001e8658c6..02f2574f340e 100644 --- a/packages/svelte/tests/runtime-legacy/samples/binding-focused/_config.js +++ b/packages/svelte/tests/runtime-legacy/samples/binding-focused/_config.js @@ -1,70 +1,19 @@ -import { tick } from 'svelte'; +import { flushSync } from 'svelte'; -import { ok, test } from '../../test'; +import { test } from '../../test'; export default test({ - html: ` -
false
-
false
- - - - `, - - ssrHtml: ` -
-
- - - - `, - async test({ assert, component, target, window }) { - const [d1, d2] = target.querySelectorAll('div'); const [in1, in2] = target.querySelectorAll('input'); - const button = target.querySelector('button'); - ok(in1); - ok(in2); - ok(button); - ok(d1); - ok(d2); - assert.equal(d1.textContent, 'false'); - assert.equal(d2.textContent, 'false'); - const event1 = new window.MouseEvent('click', { bubbles: true }); - in1.value = '1'; - await in1.dispatchEvent(event1); - await tick(); + + flushSync(() => in1.focus()); assert.equal(window.document.activeElement, in1); assert.equal(component.a, true); assert.equal(component.b, false); - assert.equal(d1.textContent, 'true'); - assert.equal(d2.textContent, 'false'); - in2.value = '1'; - const event2 = new window.MouseEvent('click', { bubbles: true }); - await in2.dispatchEvent(event2); - await tick(); + flushSync(() => in2.focus()); + assert.equal(window.document.activeElement, in2); assert.equal(component.a, false); assert.equal(component.b, true); - assert.equal(d1.textContent, 'false'); - assert.equal(d2.textContent, 'true'); - - const event3 = new window.MouseEvent('click', { bubbles: true }); - await button.dispatchEvent(event3); - await tick(); - - assert.equal(d1.textContent, 'false'); - assert.equal(d2.textContent, 'false'); - - assert.htmlEqual( - target.innerHTML, - ` -
false
-
false
- - - - ` - ); } }); diff --git a/packages/svelte/tests/runtime-legacy/samples/binding-focused/main.svelte b/packages/svelte/tests/runtime-legacy/samples/binding-focused/main.svelte index 86eb2a984e8e..7ba892393dc7 100644 --- a/packages/svelte/tests/runtime-legacy/samples/binding-focused/main.svelte +++ b/packages/svelte/tests/runtime-legacy/samples/binding-focused/main.svelte @@ -2,8 +2,6 @@ export let a; export let b; -
{a}
-
{b}
- - - + + + From ba3da6e9c6815644f38bd1d0eaece38b873eaef2 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 21 Apr 2024 19:02:14 -0400 Subject: [PATCH 4/6] revert --- packages/svelte/src/internal/client/dom/elements/events.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/svelte/src/internal/client/dom/elements/events.js b/packages/svelte/src/internal/client/dom/elements/events.js index 23166ef5e6c2..330bcc266128 100644 --- a/packages/svelte/src/internal/client/dom/elements/events.js +++ b/packages/svelte/src/internal/client/dom/elements/events.js @@ -25,6 +25,7 @@ export function event(event_name, dom, handler, capture, passive) { return handler.call(this, event); } } + dom.addEventListener(event_name, target_handler, options); // @ts-ignore @@ -148,6 +149,7 @@ export function handle_event_propagation(handler_element, event) { current_target = parent_element; } + // @ts-expect-error is used above event.__root = handler_element; // @ts-expect-error is used above From a13b088b4598e80daf967f056a7f9415c2c40f24 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 21 Apr 2024 19:04:37 -0400 Subject: [PATCH 5/6] simplify implementation --- .../client/dom/elements/bindings/universal.js | 21 +++---------------- 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/elements/bindings/universal.js b/packages/svelte/src/internal/client/dom/elements/bindings/universal.js index d7ad0c790909..f42f67d59472 100644 --- a/packages/svelte/src/internal/client/dom/elements/bindings/universal.js +++ b/packages/svelte/src/internal/client/dom/elements/bindings/universal.js @@ -1,4 +1,5 @@ import { render_effect } from '../../../reactivity/effects.js'; +import { listen } from './shared.js'; /** * @param {'innerHTML' | 'textContent' | 'innerText'} property @@ -74,23 +75,7 @@ export function bind_property(property, event_name, type, element, get_value, up * @returns {void} */ export function bind_focused(element, update) { - var focus_handler = () => { - update(true); - }; - var blur_handler = () => { - update(false); - }; - - element.addEventListener('focus', focus_handler); - element.addEventListener('blur', blur_handler); - - /** @type {ReturnType} */ - - render_effect(() => { - if (element === document.activeElement) { - update(true); - } else { - update(false); - } + listen(element, ['focus', 'blur'], () => { + update(element === document.activeElement); }); } From bb4c3e10497f8d01ac1ea28d656a969eb9389628 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 22 Apr 2024 07:46:29 -0400 Subject: [PATCH 6/6] changeset --- .changeset/thick-swans-type.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/thick-swans-type.md diff --git a/.changeset/thick-swans-type.md b/.changeset/thick-swans-type.md new file mode 100644 index 000000000000..430cdbdc5b7c --- /dev/null +++ b/.changeset/thick-swans-type.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +feat: add read-only `bind:focused`