Skip to content

Instantly share code, notes, and snippets.

@swyxio
Last active November 20, 2020 14:45
Show Gist options
  • Save swyxio/ee1977eddd97167a24504d2a4a4794cd to your computer and use it in GitHub Desktop.
Save swyxio/ee1977eddd97167a24504d2a4a4794cd to your computer and use it in GitHub Desktop.
Svelte micro-RFC: configurable event modifiers

This is a micro RFC because i'm just jotting down an idea that I would love to have for Svelte, but I'm not sure if it belongs as an official RFC yet. the official RFC process offers no room for lightweight proposals so i am writing a gist.

see also Twitter discussion


The Problem

Svelte offers very useful event modifiers like preventDefault and once.

<form on:submit|preventDefault={handleSubmit}>
	<!-- the `submit` event's default is prevented,
		 so the page won't reload -->
</form>

However the most useful modifiers, like debounce and throttle, are out of reach. This is because we cannot agree on an API for configuring the parameters of these slightly more advanced features (e.g. debounce for 100ms vs 300ms)

As Guy Steele notes in Growing a Language, languages grow best when users can extend core language features in a way that looks native to the language, so we can cherry pick the best ideas but also users can use new features in idiomatic ways.

The Solution

Since we now have a svelte.config.js in sveltekit, we make the modifiers pluggable:

export default {
  extendModifiers: {
    // // example internal modifiers
    // preventDefault: e => fn => e.preventDefault() || fn(),
    // example custom modifier
    debounce100: e => fn => debounce(() => fn(e), 100),
    debounce300: e => fn => debounce(() => fn(e), 300)
  }
}

function debounce(func, wait, immediate) {
	var timeout;
	return function() {
		var context = this, args = arguments;
		var later = function() {
			timeout = null;
			if (!immediate) func.apply(context, args);
		};
		var callNow = immediate && !timeout;
		clearTimeout(timeout);
		timeout = setTimeout(later, wait);
		if (callNow) func.apply(context, args);
	};
};

We could then use this in our code:

<button on:click|debounce100={handleClick}>
  click
</button>

This would make it easy to create high quality UI resistant to accidental clicks.

If we don't want to offer global configs, we can also offer local modifications on a per-file basis:

<svelte:options config={{
  extendModifiers: {
    debounce100: e => fn => debounce(() => fn(e), 100),
    debounce300: e => fn => debounce(() => fn(e), 300)
  }
  }}/>

The inspiration for this comes from tailwind configs which are customizable the same way.

Alternatives

You could conceivably do this with actions, incl built in actions:

<button on:debouncedClick={handleClick} use:debouncedClick>
  click
</button>

But this would be clunky because of the repetition and the indirection. Modifiers are just much easier to compose onto any given event (including potentially custom events!)

@swyxio
Copy link
Author

swyxio commented Nov 20, 2020

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment