Skip to content

Commit

Permalink
refactor(editor): Add typed event bus
Browse files Browse the repository at this point in the history
  • Loading branch information
tomi committed Aug 13, 2024
1 parent 9d7caac commit 1d00301
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 11 deletions.
4 changes: 2 additions & 2 deletions packages/design-system/src/components/N8nFormBox/FormBox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ import N8nHeading from '../N8nHeading';
import N8nLink from '../N8nLink';
import N8nButton from '../N8nButton';
import type { IFormInput } from 'n8n-design-system/types';
import { createEventBus } from '../../utils';
import { createFormEventBus } from '../../utils';
interface FormBoxProps {
title?: string;
Expand All @@ -67,7 +67,7 @@ withDefaults(defineProps<FormBoxProps>(), {
redirectLink: '',
});
const formBus = createEventBus();
const formBus = createFormEventBus();
const emit = defineEmits<{
submit: [value: { [key: string]: Value }];
update: [value: { name: string; value: Value }];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import { computed, onMounted, reactive, ref, watch } from 'vue';
import N8nFormInput from '../N8nFormInput';
import type { IFormInput } from '../../types';
import ResizeObserver from '../ResizeObserver';
import type { EventBus } from '../../utils';
import { createEventBus } from '../../utils';
import type { FormEventBus } from '../../utils';
import { createFormEventBus } from '../../utils';
export type FormInputsProps = {
inputs?: IFormInput[];
eventBus?: EventBus;
eventBus?: FormEventBus;
columnView?: boolean;
verticalSpacing?: '' | 'xs' | 's' | 'm' | 'l' | 'xl';
teleported?: boolean;
Expand All @@ -19,7 +19,7 @@ type Value = string | number | boolean | null | undefined;
const props = withDefaults(defineProps<FormInputsProps>(), {
inputs: () => [],
eventBus: createEventBus,
eventBus: createFormEventBus,
columnView: false,
verticalSpacing: '',
teleported: true,
Expand Down
25 changes: 25 additions & 0 deletions packages/design-system/src/utils/__tests__/event-bus.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,31 @@ describe('createEventBus()', () => {
});
});

describe('once()', () => {
it('should register event handler', () => {
const handler = vi.fn();
const eventName = 'test';

eventBus.once(eventName, handler);

eventBus.emit(eventName, {});

expect(handler).toHaveBeenCalled();
});

it('should unregister event handler after first call', () => {
const handler = vi.fn();
const eventName = 'test';

eventBus.once(eventName, handler);

eventBus.emit(eventName, {});
eventBus.emit(eventName, {});

expect(handler).toHaveBeenCalledTimes(1);
});
});

describe('off()', () => {
it('should register event handler', () => {
const handler = vi.fn();
Expand Down
58 changes: 53 additions & 5 deletions packages/design-system/src/utils/event-bus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,51 @@
export type CallbackFn = Function;
export type UnregisterFn = () => void;

export interface EventBus {
on: (eventName: string, fn: CallbackFn) => UnregisterFn;
off: (eventName: string, fn: CallbackFn) => void;
emit: <T = Event>(eventName: string, event?: T) => void;
export type Listener<Payload> = (payload: Payload) => void;

export type Payloads<ListenerMap> = {
[E in keyof ListenerMap]: unknown;
};

// TODO: Fix all usages of `createEventBus` and convert `any` to `unknown`
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export interface EventBus<ListenerMap extends Payloads<ListenerMap> = Record<string, any>> {
on: <EventName extends keyof ListenerMap & string>(
eventName: EventName,
fn: Listener<ListenerMap[EventName]>,
) => UnregisterFn;

once: <EventName extends keyof ListenerMap & string>(
eventName: EventName,
fn: Listener<ListenerMap[EventName]>,
) => UnregisterFn;

off: <EventName extends keyof ListenerMap & string>(
eventName: EventName,
fn: Listener<ListenerMap[EventName]>,
) => void;

emit: <EventName extends keyof ListenerMap & string>(
eventName: EventName,
event?: ListenerMap[EventName],
) => void;
}

export function createEventBus(): EventBus {
/**
* Creates an event bus with the given listener map.
*
* @example
* ```ts
* const eventBus = createEventBus<{
* 'user-logged-in': { username: string };
* 'user-logged-out': never;
* }>();
*/
export function createEventBus<
// TODO: Fix all usages of `createEventBus` and convert `any` to `unknown`
// eslint-disable-next-line @typescript-eslint/no-explicit-any
ListenerMap extends Payloads<ListenerMap> = Record<string, any>,
>(): EventBus<ListenerMap> {
const handlers = new Map<string, CallbackFn[]>();

function off(eventName: string, fn: CallbackFn) {
Expand All @@ -33,6 +71,15 @@ export function createEventBus(): EventBus {
return () => off(eventName, fn);
}

function once(eventName: string, fn: CallbackFn): UnregisterFn {
const unregister = on(eventName, (...args: unknown[]) => {
unregister();
fn(...args);
});

return unregister;
}

function emit<T = Event>(eventName: string, event?: T) {
const eventFns = handlers.get(eventName);

Expand All @@ -45,6 +92,7 @@ export function createEventBus(): EventBus {

return {
on,
once,
off,
emit,
};
Expand Down
12 changes: 12 additions & 0 deletions packages/design-system/src/utils/form-event-bus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { createEventBus } from './event-bus';

export interface FormEventBusEvents {
submit: never;
}

export type FormEventBus = ReturnType<typeof createFormEventBus>;

/**
* Creates a new event bus to be used with the `FormInputs` component.
*/
export const createFormEventBus = createEventBus<FormEventBusEvents>;
1 change: 1 addition & 0 deletions packages/design-system/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './event-bus';
export * from './form-event-bus';
export * from './markdown';
export * from './typeguards';
export * from './uid';
Expand Down

0 comments on commit 1d00301

Please sign in to comment.