@psytask/core 1.1.0 → 1.1.2

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/dist/index.d.ts CHANGED
@@ -1,28 +1,5 @@
1
1
  type LooseObject = Record<string, unknown>;
2
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
- }
26
3
 
27
4
  declare const symbol: typeof Symbol.dispose;
28
5
  type EventMap<T extends LooseObject> = T & {
@@ -46,44 +23,65 @@ declare class EventEmitter<T extends LooseObject & {
46
23
  emit<K extends keyof EventMap<T>>(type: K, ...[evt]: EventMap<T>[K] extends undefined ? [evt?: EventMap<T>[K]] : [evt: EventMap<T>[K]]): this;
47
24
  }
48
25
 
26
+ /** Timestamps of elapsed frames */
49
27
  type TimerRecords = number[];
50
28
  type Timer = {
51
- start(onFrame?: (records: TimerRecords) => void): Promise<TimerRecords>;
29
+ /**
30
+ * If called in {@link window.requestAnimationFrame rAF} callback or microtasks
31
+ * generated by rAF callback, the start time (the first element of
32
+ * {@link TimerRecords timer records}) will be the current frame time,
33
+ * otherwise the next frame time.
34
+ *
35
+ * @example
36
+ *
37
+ * Do not repeat
38
+ *
39
+ * ```ts
40
+ * timer.start();
41
+ * timer.start(); // error
42
+ *
43
+ * await timer.start();
44
+ * timer.start(); // right
45
+ *
46
+ * timer.start();
47
+ * timer.stop();
48
+ * timer.start(); // right
49
+ * ```
50
+ */
51
+ start(onFrame?: (time: number) => void): Promise<TimerRecords>;
52
52
  stop(): void;
53
53
  };
54
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
55
+ * Create {@link requestAnimationFrame rAF} timer
63
56
  *
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 |
57
+ * @example Basic usage
72
58
  *
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
59
+ * ```ts
60
+ * const frame_ms = 16.67;
61
+ * const duration = 100;
62
+ * const timer = createTimer(
63
+ * (time, records) => time - records[0] >= duration - frame_ms / 2,
64
+ * );
81
65
  * ```
82
66
  */
83
- declare const createTimer: (shouldStop: (records: TimerRecords) => boolean) => Timer;
67
+ declare const createTimer: (
68
+ /**
69
+ * Return true to stop timer
70
+ *
71
+ * @param time Current frame time
72
+ * @param records History frame times, whose elements represent the start time
73
+ * of each frame that triggers VSync
74
+ */
75
+ shouldStop: (time: number, records: TimerRecords) => boolean) => Timer;
84
76
  type NodeLike = string | Node;
85
77
  type BuiltinData = {
78
+ /**
79
+ * History frame times, whose elements represent the start time of each frame
80
+ * that triggers VSync
81
+ */
86
82
  frame_times: TimerRecords;
83
+ /** Real show duration */
84
+ duration: number;
87
85
  };
88
86
  type ForbiddenData = {
89
87
  [K in keyof BuiltinData]?: never;
@@ -205,33 +203,12 @@ declare class Scene<T extends MaybeGenericComponent> extends EventEmitter<SceneE
205
203
  *
206
204
  * @example
207
205
  *
208
- * Integrate with `@vue/reactivity`
206
+ * Basic usage
209
207
  *
210
208
  * ```ts
211
- * import { reactive, effect } from '@vue/reactivity';
212
- *
213
- * using scene = new Scene(
214
- * (props: { text: string }) => {
215
- * const node = document.createElement('div');
216
- * effect(() => {
217
- * node.textContent = props.text; // auto update when props.text changes
218
- * });
219
- * return node;
220
- * },
221
- * {
222
- * root: document.createElement('div'),
223
- * defaultProps: { text: 'default' },
224
- * adapter: createComponentAdapter(reactive),
225
- * timer: () => createTimer((records) => records.length > 100), // show 100 frames
226
- * },
227
- * );
228
- * document.body.appendChild(scene.root);
229
- *
230
- * await scene.show({ text: 'new' }); // show `new`
231
- * await scene.show(); // show `default`
209
+ * await scene.show({ text: 'new' }); // show with new props
210
+ * await scene.show(); // show with default props
232
211
  * ```
233
- *
234
- * @function
235
212
  */
236
213
  show: T extends Component<infer P, infer D> ? SceneShow<P, D> : T;
237
214
  /**
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- /** @psytask/core v1.1.0 cubxx MIT */
1
+ /** @psytask/core v1.1.2 cubxx MIT */
2
2
  const symbol = Symbol.dispose ?? /* @__PURE__ */ Symbol.for("Symbol.dispose");
3
3
  class EventEmitter {
4
4
  listeners = {};
@@ -54,17 +54,21 @@ new Proxy(
54
54
  );
55
55
  const array_normalize = (e) => Array.isArray(e) ? e : [e];
56
56
 
57
+ let currentFrameTime;
58
+ const setCurrentFrameTime = (time) => (currentFrameTime = time, setTimeout(() => currentFrameTime = 0), // reset after render pipeline
59
+ rAF(setCurrentFrameTime));
57
60
  const createTimer = (shouldStop) => {
61
+ currentFrameTime ?? setCurrentFrameTime(0);
58
62
  const timer = {
59
63
  start: (cb) => new Promise((resolve) => {
60
- timer.stop = () => (cancelAnimationFrame(handle), resolve(records));
61
64
  const records = [];
65
+ const record = (time) => (records.push(time), cb?.(time));
66
+ currentFrameTime && record(currentFrameTime);
62
67
  const frame = (time) => {
63
- records.push(time);
64
- cb?.(records);
65
- shouldStop(records) ? timer.stop() : handle = rAF(frame);
68
+ shouldStop(time, records) ? timer.stop() : (handle = rAF(frame), record(time));
66
69
  };
67
70
  let handle = rAF(frame);
71
+ timer.stop = () => (cancelAnimationFrame(handle), resolve(records));
68
72
  }),
69
73
  stop() {
70
74
  }
@@ -120,33 +124,12 @@ class Scene extends EventEmitter {
120
124
  *
121
125
  * @example
122
126
  *
123
- * Integrate with `@vue/reactivity`
127
+ * Basic usage
124
128
  *
125
129
  * ```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`
130
+ * await scene.show({ text: 'new' }); // show with new props
131
+ * await scene.show(); // show with default props
147
132
  * ```
148
- *
149
- * @function
150
133
  */
151
134
  //@ts-expect-error impl generic component
152
135
  show = this.#show;
@@ -163,13 +146,12 @@ class Scene extends EventEmitter {
163
146
  this.emit("show");
164
147
  root.style.scale = "1";
165
148
  root.focus();
166
- const records = await this.#timer.start(
167
- (records2) => this.emit("frame", records2[records2.length - 1])
168
- );
149
+ const records = await this.#timer.start((time) => this.emit("frame", time));
169
150
  root.style.scale = "0";
170
151
  return {
171
152
  ...this.emit("close").data?.(),
172
- frame_times: records
153
+ frame_times: records,
154
+ duration: currentFrameTime - records[0]
173
155
  };
174
156
  }
175
157
  }
package/dist/index.min.js CHANGED
@@ -1 +1 @@
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};
1
+ const f=Symbol.dispose??Symbol.for("Symbol.dispose");class m{listeners={};[f](){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=r=>{try{s(r)}finally{this.off(t,e)}};return this.on(t,e),this}emit(t,...[s]){const e=this.listeners[t];if(e)for(const r of[...e])r(s);return this}}const y=o=>{throw Error(o)},h=requestAnimationFrame,w=Object,b=(o,t)=>w.assign(o,t),S=document;new Proxy({},{get:(o,t)=>s=>b(S.createElement(t),s)});const g=o=>Array.isArray(o)?o:[o];let d;const p=o=>(d=o,setTimeout(()=>d=0),h(p)),A=o=>{d??p(0);const t={start:s=>new Promise(e=>{const r=[],n=c=>(r.push(c),s?.(c));d&&n(d);const i=c=>{o(c,r)?t.stop():(a=h(i),n(c))};let a=h(i);t.stop=()=>(cancelAnimationFrame(a),e(r))}),stop(){}};return t},E=o=>o,F=o=>({wrap:t=>(s=>t(o(s))),render:(t,s,e)=>{const r=o(s);e&&l.push(e);const n=t(r);e&&l.pop();let i;const a=g(typeof n!="string"&&"node"in n?(i=n.data,n.node):n);return{props:r,nodes:a,data:i}}}),l=[],P=()=>l[l.length-1]??y("Not found current scene");class O extends m{constructor(t,s){super(),this.options=s;const{root:e,adapter:r,defaultProps:n,timer:i}=s;(this.root=e).tabIndex=-1,e.style.scale="0";const{props:a,nodes:c,data:u}=r.render(t,{...n},this.on("dispose",()=>e.remove()));this.#t=i(),this.#s=a,this.data=u,e.append(...c)}root;#t;#s;data;show=this.#e;async close(){await 0,this.#t.stop()}async#e(t){const{root:s,defaultProps:e}=this.options,r={...e,...t};for(const i of Object.keys({...this.#s,...r}))this.#s[i]=r[i];this.emit("show"),s.style.scale="1",s.focus();const n=await this.#t.start(i=>this.emit("frame",i));return s.style.scale="0",{...this.emit("close").data?.(),frame_times:n,duration:d-n[0]}}}export{m as EventEmitter,O as Scene,F as createComponentAdapter,A as createTimer,E as generic,P as getCurrentScene};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@psytask/core",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "type": "module",
5
5
  "author": "cubxx",
6
6
  "license": "MIT",