最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Svelte input blur triggered when element is focused - Stack Overflow

programmeradmin1浏览0评论

Maybe its not related to svelte, but if do some DOM changes after input is focused, for example

<input onfocus={() => toggleVisibiltyOfOtherElement = true } onblur={() => console.log("blur")} />

(toggleVisibiltyOfOtherElement triggers dom changes)

then blur is also triggered. Which doesn't make any sense, since visually the input is still focused. This makes it impossible to show another element only when input is focused, because showing that element unfocuses the input

Any way to fix this?


app.svelte

<script>
    import Component from "./Component.svelte"
    let s = $state("");
    let sFiler = $state();
    let showBox = $state(false);
</script>


<input bind:this={sFilter} type="text" bind:value={s} placeholder="search..."
    onfocus={() => showBox = true }
    onblur={() => showBox = false }
 />

<Component bind:visible={showBox}>
    box content
</Component>

component.svelte

<script>
  import { clickoutside } from "./onclickoutside.svelte";
  let { visible = $bindable(),  children } = $props()
</script>

{#if visible}
  <div use:clickoutside onclickoutside={() => visible = false}>
    {@render children()}
  </div>
{/if}

<style>
    div{
        position: fixed;
        background: pink;
        left: 0;
        top: 40px;
        width: 500px;
        height: 200px;
    }
</style>

onclickoutside.svelte.js

export const clickoutside = (node, ignore) => {
  function listener(event) {
    const target = event.target;
    if (!event.target || (ignore && target.closest(ignore))) {
      return;
    }

    if (node && !node.contains(target) && !event.defaultPrevented) {
      node.dispatchEvent(new CustomEvent("clickoutside", { detail: { target } }))
    }
  }

  document.addEventListener("click", listener, true)

  return {
    destroy() {
      document.removeEventListener("click", listener, true)
    }
  }
}

Maybe its not related to svelte, but if do some DOM changes after input is focused, for example

<input onfocus={() => toggleVisibiltyOfOtherElement = true } onblur={() => console.log("blur")} />

(toggleVisibiltyOfOtherElement triggers dom changes)

then blur is also triggered. Which doesn't make any sense, since visually the input is still focused. This makes it impossible to show another element only when input is focused, because showing that element unfocuses the input

Any way to fix this?


app.svelte

<script>
    import Component from "./Component.svelte"
    let s = $state("");
    let sFiler = $state();
    let showBox = $state(false);
</script>


<input bind:this={sFilter} type="text" bind:value={s} placeholder="search..."
    onfocus={() => showBox = true }
    onblur={() => showBox = false }
 />

<Component bind:visible={showBox}>
    box content
</Component>

component.svelte

<script>
  import { clickoutside } from "./onclickoutside.svelte";
  let { visible = $bindable(),  children } = $props()
</script>

{#if visible}
  <div use:clickoutside onclickoutside={() => visible = false}>
    {@render children()}
  </div>
{/if}

<style>
    div{
        position: fixed;
        background: pink;
        left: 0;
        top: 40px;
        width: 500px;
        height: 200px;
    }
</style>

onclickoutside.svelte.js

export const clickoutside = (node, ignore) => {
  function listener(event) {
    const target = event.target;
    if (!event.target || (ignore && target.closest(ignore))) {
      return;
    }

    if (node && !node.contains(target) && !event.defaultPrevented) {
      node.dispatchEvent(new CustomEvent("clickoutside", { detail: { target } }))
    }
  }

  document.addEventListener("click", listener, true)

  return {
    destroy() {
      document.removeEventListener("click", listener, true)
    }
  }
}
Share Improve this question edited Mar 28 at 17:18 Alex asked Mar 28 at 15:42 AlexAlex 66.1k185 gold badges459 silver badges651 bronze badges 1
  • Please provide a complete, minimal reproduction. – brunnerh Commented Mar 28 at 16:42
Add a comment  | 

2 Answers 2

Reset to default 1

blur is not triggered.

The logic works as expected if focus is moved via tab, but if you click on the input, focus triggers on mouse-down, and a click is emitted on mouse-up which immediately causes the click outside logic to set the flag back to false.

You could for example add special handling for the input or move the boundary for click-outside higher up in the tree so it considers both the input and the content to show to be inside.

Now with the code you can see that the problem is what Brunnerh gives in his answer, the mouseup after focus triggers the clickoutside custom event and hides the box. The easiest solution is to simply use onclick instead of onfocus:

<script>
    import Component from "./Component.svelte"
    let s = $state("");
    let sFiler = $state();
    let showBox = $state(false);
</script>


<input bind:this={sFilter} type="text" bind:value={s} placeholder="search..."
    onclick={() => showBox = true }
    onblur={() => showBox = false }
 />

<Component bind:visible={showBox}>
    box content
</Component>

Keep in mind that if a user for some reason presses the mouse inside the input, then drags out of it and then releases the mouse, the click action is never triggered and the box won't show up. That's a very unexpected behavior but you can get around it by doing the following:

//App.svelte
<script>
    import Component from "./Component.svelte"
    let s = $state("");
    let sFiler = $state();
    let showBox = $state(false);
        let inputMouseDown = $state(false)
        function revealBox() {
            if (!inputMouseDown) return
            showBox = true
            inputMouseDown = false;
        }
</script>


<input bind:this={sFilter} type="text" bind:value={s} placeholder="search..."
    onmousedown={() => inputMouseDown = true}
    ontouchstart={() => inputMouseDown = true}
    onblur={() => showBox = false }
/>

<svelte:window onmouseup={revealBox}  ontouchend={revealBox}/>



<Component bind:visible={showBox} {inputMouseDown}>
    box content
</Component>
//Component.svelte
<script>
  import { clickoutside } from "./onclickoutside.svelte";
  let { visible = $bindable(), inputMouseDown,  children } = $props()

    function hideBox() {
        if (inputMousedown) return
        visible = false
    }
</script>

{#if visible}
  <div use:clickoutside onclickoutside={hideBox}>
    {@render children()}
  </div>
{/if}

<style>
    div{
        position: fixed;
        background: pink;
        left: 0;
        top: 40px;
        width: 500px;
        height: 200px;
    }
</style>

Also added touch support. Here's a working REPL.

发布评论

评论列表(0)

  1. 暂无评论