mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-09 22:24:05 -08:00
refactor(editor): Add typed event bus (no-changelog) (#10367)
Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
parent
bfa7075950
commit
b2e0f33959
|
@ -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;
|
||||
|
@ -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 }];
|
||||
|
|
|
@ -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;
|
||||
|
@ -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,
|
||||
|
|
|
@ -14,18 +14,30 @@ describe('createEventBus()', () => {
|
|||
|
||||
expect(handler).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return unregister fn', () => {
|
||||
describe('once()', () => {
|
||||
it('should register event handler', () => {
|
||||
const handler = vi.fn();
|
||||
const eventName = 'test';
|
||||
|
||||
const unregister = eventBus.on(eventName, handler);
|
||||
|
||||
unregister();
|
||||
eventBus.once(eventName, handler);
|
||||
|
||||
eventBus.emit(eventName, {});
|
||||
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,51 +1,84 @@
|
|||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
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;
|
||||
type Payloads<ListenerMap> = {
|
||||
[E in keyof ListenerMap]: unknown;
|
||||
};
|
||||
|
||||
type Listener<Payload> = (payload: Payload) => void;
|
||||
|
||||
// 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]>,
|
||||
): void;
|
||||
|
||||
once<EventName extends keyof ListenerMap & string>(
|
||||
eventName: EventName,
|
||||
fn: Listener<ListenerMap[EventName]>,
|
||||
): void;
|
||||
|
||||
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) {
|
||||
const eventFns = handlers.get(eventName);
|
||||
|
||||
if (eventFns) {
|
||||
eventFns.splice(eventFns.indexOf(fn) >>> 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
function on(eventName: string, fn: CallbackFn): UnregisterFn {
|
||||
let eventFns = handlers.get(eventName);
|
||||
|
||||
if (!eventFns) {
|
||||
eventFns = [fn];
|
||||
} else {
|
||||
eventFns.push(fn);
|
||||
}
|
||||
|
||||
handlers.set(eventName, eventFns);
|
||||
|
||||
return () => off(eventName, fn);
|
||||
}
|
||||
|
||||
function emit<T = Event>(eventName: string, event?: T) {
|
||||
const eventFns = handlers.get(eventName);
|
||||
|
||||
if (eventFns) {
|
||||
eventFns.slice().forEach(async (handler) => {
|
||||
await handler(event);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
on,
|
||||
off,
|
||||
emit,
|
||||
on(eventName, fn) {
|
||||
let eventFns = handlers.get(eventName);
|
||||
if (!eventFns) {
|
||||
eventFns = [fn];
|
||||
} else {
|
||||
eventFns.push(fn);
|
||||
}
|
||||
handlers.set(eventName, eventFns);
|
||||
},
|
||||
|
||||
once(eventName, fn) {
|
||||
const handler: typeof fn = (payload) => {
|
||||
this.off(eventName, handler);
|
||||
fn(payload);
|
||||
};
|
||||
this.on(eventName, handler);
|
||||
},
|
||||
|
||||
off(eventName, fn) {
|
||||
const eventFns = handlers.get(eventName);
|
||||
if (eventFns) {
|
||||
eventFns.splice(eventFns.indexOf(fn) >>> 0, 1);
|
||||
}
|
||||
},
|
||||
|
||||
emit(eventName, event) {
|
||||
const eventFns = handlers.get(eventName);
|
||||
if (eventFns) {
|
||||
eventFns.slice().forEach((handler) => handler(event));
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
12
packages/design-system/src/utils/form-event-bus.ts
Normal file
12
packages/design-system/src/utils/form-event-bus.ts
Normal file
|
@ -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,4 +1,5 @@
|
|||
export * from './event-bus';
|
||||
export * from './form-event-bus';
|
||||
export * from './markdown';
|
||||
export * from './typeguards';
|
||||
export * from './uid';
|
||||
|
|
Loading…
Reference in a new issue