@psytask/core 1.1.0 → 1.1.1

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,41 +23,56 @@ 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
55
+ * Create {@link requestAnimationFrame rAF} timer
56
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 |
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, the first frame means the first frame
73
+ * that triggers VSync
74
+ */
75
+ shouldStop: (time: number, records: TimerRecords) => boolean) => Timer;
84
76
  type NodeLike = string | Node;
85
77
  type BuiltinData = {
86
78
  frame_times: TimerRecords;
@@ -205,33 +197,12 @@ declare class Scene<T extends MaybeGenericComponent> extends EventEmitter<SceneE
205
197
  *
206
198
  * @example
207
199
  *
208
- * Integrate with `@vue/reactivity`
200
+ * Basic usage
209
201
  *
210
202
  * ```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`
203
+ * await scene.show({ text: 'new' }); // show with new props
204
+ * await scene.show(); // show with default props
232
205
  * ```
233
- *
234
- * @function
235
206
  */
236
207
  show: T extends Component<infer P, infer D> ? SceneShow<P, D> : T;
237
208
  /**
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- /** @psytask/core v1.1.0 cubxx MIT */
1
+ /** @psytask/core v1.1.1 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,9 +146,7 @@ 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?.(),
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}}}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.1",
4
4
  "type": "module",
5
5
  "author": "cubxx",
6
6
  "license": "MIT",