mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 20: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 N8nLink from '../N8nLink';
|
||||||
import N8nButton from '../N8nButton';
|
import N8nButton from '../N8nButton';
|
||||||
import type { IFormInput } from 'n8n-design-system/types';
|
import type { IFormInput } from 'n8n-design-system/types';
|
||||||
import { createEventBus } from '../../utils';
|
import { createFormEventBus } from '../../utils';
|
||||||
|
|
||||||
interface FormBoxProps {
|
interface FormBoxProps {
|
||||||
title?: string;
|
title?: string;
|
||||||
|
@ -67,7 +67,7 @@ withDefaults(defineProps<FormBoxProps>(), {
|
||||||
redirectLink: '',
|
redirectLink: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const formBus = createEventBus();
|
const formBus = createFormEventBus();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
submit: [value: { [key: string]: Value }];
|
submit: [value: { [key: string]: Value }];
|
||||||
update: [value: { name: string; value: Value }];
|
update: [value: { name: string; value: Value }];
|
||||||
|
|
|
@ -3,12 +3,12 @@ import { computed, onMounted, reactive, ref, watch } from 'vue';
|
||||||
import N8nFormInput from '../N8nFormInput';
|
import N8nFormInput from '../N8nFormInput';
|
||||||
import type { IFormInput } from '../../types';
|
import type { IFormInput } from '../../types';
|
||||||
import ResizeObserver from '../ResizeObserver';
|
import ResizeObserver from '../ResizeObserver';
|
||||||
import type { EventBus } from '../../utils';
|
import type { FormEventBus } from '../../utils';
|
||||||
import { createEventBus } from '../../utils';
|
import { createFormEventBus } from '../../utils';
|
||||||
|
|
||||||
export type FormInputsProps = {
|
export type FormInputsProps = {
|
||||||
inputs?: IFormInput[];
|
inputs?: IFormInput[];
|
||||||
eventBus?: EventBus;
|
eventBus?: FormEventBus;
|
||||||
columnView?: boolean;
|
columnView?: boolean;
|
||||||
verticalSpacing?: '' | 'xs' | 's' | 'm' | 'l' | 'xl';
|
verticalSpacing?: '' | 'xs' | 's' | 'm' | 'l' | 'xl';
|
||||||
teleported?: boolean;
|
teleported?: boolean;
|
||||||
|
@ -19,7 +19,7 @@ type Value = string | number | boolean | null | undefined;
|
||||||
|
|
||||||
const props = withDefaults(defineProps<FormInputsProps>(), {
|
const props = withDefaults(defineProps<FormInputsProps>(), {
|
||||||
inputs: () => [],
|
inputs: () => [],
|
||||||
eventBus: createEventBus,
|
eventBus: createFormEventBus,
|
||||||
columnView: false,
|
columnView: false,
|
||||||
verticalSpacing: '',
|
verticalSpacing: '',
|
||||||
teleported: true,
|
teleported: true,
|
||||||
|
|
|
@ -14,18 +14,30 @@ describe('createEventBus()', () => {
|
||||||
|
|
||||||
expect(handler).toHaveBeenCalled();
|
expect(handler).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should return unregister fn', () => {
|
describe('once()', () => {
|
||||||
|
it('should register event handler', () => {
|
||||||
const handler = vi.fn();
|
const handler = vi.fn();
|
||||||
const eventName = 'test';
|
const eventName = 'test';
|
||||||
|
|
||||||
const unregister = eventBus.on(eventName, handler);
|
eventBus.once(eventName, handler);
|
||||||
|
|
||||||
unregister();
|
|
||||||
|
|
||||||
eventBus.emit(eventName, {});
|
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
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
export type CallbackFn = Function;
|
export type CallbackFn = Function;
|
||||||
export type UnregisterFn = () => void;
|
|
||||||
|
|
||||||
export interface EventBus {
|
type Payloads<ListenerMap> = {
|
||||||
on: (eventName: string, fn: CallbackFn) => UnregisterFn;
|
[E in keyof ListenerMap]: unknown;
|
||||||
off: (eventName: string, fn: CallbackFn) => void;
|
};
|
||||||
emit: <T = Event>(eventName: string, event?: T) => void;
|
|
||||||
|
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[]>();
|
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 {
|
return {
|
||||||
on,
|
on(eventName, fn) {
|
||||||
off,
|
let eventFns = handlers.get(eventName);
|
||||||
emit,
|
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 './event-bus';
|
||||||
|
export * from './form-event-bus';
|
||||||
export * from './markdown';
|
export * from './markdown';
|
||||||
export * from './typeguards';
|
export * from './typeguards';
|
||||||
export * from './uid';
|
export * from './uid';
|
||||||
|
|
Loading…
Reference in a new issue