admin管理员组

文章数量:1357696

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.

本文标签: javascriptSvelte input blur triggered when element is focusedStack Overflow