Skip to content

Commit

Permalink
[feat] dispatch cancelable custom events (#7064)
Browse files Browse the repository at this point in the history
  • Loading branch information
bluwy committed Apr 12, 2022
1 parent c371c3f commit 0f94c89
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -5,6 +5,7 @@
* Return the context object in `setContext` [#7427](https://github.com/sveltejs/svelte/issues/7427)
* Fix `{@const}` tag not working inside Component when there's no `let:` [#7189](https://github.com/sveltejs/svelte/issues/7189)
* Ignore comments in `{#each}` blocks when containing elements with `animate:` ([#3999](https://github.com/sveltejs/svelte/issues/3999))
* 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 when called will also return a boolean depending on whether the event is cancelled ([#7064](https://github.com/sveltejs/svelte/pull/7064))

## 3.47.0

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-eventname). 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
4 changes: 2 additions & 2 deletions src/runtime/internal/dom.ts
Expand Up @@ -634,9 +634,9 @@ 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

0 comments on commit 0f94c89

Please sign in to comment.