@psytask/core 1.0.0 → 1.1.0-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/index.d.ts +170 -124
- package/dist/index.js +132 -98
- package/dist/index.min.js +1 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[PsyTask](https://github.com/bluebonesx/psytask) scene and event emitter model.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,68 +1,116 @@
|
|
|
1
|
-
type LooseObject = Record<string,
|
|
2
|
-
type Merge<T, U> = T extends
|
|
1
|
+
type LooseObject = Record<string, unknown>;
|
|
2
|
+
type Merge<T, U> = T extends unknown ? Omit<T, keyof U> & U : never;
|
|
3
|
+
type Split<T extends string, S extends string> = T extends unknown
|
|
4
|
+
? T extends `${infer L}${S}${infer R}`
|
|
5
|
+
? [L, ...Split<R, S>]
|
|
6
|
+
: [T]
|
|
7
|
+
: never;
|
|
8
|
+
|
|
9
|
+
declare global {
|
|
10
|
+
interface ObjectConstructor {
|
|
11
|
+
keys<T>(o: T): (keyof T & string)[];
|
|
12
|
+
}
|
|
13
|
+
interface String {
|
|
14
|
+
split<T extends string, S extends string>(
|
|
15
|
+
this: T,
|
|
16
|
+
separator: S,
|
|
17
|
+
): Split<T, S>;
|
|
18
|
+
}
|
|
19
|
+
interface Array<T> {
|
|
20
|
+
map<R>(fn: <E extends T>(e: E, i: number, arr: this) => R): R[];
|
|
21
|
+
}
|
|
22
|
+
interface Performance {
|
|
23
|
+
getEntriesByType(type: 'resource'): PerformanceResourceTiming[];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
3
26
|
|
|
4
27
|
declare const symbol: typeof Symbol.dispose;
|
|
28
|
+
type EventMap<T extends LooseObject> = T & {
|
|
29
|
+
dispose: undefined;
|
|
30
|
+
};
|
|
5
31
|
/** {@link Disposable} event emitter, use {@link Set} to manage listeners */
|
|
6
|
-
declare class EventEmitter<
|
|
32
|
+
declare class EventEmitter<T extends LooseObject & {
|
|
7
33
|
dispose?: never;
|
|
8
|
-
} = {}
|
|
9
|
-
dispose: null;
|
|
10
|
-
} = M & {
|
|
11
|
-
dispose: null;
|
|
12
|
-
}> implements Disposable {
|
|
34
|
+
} = {}> implements Disposable {
|
|
13
35
|
readonly listeners: {
|
|
14
|
-
|
|
36
|
+
[K in keyof EventMap<T>]?: Set<(e: EventMap<T>[K]) => void>;
|
|
15
37
|
};
|
|
16
38
|
[symbol](): void;
|
|
17
39
|
/** Add event listener */
|
|
18
|
-
on<K extends keyof EventMap
|
|
40
|
+
on<K extends keyof EventMap<T>>(type: K, listener: (evt: EventMap<T>[K]) => void): this;
|
|
19
41
|
/** Remove event listener */
|
|
20
|
-
off<K extends keyof EventMap
|
|
42
|
+
off<K extends keyof EventMap<T>>(type: K, listener: (evt: EventMap<T>[K]) => void): this;
|
|
21
43
|
/** Add one-time event listener, can not be removed manually */
|
|
22
|
-
once<K extends keyof EventMap
|
|
44
|
+
once<K extends keyof EventMap<T>>(type: K, listener: (evt: EventMap<T>[K]) => void): this;
|
|
23
45
|
/** Emit event listeners */
|
|
24
|
-
emit<K extends keyof EventMap
|
|
46
|
+
emit<K extends keyof EventMap<T>>(type: K, ...[evt]: EventMap<T>[K] extends undefined ? [evt?: EventMap<T>[K]] : [evt: EventMap<T>[K]]): this;
|
|
25
47
|
}
|
|
26
48
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
49
|
+
type TimerRecords = number[];
|
|
50
|
+
type Timer = {
|
|
51
|
+
start(onFrame?: (records: TimerRecords) => void): Promise<TimerRecords>;
|
|
52
|
+
stop(): void;
|
|
30
53
|
};
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
54
|
+
/**
|
|
55
|
+
* ## Render logic
|
|
56
|
+
*
|
|
57
|
+
* ```text
|
|
58
|
+
* rAF(scene_1.show) -> render -> vsync -> rAF[scene_1.start_time] -> ... ->
|
|
59
|
+
* rAF(scene_2.show) -> ...
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* ## Closing condition
|
|
63
|
+
*
|
|
64
|
+
* | symbol/expression | description |
|
|
65
|
+
* | :---------------------: | ------------------- |
|
|
66
|
+
* | t | current frame time |
|
|
67
|
+
* | t_0 | start frame time |
|
|
68
|
+
* | D | duration |
|
|
69
|
+
* | \delta | next frame duration |
|
|
70
|
+
* | e = t - t_0 - D | duration error |
|
|
71
|
+
* | \|e\| <= \|e + \delta\| | closing condition |
|
|
72
|
+
*
|
|
73
|
+
* Inference:
|
|
74
|
+
*
|
|
75
|
+
* ```text
|
|
76
|
+
* For |e| <= |e + \delta|, given that \delta > 0
|
|
77
|
+
* if e >= 0 then e <= e + \delta -> true
|
|
78
|
+
* if e < 0 then -e <= |e + \delta|
|
|
79
|
+
* if e + \delta >= 0 then -e <= e + \delta -> e >= -\delta / 2
|
|
80
|
+
* if e + \delta < 0 then -e <= -e - \delta -> false
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
declare const createTimer: (shouldStop: (records: TimerRecords) => boolean) => Timer;
|
|
84
|
+
type NodeLike = string | Node;
|
|
85
|
+
type BuiltinData = {
|
|
86
|
+
frame_times: TimerRecords;
|
|
34
87
|
};
|
|
35
|
-
type
|
|
36
|
-
|
|
37
|
-
duration?: number;
|
|
38
|
-
onStart: (time: number) => void;
|
|
39
|
-
onFrame: (time: number) => void;
|
|
40
|
-
}) => {
|
|
41
|
-
promise: Promise<void>;
|
|
42
|
-
close: () => void;
|
|
88
|
+
type ForbiddenData = {
|
|
89
|
+
[K in keyof BuiltinData]?: never;
|
|
43
90
|
};
|
|
44
|
-
type NodeLike = string | Node;
|
|
45
91
|
/**
|
|
46
|
-
*
|
|
92
|
+
* Only called once when the scene is created.
|
|
47
93
|
*
|
|
48
94
|
* @param props - The reactive props to control the scene display
|
|
49
|
-
* @param ctx - The scene instance, can be used to manage lifecycle
|
|
50
95
|
* @see {@link Scene}
|
|
51
96
|
*/
|
|
52
|
-
type
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
97
|
+
type Component<P extends LooseObject = any, D extends LooseObject = LooseObject & ForbiddenData> = {
|
|
98
|
+
(props: P): NodeLike | NodeLike[] | {
|
|
99
|
+
/** The node(s) appended to the root element of scene */
|
|
100
|
+
node: NodeLike | NodeLike[];
|
|
101
|
+
/** Data getter to get data from elements */
|
|
102
|
+
data: () => D;
|
|
103
|
+
};
|
|
57
104
|
};
|
|
58
|
-
type SceneShow<P extends LooseObject =
|
|
59
|
-
|
|
105
|
+
type SceneShow<P extends LooseObject = LooseObject, D extends LooseObject = LooseObject & ForbiddenData> = (patchProps?: Partial<P>) => Promise<Merge<D, BuiltinData>>;
|
|
106
|
+
/** Same with {@link SceneShow} */
|
|
107
|
+
type GenericComponent<P extends LooseObject = LooseObject, D extends LooseObject = LooseObject & ForbiddenData> = SceneShow<P, D>;
|
|
60
108
|
/**
|
|
61
|
-
* Provide type infer for generic
|
|
109
|
+
* Provide type infer for generic component, do nothing in runtime.
|
|
62
110
|
*
|
|
63
111
|
* @example
|
|
64
112
|
*
|
|
65
|
-
* Support generic
|
|
113
|
+
* Support generic component
|
|
66
114
|
*
|
|
67
115
|
* ```ts
|
|
68
116
|
* using scene = new Scene(
|
|
@@ -74,128 +122,126 @@ type GenericSceneSetup<P extends LooseObject = any, D extends LooseObject = Loos
|
|
|
74
122
|
* ```
|
|
75
123
|
*/
|
|
76
124
|
declare const generic: {
|
|
77
|
-
<P extends LooseObject, D extends LooseObject &
|
|
125
|
+
<P extends LooseObject, D extends LooseObject & ForbiddenData = {}>(f: Component<P, D>): GenericComponent<P, D>;
|
|
78
126
|
};
|
|
79
127
|
/** @ignore */
|
|
80
|
-
type
|
|
128
|
+
type MaybeGenericComponent<P extends LooseObject = any, D extends LooseObject & ForbiddenData = {}> = Component<P, D> | GenericComponent<P, D>;
|
|
129
|
+
type ComponentAdapter = {
|
|
130
|
+
/** Wrap a component with reactive props */
|
|
131
|
+
wrap: <T extends Component>(component: T) => T;
|
|
132
|
+
/** Render a component with default props and provided scene */
|
|
133
|
+
render: <T extends Component>(component: T, defaultProps: Parameters<T>[0],
|
|
134
|
+
/** If not provided, it will use current scene */
|
|
135
|
+
ctx?: Scene<Component>) => {
|
|
136
|
+
props: Parameters<T>[0];
|
|
137
|
+
} & (ReturnType<T> extends infer R ? R extends {
|
|
138
|
+
node: infer N;
|
|
139
|
+
data: infer D;
|
|
140
|
+
} ? {
|
|
141
|
+
nodes: N extends NodeLike ? [N] : N;
|
|
142
|
+
data: D;
|
|
143
|
+
} : {
|
|
144
|
+
nodes: R extends NodeLike ? [R] : R;
|
|
145
|
+
data: undefined;
|
|
146
|
+
} : never);
|
|
147
|
+
};
|
|
148
|
+
declare const createComponentAdapter: (reactive: <T extends LooseObject>(obj: T) => T) => ComponentAdapter;
|
|
81
149
|
/**
|
|
82
|
-
*
|
|
150
|
+
* Lifecycle hooks.
|
|
83
151
|
*
|
|
84
|
-
* | name
|
|
85
|
-
* |
|
|
86
|
-
* |
|
|
87
|
-
* |
|
|
88
|
-
* |
|
|
89
|
-
* | mouse:left | the left mouse button is pressed |
|
|
90
|
-
* | mouse:middle | the middle mouse button is pressed |
|
|
91
|
-
* | mouse:right | the right mouse button is pressed |
|
|
92
|
-
* | mouse:unknown | an unknown mouse button is pressed |
|
|
93
|
-
* | key:\<key\> | a {@link https://developer.mozilla.org/docs/Web/API/UI_Events/Keyboard_event_key_values key} is pressed |
|
|
94
|
-
* | \<HTMLElement-Event\> | an {@link https://developer.mozilla.org/docs/Web/API/HTMLElement#events html element event} is triggered |
|
|
152
|
+
* | name | trigger timing |
|
|
153
|
+
* | ----- | ------------------------------------- |
|
|
154
|
+
* | show | the scene is shown |
|
|
155
|
+
* | frame | on each frame when the scene is shown |
|
|
156
|
+
* | close | the scene is closed |
|
|
95
157
|
*/
|
|
96
|
-
type SceneEventMap =
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
} & {
|
|
101
|
-
[K in `mouse:${'left' | 'middle' | 'right' | 'unknown'}`]: MouseEvent;
|
|
102
|
-
} & {
|
|
103
|
-
[K in `key:${string}`]: KeyboardEvent;
|
|
158
|
+
type SceneEventMap = {
|
|
159
|
+
show: undefined;
|
|
160
|
+
frame: number;
|
|
161
|
+
close: undefined;
|
|
104
162
|
};
|
|
163
|
+
/**
|
|
164
|
+
* @example
|
|
165
|
+
*
|
|
166
|
+
* Must be called on the top scope of component
|
|
167
|
+
*
|
|
168
|
+
* ```ts
|
|
169
|
+
* const Component = (props: {}) => {
|
|
170
|
+
* const ctx = getCurrentScene();
|
|
171
|
+
* return '';
|
|
172
|
+
* };
|
|
173
|
+
* ```
|
|
174
|
+
*
|
|
175
|
+
* DO NOT call on other place
|
|
176
|
+
*
|
|
177
|
+
* ```ts
|
|
178
|
+
* const Component = (props: {}) => {
|
|
179
|
+
* const fn = () => {
|
|
180
|
+
* const ctx = getCurrentScene(); // WRONG!
|
|
181
|
+
* };
|
|
182
|
+
* return '';
|
|
183
|
+
* };
|
|
184
|
+
* ```
|
|
185
|
+
*/
|
|
186
|
+
declare const getCurrentScene: () => Scene<MaybeGenericComponent<any, {}>>;
|
|
105
187
|
/** Scene options */
|
|
106
|
-
type SceneOptions<T extends
|
|
188
|
+
type SceneOptions<T extends MaybeGenericComponent> = {
|
|
107
189
|
/** Root element */
|
|
108
190
|
root: HTMLDivElement;
|
|
109
|
-
/** Frame duration in milliseconds */
|
|
110
|
-
frame_ms: number;
|
|
111
191
|
/** Default props */
|
|
112
192
|
defaultProps: Parameters<T>[0];
|
|
113
|
-
/**
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
|
|
117
|
-
/** Whether to record frame times */
|
|
118
|
-
record_frame_times?: boolean;
|
|
119
|
-
/** Control display timing */
|
|
120
|
-
createTimer?: SceneTimerCreator;
|
|
193
|
+
/** Control show timing */
|
|
194
|
+
timer: () => Timer;
|
|
195
|
+
/** Component adapter */
|
|
196
|
+
adapter: ComponentAdapter;
|
|
121
197
|
};
|
|
122
|
-
declare class Scene<T extends
|
|
198
|
+
declare class Scene<T extends MaybeGenericComponent> extends EventEmitter<SceneEventMap> {
|
|
123
199
|
#private;
|
|
124
|
-
|
|
200
|
+
readonly options: SceneOptions<T>;
|
|
125
201
|
readonly root: HTMLDivElement;
|
|
202
|
+
readonly data: T extends MaybeGenericComponent<infer P, infer D> ? () => D : undefined;
|
|
126
203
|
/**
|
|
127
|
-
* Show
|
|
204
|
+
* Show DOM and update props one-time
|
|
128
205
|
*
|
|
129
206
|
* @example
|
|
130
207
|
*
|
|
131
|
-
*
|
|
208
|
+
* Integrate with `@vue/reactivity`
|
|
132
209
|
*
|
|
133
210
|
* ```ts
|
|
211
|
+
* import { reactive, effect } from '@vue/reactivity';
|
|
212
|
+
*
|
|
134
213
|
* using scene = new Scene(
|
|
135
|
-
* (props: { text: string }
|
|
214
|
+
* (props: { text: string }) => {
|
|
136
215
|
* const node = document.createElement('div');
|
|
137
|
-
*
|
|
138
|
-
* node.textContent =
|
|
216
|
+
* effect(() => {
|
|
217
|
+
* node.textContent = props.text; // auto update when props.text changes
|
|
139
218
|
* });
|
|
140
219
|
* return node;
|
|
141
220
|
* },
|
|
142
221
|
* {
|
|
143
|
-
*
|
|
222
|
+
* root: document.createElement('div'),
|
|
144
223
|
* defaultProps: { text: 'default' },
|
|
224
|
+
* adapter: createComponentAdapter(reactive),
|
|
225
|
+
* timer: () => createTimer((records) => records.length > 100), // show 100 frames
|
|
145
226
|
* },
|
|
146
227
|
* );
|
|
228
|
+
* document.body.appendChild(scene.root);
|
|
229
|
+
*
|
|
147
230
|
* await scene.show({ text: 'new' }); // show `new`
|
|
148
231
|
* await scene.show(); // show `default`
|
|
149
232
|
* ```
|
|
150
233
|
*
|
|
151
234
|
* @function
|
|
152
235
|
*/
|
|
153
|
-
show: T extends
|
|
236
|
+
show: T extends Component<infer P, infer D> ? SceneShow<P, D> : T;
|
|
154
237
|
/**
|
|
155
|
-
* @param
|
|
156
|
-
* @param
|
|
238
|
+
* @param component - {@link Component}
|
|
239
|
+
* @param options - {@link SceneOptions}
|
|
157
240
|
*/
|
|
158
|
-
constructor(
|
|
159
|
-
/**
|
|
160
|
-
* Override default options one-time
|
|
161
|
-
*
|
|
162
|
-
* @example
|
|
163
|
-
*
|
|
164
|
-
* Change duration temporarily
|
|
165
|
-
*
|
|
166
|
-
* ```ts
|
|
167
|
-
* using scene = new Scene(() => '', {
|
|
168
|
-
* root: document.appendChild(document.createElement('div')),
|
|
169
|
-
* frame_ms: 16.67,
|
|
170
|
-
* defaultProps: {},
|
|
171
|
-
* duration: 100,
|
|
172
|
-
* });
|
|
173
|
-
* await scene.config({ duration: 200 }).show(); // show 200ms
|
|
174
|
-
* await scene.show(); // show 100ms
|
|
175
|
-
* ```
|
|
176
|
-
*/
|
|
177
|
-
config(patchOptions: {
|
|
178
|
-
[K in {
|
|
179
|
-
[K in keyof SceneOptions<T>]-?: undefined extends SceneOptions<T>[K] ? K : never;
|
|
180
|
-
}[keyof SceneOptions<T>]]?: SceneOptions<T>[K];
|
|
181
|
-
}): this;
|
|
182
|
-
/** Add a microtask to close the scene. It is useful when close in 'scene:show' */
|
|
241
|
+
constructor(component: T, options: SceneOptions<T>);
|
|
242
|
+
/** Add a microtask to close the scene. It is useful when close in 'show' */
|
|
183
243
|
close(): Promise<void>;
|
|
184
244
|
}
|
|
185
245
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
* Add event listener and return cleanup function
|
|
189
|
-
*
|
|
190
|
-
* @example
|
|
191
|
-
*
|
|
192
|
-
* Listen to window resize event
|
|
193
|
-
*
|
|
194
|
-
* ```ts
|
|
195
|
-
* const cleanup = on(window, 'resize', (e) => {});
|
|
196
|
-
* ```
|
|
197
|
-
*/
|
|
198
|
-
declare const on: <T extends EventTarget, K extends EventType<T>>(target: T, type: K, listener: (ev: `on${K}` extends infer P extends Extract<keyof T, string> ? T[P] & {} extends infer F extends (...args: any) => any ? Parameters<F>[0] : never : never) => void, options?: boolean | AddEventListenerOptions) => () => void;
|
|
199
|
-
|
|
200
|
-
export { EventEmitter, Scene, generic, on };
|
|
201
|
-
export type { MaybeGenericSceneSetup, NodeLike, SceneEventMap, SceneOptions, SceneSetup, SceneTimerCreator };
|
|
246
|
+
export { EventEmitter, Scene, createComponentAdapter, createTimer, generic, getCurrentScene };
|
|
247
|
+
export type { Component, ComponentAdapter, MaybeGenericComponent, NodeLike, SceneEventMap, SceneOptions, Timer, TimerRecords };
|
package/dist/index.js
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
/** @psytask/core v1.
|
|
2
|
-
|
|
3
|
-
// src/event-emitter.ts
|
|
4
|
-
var symbol = Symbol.dispose ?? Symbol.for("Symbol.dispose");
|
|
5
|
-
|
|
1
|
+
/** @psytask/core v1.1.0-0 cubxx MIT */
|
|
2
|
+
const symbol = Symbol.dispose ?? /* @__PURE__ */ Symbol.for("Symbol.dispose");
|
|
6
3
|
class EventEmitter {
|
|
7
4
|
listeners = {};
|
|
8
5
|
[symbol]() {
|
|
9
|
-
this.emit("dispose"
|
|
6
|
+
this.emit("dispose");
|
|
10
7
|
}
|
|
8
|
+
/** Add event listener */
|
|
11
9
|
on(type, listener) {
|
|
12
|
-
(this.listeners[type] ??= new Set).add(listener);
|
|
10
|
+
(this.listeners[type] ??= /* @__PURE__ */ new Set()).add(listener);
|
|
13
11
|
return this;
|
|
14
12
|
}
|
|
13
|
+
/** Remove event listener */
|
|
15
14
|
off(type, listener) {
|
|
16
15
|
const listeners = this.listeners[type];
|
|
17
16
|
if (listeners) {
|
|
@@ -20,6 +19,7 @@ class EventEmitter {
|
|
|
20
19
|
}
|
|
21
20
|
return this;
|
|
22
21
|
}
|
|
22
|
+
/** Add one-time event listener, can not be removed manually */
|
|
23
23
|
once(type, listener) {
|
|
24
24
|
const wrapper = (evt) => {
|
|
25
25
|
try {
|
|
@@ -31,113 +31,147 @@ class EventEmitter {
|
|
|
31
31
|
this.on(type, wrapper);
|
|
32
32
|
return this;
|
|
33
33
|
}
|
|
34
|
-
|
|
34
|
+
/** Emit event listeners */
|
|
35
|
+
emit(type, ...[evt]) {
|
|
35
36
|
const listeners = this.listeners[type];
|
|
36
|
-
if (listeners)
|
|
37
|
-
for (const listener of [...listeners])
|
|
38
|
-
listener(e);
|
|
37
|
+
if (listeners) for (const listener of [...listeners]) listener(evt);
|
|
39
38
|
return this;
|
|
40
39
|
}
|
|
41
40
|
}
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
|
|
42
|
+
const ERR = (msg) => {
|
|
44
43
|
throw Error(msg);
|
|
45
44
|
};
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
const rAF = requestAnimationFrame;
|
|
46
|
+
const $Object = Object;
|
|
47
|
+
const modify = (a, b) => $Object.assign(a, b);
|
|
48
|
+
const doc = document;
|
|
49
|
+
new Proxy(
|
|
50
|
+
{},
|
|
51
|
+
{
|
|
52
|
+
get: (_, tag) => (props) => modify(doc.createElement(tag), props)
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
const array_normalize = (e) => Array.isArray(e) ? e : [e];
|
|
51
56
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
const createTimer = (shouldStop) => {
|
|
58
|
+
const timer = {
|
|
59
|
+
start: (cb) => new Promise((resolve) => {
|
|
60
|
+
timer.stop = () => (cancelAnimationFrame(handle), resolve(records));
|
|
61
|
+
const records = [];
|
|
62
|
+
const frame = (time) => {
|
|
63
|
+
records.push(time);
|
|
64
|
+
cb?.(records);
|
|
65
|
+
shouldStop(records) ? timer.stop() : handle = rAF(frame);
|
|
66
|
+
};
|
|
67
|
+
let handle = rAF(frame);
|
|
68
|
+
}),
|
|
69
|
+
stop() {
|
|
70
|
+
}
|
|
61
71
|
};
|
|
72
|
+
return timer;
|
|
62
73
|
};
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
74
|
+
const generic = (f) => (
|
|
75
|
+
//@ts-expect-error impl generic component
|
|
76
|
+
f
|
|
77
|
+
);
|
|
78
|
+
const createComponentAdapter = (reactive) => ({
|
|
79
|
+
wrap: (component) => ((props) => component(reactive(props))),
|
|
80
|
+
render: (component, defaultProps, ctx) => {
|
|
81
|
+
const props = reactive(defaultProps);
|
|
82
|
+
ctx && sceneStack.push(ctx);
|
|
83
|
+
const instanceOrNode = component(props);
|
|
84
|
+
ctx && sceneStack.pop();
|
|
85
|
+
let data;
|
|
86
|
+
const nodes = array_normalize(
|
|
87
|
+
typeof instanceOrNode !== "string" && "node" in instanceOrNode ? (data = instanceOrNode.data, instanceOrNode.node) : instanceOrNode
|
|
88
|
+
);
|
|
89
|
+
return { props, nodes, data };
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
const sceneStack = [];
|
|
93
|
+
const getCurrentScene = () => sceneStack[sceneStack.length - 1] ?? ERR("Not found current scene");
|
|
72
94
|
class Scene extends EventEmitter {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
#timer;
|
|
79
|
-
constructor(setup, defaultOptions) {
|
|
95
|
+
/**
|
|
96
|
+
* @param component - {@link Component}
|
|
97
|
+
* @param options - {@link SceneOptions}
|
|
98
|
+
*/
|
|
99
|
+
constructor(component, options) {
|
|
80
100
|
super();
|
|
81
|
-
|
|
101
|
+
this.options = options;
|
|
102
|
+
const { root, adapter, defaultProps, timer } = options;
|
|
82
103
|
(this.root = root).tabIndex = -1;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
config(patchOptions) {
|
|
93
|
-
this.#options = { ...this.#defaultOptions, ...patchOptions };
|
|
94
|
-
return this;
|
|
104
|
+
root.style.scale = "0";
|
|
105
|
+
const { props, nodes, data } = adapter.render(
|
|
106
|
+
component,
|
|
107
|
+
{ ...defaultProps },
|
|
108
|
+
this.on("dispose", () => root.remove())
|
|
109
|
+
);
|
|
110
|
+
this.#timer = timer();
|
|
111
|
+
this.#props = props, this.data = data;
|
|
112
|
+
root.append(...nodes);
|
|
95
113
|
}
|
|
114
|
+
root;
|
|
115
|
+
#timer;
|
|
116
|
+
#props;
|
|
117
|
+
data;
|
|
118
|
+
/**
|
|
119
|
+
* Show DOM and update props one-time
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
*
|
|
123
|
+
* Integrate with `@vue/reactivity`
|
|
124
|
+
*
|
|
125
|
+
* ```ts
|
|
126
|
+
* import { reactive, effect } from '@vue/reactivity';
|
|
127
|
+
*
|
|
128
|
+
* using scene = new Scene(
|
|
129
|
+
* (props: { text: string }) => {
|
|
130
|
+
* const node = document.createElement('div');
|
|
131
|
+
* effect(() => {
|
|
132
|
+
* node.textContent = props.text; // auto update when props.text changes
|
|
133
|
+
* });
|
|
134
|
+
* return node;
|
|
135
|
+
* },
|
|
136
|
+
* {
|
|
137
|
+
* root: document.createElement('div'),
|
|
138
|
+
* defaultProps: { text: 'default' },
|
|
139
|
+
* adapter: createComponentAdapter(reactive),
|
|
140
|
+
* timer: () => createTimer((records) => records.length > 100), // show 100 frames
|
|
141
|
+
* },
|
|
142
|
+
* );
|
|
143
|
+
* document.body.appendChild(scene.root);
|
|
144
|
+
*
|
|
145
|
+
* await scene.show({ text: 'new' }); // show `new`
|
|
146
|
+
* await scene.show(); // show `default`
|
|
147
|
+
* ```
|
|
148
|
+
*
|
|
149
|
+
* @function
|
|
150
|
+
*/
|
|
151
|
+
//@ts-expect-error impl generic component
|
|
152
|
+
show = this.#show;
|
|
153
|
+
/** Add a microtask to close the scene. It is useful when close in 'show' */
|
|
96
154
|
async close() {
|
|
97
155
|
await 0;
|
|
98
|
-
this.#timer
|
|
156
|
+
this.#timer.stop();
|
|
99
157
|
}
|
|
100
|
-
async#show(patchProps) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
defaultProps,
|
|
108
|
-
duration,
|
|
109
|
-
close_on,
|
|
110
|
-
record_frame_times
|
|
111
|
-
} = this.#options;
|
|
112
|
-
this.emit("scene:show", { ...defaultProps, ...patchProps });
|
|
113
|
-
root.style.transform = "scale(1)";
|
|
158
|
+
async #show(patchProps) {
|
|
159
|
+
const { root, defaultProps } = this.options;
|
|
160
|
+
const newProps = { ...defaultProps, ...patchProps };
|
|
161
|
+
for (const key of Object.keys({ ...this.#props, ...newProps }))
|
|
162
|
+
this.#props[key] = newProps[key];
|
|
163
|
+
this.emit("show");
|
|
164
|
+
root.style.scale = "1";
|
|
114
165
|
root.focus();
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
});
|
|
124
|
-
this.once("scene:close", () => cleanups.map((fn) => fn && fn()));
|
|
125
|
-
const showInfo = createShowInfo();
|
|
126
|
-
await (this.#timer = createTimer({
|
|
127
|
-
frame_ms,
|
|
128
|
-
duration,
|
|
129
|
-
onStart: (time) => showInfo.start_time = time,
|
|
130
|
-
onFrame: (time) => {
|
|
131
|
-
record_frame_times && showInfo.frame_times.push(time);
|
|
132
|
-
this.emit("scene:frame", time);
|
|
133
|
-
}
|
|
134
|
-
})).promise;
|
|
135
|
-
return { ...this.emit("scene:close", null).#data?.(), ...showInfo };
|
|
166
|
+
const records = await this.#timer.start(
|
|
167
|
+
(records2) => this.emit("frame", records2[records2.length - 1])
|
|
168
|
+
);
|
|
169
|
+
root.style.scale = "0";
|
|
170
|
+
return {
|
|
171
|
+
...this.emit("close").data?.(),
|
|
172
|
+
frame_times: records
|
|
173
|
+
};
|
|
136
174
|
}
|
|
137
175
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
generic,
|
|
141
|
-
Scene,
|
|
142
|
-
EventEmitter
|
|
143
|
-
};
|
|
176
|
+
|
|
177
|
+
export { EventEmitter, Scene, createComponentAdapter, createTimer, generic, getCurrentScene };
|
package/dist/index.min.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
var V=Symbol.dispose??Symbol.for("Symbol.dispose");class P{listeners={};[V](){this.emit("dispose",null)}on(T,c){return(this.listeners[T]??=new Set).add(c),this}off(T,c){let x=this.listeners[T];if(x)x.delete(c),x.size===0&&delete this.listeners[T];return this}once(T,c){let x=(k)=>{try{c(k)}finally{this.off(T,x)}};return this.on(T,x),this}emit(T,c){let x=this.listeners[T];if(x)for(let k of[...x])k(c);return this}}var A=(T)=>{throw Error(T)},r=requestAnimationFrame;var C=(T)=>Array.isArray(T)?T:[T];var E=(T,c,x,k)=>(T.addEventListener(c,x,k),()=>T.removeEventListener(c,x,k));var B=()=>({start_time:NaN,frame_times:[]}),F=(T)=>{let c,x,k=(b)=>(T.onFrame(b),typeof T.duration==="number"&&b-c>=T.duration-T.frame_ms*1.5?x():U=r(k)),U=r((b)=>(T.onStart(b),k(c=b)));return{promise:new Promise((b)=>x=()=>(cancelAnimationFrame(U),b())),close:x}},d=(T)=>T,G=["left","middle","right"],J={scene:0,dispose:0,key:"keydown",mouse:"mousedown"};class Q extends P{root;#c;show=this.#j;#x;#b;#T;constructor(T,c){super();let{root:x,defaultProps:k}=this.#b=c;(this.root=x).tabIndex=-1;let U=()=>{x.style.transform="scale(0)",this.#x=c,this.#T=void 0};U();let b=T(k,this.on("scene:close",U).on("dispose",()=>x.remove()));x.append(...C(typeof b==="object"&&"node"in b?(this.#c=b.data,b.node):b))}config(T){return this.#x={...this.#b,...T},this}async close(){await 0,this.#T?.close()}async#j(T){if(this.#T)A("Scene is showing");let{root:c,frame_ms:x,createTimer:k=F,defaultProps:U,duration:b,close_on:H,record_frame_times:L}=this.#x;if(this.emit("scene:show",{...U,...T}),c.style.transform="scale(1)",c.focus(),typeof H<"u"){let j=()=>this.close();C(H).map((K)=>this.on(K,j).once("scene:close",()=>this.off(K,j)))}let z=0,$=0,q=Object.keys(this.listeners).map((j)=>{let K=J[j.split(":",1)[0]]??j;return K==="keydown"?!$++&&E(c,K,(n)=>this.emit(`key:${n.key}`,n).emit(K,n)):K==="mousedown"?!z++&&E(c,K,(n)=>this.emit(`mouse:${G[n.button]??"unknown"}`,n).emit(K,n)):K&&E(c,K,(n)=>this.emit(j,n))});this.once("scene:close",()=>q.map((j)=>j&&j()));let g=B();return await(this.#T=k({frame_ms:x,duration:b,onStart:(j)=>g.start_time=j,onFrame:(j)=>{L&&g.frame_times.push(j),this.emit("scene:frame",j)}})).promise,{...this.emit("scene:close",null).#c?.(),...g}}}export{E as on,d as generic,Q as Scene,P as EventEmitter};
|
|
1
|
+
const m=Symbol.dispose??Symbol.for("Symbol.dispose");class l{listeners={};[m](){this.emit("dispose")}on(t,s){return(this.listeners[t]??=new Set).add(s),this}off(t,s){const e=this.listeners[t];return e&&(e.delete(s),e.size===0&&delete this.listeners[t]),this}once(t,s){const e=n=>{try{s(n)}finally{this.off(t,e)}};return this.on(t,e),this}emit(t,...[s]){const e=this.listeners[t];if(e)for(const n of[...e])n(s);return this}}const f=o=>{throw Error(o)},h=requestAnimationFrame,u=Object,y=(o,t)=>u.assign(o,t),w=document;new Proxy({},{get:(o,t)=>s=>y(w.createElement(t),s)});const b=o=>Array.isArray(o)?o:[o],g=o=>{const t={start:s=>new Promise(e=>{t.stop=()=>(cancelAnimationFrame(r),e(n));const n=[],i=c=>{n.push(c),s?.(n),o(n)?t.stop():r=h(i)};let r=h(i)}),stop(){}};return t},S=o=>o,A=o=>({wrap:t=>(s=>t(o(s))),render:(t,s,e)=>{const n=o(s);e&&a.push(e);const i=t(n);e&&a.pop();let r;const c=b(typeof i!="string"&&"node"in i?(r=i.data,i.node):i);return{props:n,nodes:c,data:r}}}),a=[],E=()=>a[a.length-1]??f("Not found current scene");class P extends l{constructor(t,s){super(),this.options=s;const{root:e,adapter:n,defaultProps:i,timer:r}=s;(this.root=e).tabIndex=-1,e.style.scale="0";const{props:c,nodes:d,data:p}=n.render(t,{...i},this.on("dispose",()=>e.remove()));this.#t=r(),this.#s=c,this.data=p,e.append(...d)}root;#t;#s;data;show=this.#e;async close(){await 0,this.#t.stop()}async#e(t){const{root:s,defaultProps:e}=this.options,n={...e,...t};for(const r of Object.keys({...this.#s,...n}))this.#s[r]=n[r];this.emit("show"),s.style.scale="1",s.focus();const i=await this.#t.start(r=>this.emit("frame",r[r.length-1]));return s.style.scale="0",{...this.emit("close").data?.(),frame_times:i}}}export{l as EventEmitter,P as Scene,A as createComponentAdapter,g as createTimer,S as generic,E as getCurrentScene};
|