svecosystem / runed

Magical utilities for your Svelte applications (WIP)
https://runed.dev
MIT License
461 stars 23 forks source link

Feature Request: useClickOutside #37

Open sviripa opened 4 months ago

sviripa commented 4 months ago

Describe the feature in detail (code, mocks, or screenshots encouraged)

A utility function which accepts a reference to an instance of HtmlElement and a callback function and invokes a callback function when the user clicks outside of the given HtmlElement instance.

Here's how I imagine it would look like in action:

<script lang="ts">
  import { useClickOutside } from "runed";

  let container = $state<HTMLElement>();

  useClickOutside(container, () => console.log("clicked outside of the container"));
</script>

<div  bind:this={container}>
</div>

Alternatively, it could create a reference inside of useClickOutside and return it to the component to be attached.

<script lang="ts">
  import { useClickOutside } from "runed";

  let container = useClickOutside<HTMLElement>(() => console.log("clicked outside of the container"));
</script>

<div  bind:this={container}>
</div>

What type of pull request would this be?

New Feature

Provide relevant links or additional information.

No response

huntabyte commented 4 months ago

Hey there! I think this is a great one to add!

To ensure it works with reactive values and across function boundaries, we'll want it to work like the following examples, one is passing a Getter and the other is passing a Box.

<script lang="ts">
  import { useClickOutside } from "runed";

  let container = $state<HTMLElement>();

  useClickOutside(() => container, () => console.log("clicked outside of the container"));
</script>

<div bind:this={container}></div>

or

<script lang="ts">
  import { useClickOutside, box } from "runed";

  const container = box<HTMLElement | null>(null)

  useClickOutside(container, () => console.log("clicked outside of the container"));
</script>

<div bind:this={container.value}></div>

--

A good reference point code-wise is the useElementSize, which follows a similar pattern.

We'd also want to include the ability to stop and start this functionality programmatically. It would start by default, and invoking start again wouldn't do anything unless it is stopped.

<script lang="ts">
  import { useClickOutside, box } from "runed";

  const container = box<HTMLElement | null>(null)

  const outsideClick = useClickOutside(container, () => console.log("clicked outside of the container"));
</script>

<button onclick={outsideClick.stop}>Stop listening for outside clicks</button>
<button onclick={outsideClick.start}>Start listening again</button>
<div bind:this={container.value}></div>

If you'd like to take a stab at this, feel free!

sviripa commented 4 months ago

Makes sense to me. I'll work on this

huntabyte commented 4 months ago

Feel free to submit an early draft PR if you'd like some feedback!