Skip to content

Commit 951e777

Browse files
author
qiaoyuwen
committed
feat: designer
1 parent 838d30b commit 951e777

File tree

5 files changed

+436
-0
lines changed

5 files changed

+436
-0
lines changed

packages/react-mobile/src/core/models/Engine.ts

Whitespace-only changes.
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
/* eslint-disable no-console */
2+
/* eslint-disable no-param-reassign */
3+
/* eslint-disable @typescript-eslint/no-unused-vars */
4+
import type { ISubscriber } from './subscribable';
5+
import type { Event } from './event';
6+
7+
export const EVENTS_SYMBOL = Symbol('__EVENTS_SYMBOL__');
8+
export const EVENTS_ONCE_SYMBOL = Symbol('EVENTS_ONCE_SYMBOL');
9+
export const EVENTS_BATCH_SYMBOL = Symbol('EVENTS_BATCH_SYMBOL');
10+
11+
type GlobalEnv = {
12+
ALL_EVENT_DRIVERS: EventDriver[];
13+
};
14+
15+
export const env: GlobalEnv = {
16+
ALL_EVENT_DRIVERS: [],
17+
};
18+
19+
export type EventContainer = Window | HTMLElement | HTMLDocument;
20+
export type EventDriverContainer = HTMLElement | HTMLDocument;
21+
22+
export interface ICustomEvent<EventData = any, EventContext = any> {
23+
type: string;
24+
data?: EventData;
25+
context?: EventContext;
26+
}
27+
28+
export type EventOptions =
29+
| boolean
30+
| (AddEventListenerOptions & EventListenerOptions & { once?: boolean });
31+
32+
export interface IEventDriver {
33+
container: EventDriverContainer;
34+
contentWindow: Window;
35+
36+
attach: (container: EventDriverContainer) => void;
37+
detach: (container: EventDriverContainer) => void;
38+
dispatch: <T extends ICustomEvent<any> = any>(event: T) => void | boolean;
39+
subscribe: <T extends ICustomEvent<any> = any>(subscriber: ISubscriber<T>) => void;
40+
41+
addEventListener: (<K extends keyof HTMLElementEventMap>(
42+
type: K,
43+
listener: (this: HTMLElement, event: HTMLElementEventMap[K]) => any,
44+
options?: EventOptions,
45+
) => void) &
46+
((type: string, listener: EventListenerOrEventListenerObject, options?: EventOptions) => void) &
47+
((type: any, listener: any, options: any) => void);
48+
49+
removeEventListener: (<K extends keyof HTMLElementEventMap>(
50+
type: K,
51+
listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any,
52+
options?: EventOptions,
53+
) => void) &
54+
((type: string, listener: EventListenerOrEventListenerObject, options?: EventOptions) => void) &
55+
((type: any, listener: any, options?: any) => void);
56+
57+
batchAddEventListener: (<K extends keyof HTMLElementEventMap>(
58+
type: K,
59+
listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any,
60+
options?: EventOptions,
61+
) => void) &
62+
((type: string, listener: EventListenerOrEventListenerObject, options?: EventOptions) => void) &
63+
((type: any, listener: any, options?: any) => void);
64+
65+
batchRemoveEventListener: (<K extends keyof HTMLElementEventMap>(
66+
type: K,
67+
listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any,
68+
options?: EventOptions,
69+
) => void) &
70+
((type: string, listener: EventListenerOrEventListenerObject, options?: EventOptions) => void) &
71+
((type: any, listener: any, options: any) => void);
72+
}
73+
74+
export interface IEventDriverClass<T> {
75+
new (engine: T, context?: any): IEventDriver;
76+
}
77+
78+
export interface CustomEventClass {
79+
new (...args: any[]): any;
80+
}
81+
82+
/**
83+
* 事件驱动器基类
84+
*/
85+
export class EventDriver<Engine extends Event = Event, Context = any> implements IEventDriver {
86+
engine: Engine;
87+
88+
container: EventDriverContainer = document;
89+
90+
contentWindow: Window = window;
91+
92+
context: Context;
93+
94+
constructor(engine: Engine, context: Context) {
95+
this.engine = engine;
96+
this.context = context;
97+
}
98+
99+
dispatch<T extends ICustomEvent<any> = any>(event: T) {
100+
return this.engine.dispatch(event, this.context);
101+
}
102+
103+
subscribe<T extends ICustomEvent<any> = any>(subscriber: ISubscriber<T>) {
104+
return this.engine.subscribe(subscriber);
105+
}
106+
107+
subscribeTo<T extends CustomEventClass>(type: T, subscriber: ISubscriber<InstanceType<T>>) {
108+
return this.engine.subscribeTo(type, subscriber);
109+
}
110+
111+
subscribeWith<T extends ICustomEvent = ICustomEvent>(
112+
type: string | string[],
113+
subscriber: ISubscriber<T>,
114+
) {
115+
return this.subscribeWith(type, subscriber);
116+
}
117+
118+
attach(container: EventDriverContainer) {
119+
console.error('attach must implement.');
120+
}
121+
122+
detach(container: EventDriverContainer) {
123+
console.error('attach must implement.');
124+
}
125+
126+
eventTarget(type: string) {
127+
if (type === 'resize' || type === 'scroll') {
128+
if (this.container === this.contentWindow?.document) {
129+
return this.contentWindow;
130+
}
131+
}
132+
return this.container;
133+
}
134+
135+
addEventListener<K extends keyof HTMLElementEventMap>(
136+
type: K,
137+
listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any,
138+
options?: boolean | EventOptions,
139+
): void;
140+
addEventListener(
141+
type: string,
142+
listener: EventListenerOrEventListenerObject,
143+
options?: boolean | EventOptions,
144+
): void;
145+
addEventListener(type: any, listener: any, options: any) {
146+
const target = this.eventTarget(type);
147+
if (options?.once) {
148+
target[EVENTS_ONCE_SYMBOL] = target[EVENTS_ONCE_SYMBOL] || {};
149+
delete options.once;
150+
if (!target[EVENTS_ONCE_SYMBOL][type]) {
151+
target.addEventListener(type, listener, options);
152+
target[EVENTS_ONCE_SYMBOL][type] = listener;
153+
}
154+
} else {
155+
target[EVENTS_SYMBOL] = target[EVENTS_SYMBOL] || {};
156+
target[EVENTS_SYMBOL][type] = target[EVENTS_SYMBOL][type] || new Map();
157+
if (!target[EVENTS_SYMBOL][type]?.get?.(listener)) {
158+
target.addEventListener(type, listener, options);
159+
target[EVENTS_SYMBOL][type]?.set?.(listener, true);
160+
}
161+
}
162+
}
163+
164+
removeEventListener<K extends keyof HTMLElementEventMap>(
165+
type: K,
166+
listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any,
167+
options?: boolean | EventOptions,
168+
): void;
169+
removeEventListener(
170+
type: string,
171+
listener: EventListenerOrEventListenerObject,
172+
options?: boolean | EventOptions,
173+
): void;
174+
removeEventListener(type: any, listener: any, options?: any) {
175+
const target = this.eventTarget(type);
176+
if (options?.once) {
177+
target[EVENTS_ONCE_SYMBOL] = target[EVENTS_ONCE_SYMBOL] || {};
178+
delete options.once;
179+
delete target[EVENTS_ONCE_SYMBOL][type];
180+
target.removeEventListener(type, listener, options);
181+
} else {
182+
target[EVENTS_SYMBOL] = target[EVENTS_SYMBOL] || {};
183+
target[EVENTS_SYMBOL][type] = target[EVENTS_SYMBOL][type] || new Map();
184+
target[EVENTS_SYMBOL][type]?.delete?.(listener);
185+
target.removeEventListener(type, listener, options);
186+
}
187+
}
188+
189+
batchAddEventListener<K extends keyof HTMLElementEventMap>(
190+
type: K,
191+
listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any,
192+
options?: boolean | EventOptions,
193+
): void;
194+
batchAddEventListener(
195+
type: string,
196+
listener: EventListenerOrEventListenerObject,
197+
options?: boolean | EventOptions,
198+
): void;
199+
batchAddEventListener(type: any, listener: any, options?: any) {
200+
if (!env.ALL_EVENT_DRIVERS.includes(this)) {
201+
env.ALL_EVENT_DRIVERS.push(this);
202+
}
203+
env.ALL_EVENT_DRIVERS.forEach((driver) => {
204+
const target = driver.eventTarget(type);
205+
target[EVENTS_BATCH_SYMBOL] = target[EVENTS_BATCH_SYMBOL] || {};
206+
if (!target[EVENTS_BATCH_SYMBOL][type]) {
207+
target.addEventListener(type, listener, options);
208+
target[EVENTS_BATCH_SYMBOL][type] = listener;
209+
}
210+
});
211+
}
212+
213+
batchRemoveEventListener<K extends keyof HTMLElementEventMap>(
214+
type: K,
215+
listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any,
216+
options?: boolean | EventOptions,
217+
): void;
218+
batchRemoveEventListener(
219+
type: string,
220+
listener: EventListenerOrEventListenerObject,
221+
options?: boolean | EventOptions,
222+
): void;
223+
batchRemoveEventListener(type: any, listener: any, options: any) {
224+
env.ALL_EVENT_DRIVERS.forEach((driver) => {
225+
const target = driver.eventTarget(type);
226+
target[EVENTS_BATCH_SYMBOL] = target[EVENTS_BATCH_SYMBOL] || {};
227+
target.removeEventListener(type, listener, options);
228+
delete target[EVENTS_BATCH_SYMBOL][type];
229+
});
230+
}
231+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/* eslint-disable no-param-reassign */
2+
/* eslint-disable consistent-return */
3+
import type {
4+
CustomEventClass,
5+
EventContainer,
6+
EventDriver,
7+
ICustomEvent,
8+
IEventDriverClass,
9+
} from './event-driver';
10+
import { env, EVENTS_BATCH_SYMBOL, EVENTS_ONCE_SYMBOL, EVENTS_SYMBOL } from './event-driver';
11+
import type { ISubscriber } from './subscribable';
12+
import { Subscribable } from './subscribable';
13+
import { isArr, isWindow } from './types';
14+
15+
const ATTACHED_SYMBOL = Symbol('ATTACHED_SYMBOL');
16+
17+
export interface IEventEffect<T> {
18+
(engine: T): void;
19+
}
20+
21+
export interface IEventProps<T = Event> {
22+
drivers?: IEventDriverClass<T>[];
23+
effects?: IEventEffect<T>[];
24+
}
25+
26+
/**
27+
* 事件引擎
28+
*/
29+
export class Event extends Subscribable<ICustomEvent> {
30+
private drivers: IEventDriverClass<any>[] = [];
31+
private containers: EventContainer[] = [];
32+
33+
constructor(props?: IEventProps) {
34+
super();
35+
if (isArr(props?.effects)) {
36+
props!.effects.forEach((plugin) => {
37+
plugin(this);
38+
});
39+
}
40+
if (isArr(props?.drivers)) {
41+
this.drivers = props!.drivers;
42+
}
43+
}
44+
45+
subscribeTo<T extends CustomEventClass>(type: T, subscriber: ISubscriber<InstanceType<T>>) {
46+
return this.subscribe((event) => {
47+
if (type && event instanceof type) {
48+
return subscriber(event);
49+
}
50+
});
51+
}
52+
53+
subscribeWith<T extends ICustomEvent = ICustomEvent>(
54+
type: string | string[],
55+
subscriber: ISubscriber<T>,
56+
) {
57+
return this.subscribe((event) => {
58+
if (isArr(type)) {
59+
if (type.includes(event?.type)) {
60+
return subscriber(event);
61+
}
62+
} else if (type && event?.type === type) {
63+
return subscriber(event);
64+
}
65+
});
66+
}
67+
68+
attachEvents(container: EventContainer, contentWindow: Window = window, context?: any) {
69+
if (!container) return;
70+
if (isWindow(container)) {
71+
return this.attachEvents(container.document, container, context);
72+
}
73+
if (container[ATTACHED_SYMBOL]) return;
74+
this.drivers.map((EventDriverClass) => {
75+
const driver = new EventDriverClass(this, context);
76+
driver.contentWindow = contentWindow;
77+
driver.container = container;
78+
driver.attach(container);
79+
return driver;
80+
});
81+
container[ATTACHED_SYMBOL] = true;
82+
if (!this.containers.includes(container)) {
83+
this.containers.push(container);
84+
}
85+
}
86+
87+
detachEvents(container?: EventContainer) {
88+
if (!container) {
89+
this.containers.forEach((item) => {
90+
this.detachEvents(item);
91+
});
92+
return;
93+
}
94+
if (container instanceof Window) {
95+
return this.detachEvents(container.document);
96+
}
97+
if (!container[ATTACHED_SYMBOL]) return;
98+
env.ALL_EVENT_DRIVERS = env.ALL_EVENT_DRIVERS.reduce((drivers: EventDriver[], driver) => {
99+
if (driver.container === container) {
100+
driver.detach(container);
101+
return drivers;
102+
}
103+
return drivers.concat(driver);
104+
}, []);
105+
this.containers = this.containers.filter((item) => item !== container);
106+
delete container[ATTACHED_SYMBOL];
107+
delete container[EVENTS_SYMBOL];
108+
delete container[EVENTS_ONCE_SYMBOL];
109+
delete container[EVENTS_BATCH_SYMBOL];
110+
}
111+
112+
destroy() {
113+
this.detachEvents();
114+
this.unsubscribe();
115+
}
116+
}

0 commit comments

Comments
 (0)