Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] dispatch cancelable custom events #7064

Merged
merged 16 commits into from Apr 12, 2022
4 changes: 4 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,9 @@
# Svelte changelog

## Unreleased

* Add a third parameter to the returned function of `createEventDispatcher` that allows passing an object of `{ cancelable: true }` to create a cancelable custom event. The returned function will also return a boolean depending on whether the event is cancelled.

## 3.46.3

* Ignore whitespace in `{#each}` blocks when containing elements with `animate:` ([#5477](https://github.com/sveltejs/svelte/pull/5477))
Expand Down
25 changes: 23 additions & 2 deletions site/content/docs/03-run-time.md
Expand Up @@ -221,14 +221,14 @@ Retrieves the whole context map that belongs to the closest parent component. Mu
#### `createEventDispatcher`

```js
dispatch: ((name: string, detail?: any) => void) = createEventDispatcher();
dispatch: ((name: string, detail?: any, options?: DispatchOptions) => boolean) = createEventDispatcher();
```

---

Creates an event dispatcher that can be used to dispatch [component events](/docs#template-syntax-component-directives-on-component-event). Event dispatchers are functions that can take two arguments: `name` and `detail`.

Component events created with `createEventDispatcher` create a [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent). These events do not [bubble](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#Event_bubbling_and_capture) and are not cancellable with `event.preventDefault()`. The `detail` argument corresponds to the [CustomEvent.detail](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/detail) property and can contain any type of data.
Component events created with `createEventDispatcher` create a [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent). These events do not [bubble](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#Event_bubbling_and_capture). The `detail` argument corresponds to the [CustomEvent.detail](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/detail) property and can contain any type of data.

```sv
<script>
Expand All @@ -254,6 +254,27 @@ Events dispatched from child components can be listened to in their parent. Any
<Child on:notify="{callbackFunction}"/>
```

---

Events can be cancelable by passing a third parameter to the dispatch function. The function returns `false` if the event is cancelled with `event.preventDefault()`, otherwise it returns `true`.

```sv
<script>
import { createEventDispatcher } from 'svelte';

const dispatch = createEventDispatcher();

function notify() {
const shouldContinue = dispatch('notify', 'detail value', { cancelable: true });
if (shouldContinue) {
// no one called preventDefault
} else {
// a listener called preventDefault
}
}
</script>
```

### `svelte/store`

The `svelte/store` module exports functions for creating [readable](/docs#run-time-svelte-store-readable), [writable](/docs#run-time-svelte-store-writable) and [derived](/docs#run-time-svelte-store-derived) stores.
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/internal/dev.ts
Expand Up @@ -2,7 +2,7 @@ import { custom_event, append, append_hydration, insert, insert_hydration, detac
import { SvelteComponent } from './Component';

export function dispatch_dev<T=any>(type: string, detail?: T) {
document.dispatchEvent(custom_event(type, { version: '__VERSION__', ...detail }, true));
document.dispatchEvent(custom_event(type, { version: '__VERSION__', ...detail }, { bubbles: true }));
}

export function append_dev(target: Node, node: Node) {
Expand Down
5 changes: 3 additions & 2 deletions src/runtime/internal/dom.ts
Expand Up @@ -634,9 +634,10 @@ export function toggle_class(element, name, toggle) {
element.classList[toggle ? 'add' : 'remove'](name);
}

export function custom_event<T=any>(type: string, detail?: T, bubbles: boolean = false) {

export function custom_event<T=any>(type: string, detail?: T, { bubbles = false, cancelable = false } = {}): CustomEvent<T> {
const e: CustomEvent<T> = document.createEvent('CustomEvent');
e.initCustomEvent(type, bubbles, false, detail);
e.initCustomEvent(type, bubbles, cancelable, detail);
return e;
}

Expand Down
21 changes: 16 additions & 5 deletions src/runtime/internal/lifecycle.ts
Expand Up @@ -27,22 +27,33 @@ export function onDestroy(fn: () => any) {
get_current_component().$$.on_destroy.push(fn);
}

export function createEventDispatcher<
EventMap extends {} = any
>(): <EventKey extends Extract<keyof EventMap, string>>(type: EventKey, detail?: EventMap[EventKey]) => void {
export interface DispatchOptions {
cancelable?: boolean;
}

export function createEventDispatcher<EventMap extends {} = any>(): <
EventKey extends Extract<keyof EventMap, string>
>(
type: EventKey,
detail?: EventMap[EventKey],
options?: DispatchOptions
) => boolean {
const component = get_current_component();

return (type: string, detail?: any) => {
return (type: string, detail?: any, { cancelable = false } = {}): boolean => {
const callbacks = component.$$.callbacks[type];

if (callbacks) {
// TODO are there situations where events could be dispatched
// in a server (non-DOM) environment?
const event = custom_event(type, detail);
const event = custom_event(type, detail, { cancelable });
callbacks.slice().forEach(fn => {
fn.call(component, event);
});
return !event.defaultPrevented;
}

return true;
};
}

Expand Down