flexium 0.9.1 → 0.10.4

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.
Files changed (142) hide show
  1. package/README.md +14 -0
  2. package/dist/{DrawText-CJikXQjL.d.cts → DrawText-ccZrs3Xs.d.cts} +1 -1
  3. package/dist/{DrawText-Bvzl40Vi.d.ts → DrawText-ngwNNh8O.d.ts} +1 -1
  4. package/dist/advanced.d.cts +1 -1
  5. package/dist/advanced.d.ts +1 -1
  6. package/dist/advanced.js +1 -1
  7. package/dist/advanced.js.map +1 -1
  8. package/dist/advanced.mjs +1 -1
  9. package/dist/advanced.mjs.map +1 -1
  10. package/dist/canvas.d.cts +4 -4
  11. package/dist/canvas.d.ts +4 -4
  12. package/dist/canvas.js +1 -1
  13. package/dist/canvas.mjs +1 -1
  14. package/dist/chunk-3AWR7IKE.js +2 -0
  15. package/dist/chunk-3AWR7IKE.js.map +1 -0
  16. package/dist/chunk-3BQXIHYI.mjs +3 -0
  17. package/dist/chunk-3BQXIHYI.mjs.map +1 -0
  18. package/dist/chunk-5236IK5I.js +2 -0
  19. package/dist/chunk-5236IK5I.js.map +1 -0
  20. package/dist/{chunk-JHJHIMWD.js → chunk-63AW5ZOC.js} +2 -2
  21. package/dist/{chunk-JHJHIMWD.js.map → chunk-63AW5ZOC.js.map} +1 -1
  22. package/dist/chunk-6K44QCMT.mjs +3 -0
  23. package/dist/chunk-6K44QCMT.mjs.map +1 -0
  24. package/dist/chunk-7Q4UE442.mjs +2 -0
  25. package/dist/chunk-7Q4UE442.mjs.map +1 -0
  26. package/dist/chunk-AJT35P3Z.js +3 -0
  27. package/dist/chunk-AJT35P3Z.js.map +1 -0
  28. package/dist/chunk-AYQMU7XC.js +3 -0
  29. package/dist/chunk-AYQMU7XC.js.map +1 -0
  30. package/dist/chunk-B7VP6HBY.mjs +2 -0
  31. package/dist/chunk-B7VP6HBY.mjs.map +1 -0
  32. package/dist/{chunk-R5CS7UZG.mjs → chunk-BYHIHYRR.mjs} +2 -2
  33. package/dist/{chunk-R5CS7UZG.mjs.map → chunk-BYHIHYRR.mjs.map} +1 -1
  34. package/dist/chunk-HLPVL6EK.mjs +2 -0
  35. package/dist/{chunk-RUXAK74B.mjs.map → chunk-HLPVL6EK.mjs.map} +1 -1
  36. package/dist/{chunk-HDCPA76O.mjs → chunk-KJPIJNFH.mjs} +2 -2
  37. package/dist/chunk-KJPIJNFH.mjs.map +1 -0
  38. package/dist/chunk-PVPY55Z7.mjs +2 -0
  39. package/dist/{chunk-TRIEKNVZ.mjs.map → chunk-PVPY55Z7.mjs.map} +1 -1
  40. package/dist/chunk-Q7WT5IIF.mjs +3 -0
  41. package/dist/chunk-Q7WT5IIF.mjs.map +1 -0
  42. package/dist/chunk-S3EDPCYT.js +3 -0
  43. package/dist/chunk-S3EDPCYT.js.map +1 -0
  44. package/dist/{chunk-L4C5UBOX.js → chunk-WOHSSPKD.js} +2 -2
  45. package/dist/chunk-WOHSSPKD.js.map +1 -0
  46. package/dist/{chunk-DFG62GKW.js → chunk-WXEHDEIH.js} +2 -2
  47. package/dist/{chunk-DFG62GKW.js.map → chunk-WXEHDEIH.js.map} +1 -1
  48. package/dist/{chunk-3P6DMEGB.js → chunk-YDZ37ZZ4.js} +2 -2
  49. package/dist/{chunk-3P6DMEGB.js.map → chunk-YDZ37ZZ4.js.map} +1 -1
  50. package/dist/{components-D4WeooPi.d.ts → components-B7KQ8C-i.d.ts} +2 -2
  51. package/dist/{components-DZy2r6m5.d.cts → components-CxnAnbpI.d.cts} +2 -2
  52. package/dist/core.d.cts +48 -136
  53. package/dist/core.d.ts +48 -136
  54. package/dist/core.js +1 -1
  55. package/dist/core.mjs +1 -1
  56. package/dist/dom.d.cts +2 -2
  57. package/dist/dom.d.ts +2 -2
  58. package/dist/dom.js +1 -1
  59. package/dist/dom.js.map +1 -1
  60. package/dist/dom.mjs +1 -1
  61. package/dist/dom.mjs.map +1 -1
  62. package/dist/{effect-BlnnM1t5.d.cts → effect-14CxUU8r.d.cts} +8 -4
  63. package/dist/{effect-BlnnM1t5.d.ts → effect-14CxUU8r.d.ts} +8 -4
  64. package/dist/effect-3LUCHSAZ.mjs +2 -0
  65. package/dist/effect-3LUCHSAZ.mjs.map +1 -0
  66. package/dist/effect-K45UU3N4.js +2 -0
  67. package/dist/effect-K45UU3N4.js.map +1 -0
  68. package/dist/index.d.cts +1 -1
  69. package/dist/index.d.ts +1 -1
  70. package/dist/index.js +1 -1
  71. package/dist/index.js.map +1 -1
  72. package/dist/index.mjs +1 -1
  73. package/dist/index.mjs.map +1 -1
  74. package/dist/interactive.d.cts +15 -10
  75. package/dist/interactive.d.ts +15 -10
  76. package/dist/interactive.js +1 -1
  77. package/dist/interactive.js.map +1 -1
  78. package/dist/interactive.mjs +1 -1
  79. package/dist/interactive.mjs.map +1 -1
  80. package/dist/metafile-cjs.json +1 -1
  81. package/dist/metafile-esm.json +1 -1
  82. package/dist/{portal-C3ESJhlv.d.ts → portal-CVqrpmHd.d.ts} +2 -2
  83. package/dist/{portal-CAEbiMUZ.d.cts → portal-NLlE-fNZ.d.cts} +2 -2
  84. package/dist/primitives/layout.js +1 -1
  85. package/dist/primitives/layout.mjs +1 -1
  86. package/dist/primitives/motion.js +1 -1
  87. package/dist/primitives/motion.mjs +1 -1
  88. package/dist/primitives/ui.d.cts +3 -3
  89. package/dist/primitives/ui.d.ts +3 -3
  90. package/dist/primitives/ui.js +1 -1
  91. package/dist/primitives/ui.js.map +1 -1
  92. package/dist/primitives/ui.mjs +1 -1
  93. package/dist/primitives/ui.mjs.map +1 -1
  94. package/dist/primitives.d.cts +4 -4
  95. package/dist/primitives.d.ts +4 -4
  96. package/dist/primitives.js +1 -1
  97. package/dist/primitives.js.map +1 -1
  98. package/dist/primitives.mjs +1 -1
  99. package/dist/primitives.mjs.map +1 -1
  100. package/dist/router.d.cts +3 -3
  101. package/dist/router.d.ts +3 -3
  102. package/dist/router.js +1 -1
  103. package/dist/router.mjs +1 -1
  104. package/dist/server.js +1 -1
  105. package/dist/server.js.map +1 -1
  106. package/dist/server.mjs +1 -1
  107. package/dist/server.mjs.map +1 -1
  108. package/dist/signal-2QUI7H7B.js +2 -0
  109. package/dist/{signal-3YZHUCLL.js.map → signal-2QUI7H7B.js.map} +1 -1
  110. package/dist/signal-C6936A3J.d.cts +175 -0
  111. package/dist/signal-C6936A3J.d.ts +175 -0
  112. package/dist/signal-L3ZWGOVT.mjs +2 -0
  113. package/dist/{signal-F2HEYB6F.mjs.map → signal-L3ZWGOVT.mjs.map} +1 -1
  114. package/dist/test-exports.d.cts +6 -6
  115. package/dist/test-exports.d.ts +6 -6
  116. package/dist/test-exports.js +1 -1
  117. package/dist/test-exports.mjs +1 -1
  118. package/package.json +2 -1
  119. package/dist/chunk-2ZHUQBNI.mjs +0 -2
  120. package/dist/chunk-2ZHUQBNI.mjs.map +0 -1
  121. package/dist/chunk-HDCPA76O.mjs.map +0 -1
  122. package/dist/chunk-J4CK5NRW.mjs +0 -3
  123. package/dist/chunk-J4CK5NRW.mjs.map +0 -1
  124. package/dist/chunk-JEDCNAAI.mjs +0 -3
  125. package/dist/chunk-JEDCNAAI.mjs.map +0 -1
  126. package/dist/chunk-L4C5UBOX.js.map +0 -1
  127. package/dist/chunk-M4ANLZ6P.js +0 -3
  128. package/dist/chunk-M4ANLZ6P.js.map +0 -1
  129. package/dist/chunk-RDA77IE6.js +0 -2
  130. package/dist/chunk-RDA77IE6.js.map +0 -1
  131. package/dist/chunk-RUXAK74B.mjs +0 -2
  132. package/dist/chunk-TRIEKNVZ.mjs +0 -2
  133. package/dist/chunk-VIVO4FHN.js +0 -3
  134. package/dist/chunk-VIVO4FHN.js.map +0 -1
  135. package/dist/chunk-XLE6SMWX.mjs +0 -3
  136. package/dist/chunk-XLE6SMWX.mjs.map +0 -1
  137. package/dist/chunk-YGMMJWAA.js +0 -3
  138. package/dist/chunk-YGMMJWAA.js.map +0 -1
  139. package/dist/signal-3YZHUCLL.js +0 -2
  140. package/dist/signal-Dxh9PsKr.d.cts +0 -69
  141. package/dist/signal-Dxh9PsKr.d.ts +0 -69
  142. package/dist/signal-F2HEYB6F.mjs +0 -2
@@ -1,4 +1,4 @@
1
- import { S as Signal } from './signal-Dxh9PsKr.js';
1
+ import { S as SignalNode } from './signal-C6936A3J.js';
2
2
 
3
3
  /**
4
4
  * Loop - Core animation/game loop implementation with delta time and fixed timestep
@@ -38,17 +38,22 @@ declare function createLoop(options?: LoopOptions): Loop;
38
38
  interface KeyboardState {
39
39
  /** Check if a key is currently pressed */
40
40
  isPressed(key: string): boolean;
41
- /** Check if a key was just pressed this frame */
41
+ /** Check if a key was pressed in the current frame */
42
42
  isJustPressed(key: string): boolean;
43
- /** Check if a key was just released this frame */
43
+ /** Check if a key was released in the current frame */
44
44
  isJustReleased(key: string): boolean;
45
45
  /** Get all currently pressed keys */
46
+ getPressed(): string[];
47
+ /** Get all currently pressed keys (alias) */
46
48
  getPressedKeys(): string[];
47
- /** Signal that updates when any key state changes */
48
- readonly keys: Signal<Set<string>>;
49
- /** Clear just pressed/released state (call at end of frame) */
49
+ /**
50
+ * Reactive set of all currently pressed keys
51
+ * @deprecated Use isPressed() or getPressed() instead for better performance
52
+ */
53
+ keys: SignalNode<Set<string>>;
54
+ /** Clear all state at end of frame */
50
55
  clearFrameState(): void;
51
- /** Cleanup event listeners */
56
+ /** Remove event listeners */
52
57
  dispose(): void;
53
58
  }
54
59
  /**
@@ -111,9 +116,9 @@ interface Vec2 {
111
116
  }
112
117
  interface MouseState {
113
118
  /** Current mouse position relative to target */
114
- readonly position: Signal<Vec2>;
119
+ readonly position: SignalNode<Vec2>;
115
120
  /** Mouse position delta since last frame */
116
- readonly delta: Signal<Vec2>;
121
+ readonly delta: SignalNode<Vec2>;
117
122
  /** Check if a mouse button is pressed (0=left, 1=middle, 2=right) */
118
123
  isPressed(button: number): boolean;
119
124
  /** Check if left mouse button is pressed */
@@ -123,7 +128,7 @@ interface MouseState {
123
128
  /** Check if middle mouse button is pressed */
124
129
  isMiddlePressed(): boolean;
125
130
  /** Wheel delta (positive = scroll down) */
126
- readonly wheelDelta: Signal<number>;
131
+ readonly wheelDelta: SignalNode<number>;
127
132
  /** Clear frame state (call at end of frame) */
128
133
  clearFrameState(): void;
129
134
  /** Cleanup event listeners */
@@ -1,2 +1,2 @@
1
- 'use strict';var chunkYGMMJWAA_js=require('./chunk-YGMMJWAA.js');function M(d={}){let{fixedFps:n=60,onUpdate:s,onFixedUpdate:v,onRender:l}=d,c=1/n,e=false,f,t=0,p=0,i=0,r=0,E=0,w=true;function L(y){if(!e)return;f=requestAnimationFrame(L);let h=y/1e3;if(w){t=h,w=false;return}let b=Math.min(h-t,.25);if(t=h,r++,E+=b,E>=1&&(i=r,r=0,E-=1),s&&s(b),v)for(p+=b;p>=c;)v(c),p-=c;if(l){let x=v?p/c:1;l(x);}}return {start(){e||(e=true,w=true,p=0,f=requestAnimationFrame(L));},stop(){e=false,f!==void 0&&(cancelAnimationFrame(f),f=void 0);},isRunning(){return e},getFps(){return i}}}function D(d=window){let n=chunkYGMMJWAA_js.j(new Set),s=new Set,v=new Set;function l(t){return t.toLowerCase()}function c(t){let i=l(t.code);if(!n.value.has(i)){s.add(i);let r=new Set(n.value);r.add(i),n.value=r;}}function e(t){let i=l(t.code);if(n.value.has(i)){v.add(i);let r=new Set(n.value);r.delete(i),n.value=r;}}function f(){n.value=new Set,s.clear(),v.clear();}return d.addEventListener("keydown",c),d.addEventListener("keyup",e),d===window&&d.addEventListener("blur",f),{isPressed(t){return n.value.has(l(t))},isJustPressed(t){return s.has(l(t))},isJustReleased(t){return v.has(l(t))},getPressedKeys(){return Array.from(n.value)},get keys(){return n},clearFrameState(){s.clear(),v.clear();},dispose(){d.removeEventListener("keydown",c),d.removeEventListener("keyup",e),d===window&&d.removeEventListener("blur",f);}}}var k={ArrowUp:"arrowup",ArrowDown:"arrowdown",ArrowLeft:"arrowleft",ArrowRight:"arrowright",KeyW:"keyw",KeyA:"keya",KeyS:"keys",KeyD:"keyd",Space:"space",Enter:"enter",Escape:"escape",ShiftLeft:"shiftleft",ShiftRight:"shiftright",ControlLeft:"controlleft",ControlRight:"controlright",AltLeft:"altleft",AltRight:"altright",Tab:"tab",Digit0:"digit0",Digit1:"digit1",Digit2:"digit2",Digit3:"digit3",Digit4:"digit4",Digit5:"digit5",Digit6:"digit6",Digit7:"digit7",Digit8:"digit8",Digit9:"digit9"};function K(d={}){let{target:n=window,canvas:s}=d,v=chunkYGMMJWAA_js.j({x:0,y:0}),l=chunkYGMMJWAA_js.j({x:0,y:0}),c=chunkYGMMJWAA_js.j(0),e=chunkYGMMJWAA_js.j(new Set),f=0,t=0,p=0,i=0,r=0;function E(o){if(s){let m=s.getBoundingClientRect(),a=s.width/m.width,S=s.height/m.height;return {x:(o.clientX-m.left)*a,y:(o.clientY-m.top)*S}}return {x:o.clientX,y:o.clientY}}function w(o){let a=E(o);p+=a.x-f,i+=a.y-t,f=a.x,t=a.y,v.value=a,l.value={x:p,y:i};}function L(o){let m=o,a=new Set(e.value);a.add(m.button),e.value=a;}function y(o){let m=o,a=new Set(e.value);a.delete(m.button),e.value=a;}function h(o){r+=Math.sign(o.deltaY),c.value=r;}function b(){e.value=new Set;}function x(o){}let u=s||n;return u.addEventListener("mousemove",w),u.addEventListener("mousedown",L),u.addEventListener("mouseup",y),u.addEventListener("wheel",h,{passive:true}),u.addEventListener("mouseleave",b),u.addEventListener("contextmenu",x),n!==window&&window.addEventListener("mouseup",y),{get position(){return v},get delta(){return l},get wheelDelta(){return c},isPressed(o){return e.value.has(o)},isLeftPressed(){return e.value.has(0)},isRightPressed(){return e.value.has(2)},isMiddlePressed(){return e.value.has(1)},clearFrameState(){p=0,i=0,r=0,l.value={x:0,y:0},c.value=0;},dispose(){u.removeEventListener("mousemove",w),u.removeEventListener("mousedown",L),u.removeEventListener("mouseup",y),u.removeEventListener("wheel",h),u.removeEventListener("mouseleave",b),u.removeEventListener("contextmenu",x),n!==window&&window.removeEventListener("mouseup",y);}}}var F={Left:0,Middle:1,Right:2};exports.Keys=k;exports.MouseButton=F;exports.createLoop=M;exports.keyboard=D;exports.mouse=K;//# sourceMappingURL=interactive.js.map
1
+ 'use strict';var chunk5236IK5I_js=require('./chunk-5236IK5I.js');require('./chunk-AJT35P3Z.js');function M(v={}){let{fixedFps:a=60,onUpdate:d,onFixedUpdate:c,onRender:f}=v,s=1/a,n=false,g,m=0,l=0,e=0,t=0,r=0,w=true;function h(b){if(!n)return;g=requestAnimationFrame(h);let L=b/1e3;if(w){m=L,w=false;return}let E=Math.min(L-m,.25);if(m=L,t++,r+=E,r>=1&&(e=t,t=0,r-=1),d&&d(E),c)for(l+=E;l>=s;)c(s),l-=s;if(f){let x=c?l/s:1;f(x);}}return {start(){n||(n=true,w=true,l=0,g=requestAnimationFrame(h));},stop(){n=false,g!==void 0&&(cancelAnimationFrame(g),g=void 0);},isRunning(){return n},getFps(){return e}}}function D(v=window){let a=new Map,d=new chunk5236IK5I_js.d(false),c=new Set,f=new Set;function s(e){return e.toLowerCase()}function n(e){let t=a.get(e);return t||(t=new chunk5236IK5I_js.d(false),a.set(e,t)),t}function g(e){let t=s(e.code),r=n(t);r.get()||(r.set(true),c.add(t),d.set(true));}function m(e){let t=s(e.code),r=n(t);if(r.get()){r.set(false),f.add(t);let w=false;for(let h of a.values())if(h.get()){w=true;break}d.set(w);}}function l(){for(let e of a.values())e.set(false);d.set(false),c.clear(),f.clear();}return v.addEventListener("keydown",g),v.addEventListener("keyup",m),window.addEventListener("blur",l),{isPressed(e){let t=a.get(s(e));return t?t.get():false},isJustPressed(e){return c.has(s(e))},isJustReleased(e){return f.has(s(e))},getPressedKeys(){let e=[];for(let[t,r]of a.entries())r.get()&&e.push(t);return e},getPressed(){return this.getPressedKeys()},get keys(){return new chunk5236IK5I_js.d(new Set)},clearFrameState(){c.clear(),f.clear();},dispose(){v.removeEventListener("keydown",g),v.removeEventListener("keyup",m),v===window&&v.removeEventListener("blur",l);}}}var k={ArrowUp:"arrowup",ArrowDown:"arrowdown",ArrowLeft:"arrowleft",ArrowRight:"arrowright",KeyW:"keyw",KeyA:"keya",KeyS:"keys",KeyD:"keyd",Space:"space",Enter:"enter",Escape:"escape",ShiftLeft:"shiftleft",ShiftRight:"shiftright",ControlLeft:"controlleft",ControlRight:"controlright",AltLeft:"altleft",AltRight:"altright",Tab:"tab",Digit0:"digit0",Digit1:"digit1",Digit2:"digit2",Digit3:"digit3",Digit4:"digit4",Digit5:"digit5",Digit6:"digit6",Digit7:"digit7",Digit8:"digit8",Digit9:"digit9"};function K(v={}){let{target:a=window,canvas:d}=v,c=new chunk5236IK5I_js.d({x:0,y:0}),f=new chunk5236IK5I_js.d({x:0,y:0}),s=new chunk5236IK5I_js.d(0),n=new chunk5236IK5I_js.d(new Set),g=0,m=0,l=0,e=0,t=0;function r(o){if(d){let p=d.getBoundingClientRect(),i=d.width/p.width,S=d.height/p.height;return {x:(o.clientX-p.left)*i,y:(o.clientY-p.top)*S}}return {x:o.clientX,y:o.clientY}}function w(o){let i=r(o);l+=i.x-g,e+=i.y-m,g=i.x,m=i.y,c.set(i),f.set({x:l,y:e});}function h(o){let p=o,i=new Set(n.get());i.add(p.button),n.set(i);}function b(o){let p=o,i=new Set(n.get());i.delete(p.button),n.set(i);}function L(o){t+=Math.sign(o.deltaY),s.set(t);}function E(){n.set(new Set);}function x(o){}let u=d||a;return u.addEventListener("mousemove",w),u.addEventListener("mousedown",h),u.addEventListener("mouseup",b),u.addEventListener("wheel",L),u.addEventListener("mouseleave",E),u.addEventListener("contextmenu",x),a!==window&&window.addEventListener("mouseup",b),{get position(){return c},get delta(){return f},get wheelDelta(){return s},isPressed(o){return n.value.has(o)},isLeftPressed(){return n.value.has(0)},isRightPressed(){return n.value.has(2)},isMiddlePressed(){return n.value.has(1)},clearFrameState(){l=0,e=0,t=0,f.value={x:0,y:0},s.value=0;},dispose(){u.removeEventListener("mousemove",w),u.removeEventListener("mousedown",h),u.removeEventListener("mouseup",b),u.removeEventListener("wheel",L),u.removeEventListener("mouseleave",E),u.removeEventListener("contextmenu",x),a!==window&&window.removeEventListener("mouseup",b);}}}var P={Left:0,Middle:1,Right:2};exports.Keys=k;exports.MouseButton=P;exports.createLoop=M;exports.keyboard=D;exports.mouse=K;//# sourceMappingURL=interactive.js.map
2
2
  //# sourceMappingURL=interactive.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/interactive/loop.ts","../src/interactive/keyboard.ts","../src/interactive/mouse.ts"],"names":["createLoop","options","fixedFps","onUpdate","onFixedUpdate","onRender","fixedDelta","running","rafId","lastTime","accumulator","fps","frameCount","fpsTime","isFirstFrame","loop","currentTime","time","delta","alpha","keyboard","target","keys","signal","justPressed","justReleased","normalizeKey","key","handleKeyDown","e","newKeys","handleKeyUp","handleBlur","Keys","mouse","canvas","position","wheelDelta","buttons","lastX","lastY","frameDeltaX","frameDeltaY","frameWheelDelta","getCanvasCoordinates","event","rect","scaleX","scaleY","handleMouseMove","coords","handleMouseDown","newButtons","handleMouseUp","handleWheel","handleMouseLeave","handleContextMenu","_e","eventTarget","button","MouseButton"],"mappings":"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","file":"interactive.js","sourcesContent":["/**\n * Loop - Core animation/game loop implementation with delta time and fixed timestep\n */\n\nexport interface LoopOptions {\n /** Target FPS for fixed update (default: 60) */\n fixedFps?: number\n /** Called every frame with delta time in seconds */\n onUpdate?: (delta: number) => void\n /** Called at fixed intervals (for physics) */\n onFixedUpdate?: (fixedDelta: number) => void\n /** Called every frame for rendering */\n onRender?: (alpha: number) => void\n}\n\nexport interface Loop {\n /** Start the loop */\n start(): void\n /** Stop the loop */\n stop(): void\n /** Check if loop is running */\n isRunning(): boolean\n /** Get current FPS */\n getFps(): number\n}\n\n/**\n * Create an animation loop with delta time and optional fixed timestep\n */\nexport function createLoop(options: LoopOptions = {}): Loop {\n const { fixedFps = 60, onUpdate, onFixedUpdate, onRender } = options\n\n const fixedDelta = 1 / fixedFps\n let running = false\n let rafId: number | undefined\n let lastTime = 0\n let accumulator = 0\n let fps = 0\n let frameCount = 0\n let fpsTime = 0\n let isFirstFrame = true\n\n function loop(currentTime: number): void {\n if (!running) return\n\n rafId = requestAnimationFrame(loop)\n\n // Convert to seconds\n const time = currentTime / 1000\n\n // Initialize lastTime on first frame and skip processing\n if (isFirstFrame) {\n lastTime = time\n isFirstFrame = false\n return\n }\n\n const delta = Math.min(time - lastTime, 0.25) // Cap at 250ms\n lastTime = time\n\n // FPS counter\n frameCount++\n fpsTime += delta\n if (fpsTime >= 1) {\n fps = frameCount\n frameCount = 0\n fpsTime -= 1\n }\n\n // Variable update\n if (onUpdate) {\n onUpdate(delta)\n }\n\n // Fixed update (for physics)\n if (onFixedUpdate) {\n accumulator += delta\n\n while (accumulator >= fixedDelta) {\n onFixedUpdate(fixedDelta)\n accumulator -= fixedDelta\n }\n }\n\n // Render with interpolation alpha\n if (onRender) {\n const alpha = onFixedUpdate ? accumulator / fixedDelta : 1\n onRender(alpha)\n }\n }\n\n return {\n start() {\n if (running) return\n running = true\n isFirstFrame = true\n accumulator = 0\n rafId = requestAnimationFrame(loop)\n },\n\n stop() {\n running = false\n if (rafId !== undefined) {\n cancelAnimationFrame(rafId)\n rafId = undefined\n }\n },\n\n isRunning() {\n return running\n },\n\n getFps() {\n return fps\n },\n }\n}\n","/**\n * keyboard - Reactive keyboard input handler\n *\n * Creates a keyboard input handler with reactive signals.\n * Philosophy: No hooks, just factory functions that return signal-based state.\n */\n\nimport { signal, type Signal } from '../core/signal'\n\nexport interface KeyboardState {\n /** Check if a key is currently pressed */\n isPressed(key: string): boolean\n /** Check if a key was just pressed this frame */\n isJustPressed(key: string): boolean\n /** Check if a key was just released this frame */\n isJustReleased(key: string): boolean\n /** Get all currently pressed keys */\n getPressedKeys(): string[]\n /** Signal that updates when any key state changes */\n readonly keys: Signal<Set<string>>\n /** Clear just pressed/released state (call at end of frame) */\n clearFrameState(): void\n /** Cleanup event listeners */\n dispose(): void\n}\n\n/**\n * Create a keyboard input handler with reactive state\n *\n * @example\n * ```tsx\n * const kb = keyboard()\n *\n * effect(() => {\n * if (kb.isPressed(Keys.ArrowUp)) {\n * player.y -= speed\n * }\n * })\n * ```\n */\nexport function keyboard(target: EventTarget = window): KeyboardState {\n const keys = signal<Set<string>>(new Set())\n const justPressed = new Set<string>()\n const justReleased = new Set<string>()\n\n function normalizeKey(key: string): string {\n return key.toLowerCase()\n }\n\n function handleKeyDown(e: Event): void {\n const event = e as KeyboardEvent\n const key = normalizeKey(event.code)\n\n if (!keys.value.has(key)) {\n justPressed.add(key)\n const newKeys = new Set(keys.value)\n newKeys.add(key)\n keys.value = newKeys\n }\n }\n\n function handleKeyUp(e: Event): void {\n const event = e as KeyboardEvent\n const key = normalizeKey(event.code)\n\n if (keys.value.has(key)) {\n justReleased.add(key)\n const newKeys = new Set(keys.value)\n newKeys.delete(key)\n keys.value = newKeys\n }\n }\n\n function handleBlur(): void {\n // Clear all keys when window loses focus\n keys.value = new Set()\n justPressed.clear()\n justReleased.clear()\n }\n\n // Add event listeners\n target.addEventListener('keydown', handleKeyDown)\n target.addEventListener('keyup', handleKeyUp)\n if (target === window) {\n target.addEventListener('blur', handleBlur)\n }\n\n return {\n isPressed(key: string): boolean {\n return keys.value.has(normalizeKey(key))\n },\n\n isJustPressed(key: string): boolean {\n return justPressed.has(normalizeKey(key))\n },\n\n isJustReleased(key: string): boolean {\n return justReleased.has(normalizeKey(key))\n },\n\n getPressedKeys(): string[] {\n return Array.from(keys.value)\n },\n\n get keys() {\n return keys\n },\n\n clearFrameState(): void {\n justPressed.clear()\n justReleased.clear()\n },\n\n dispose(): void {\n target.removeEventListener('keydown', handleKeyDown)\n target.removeEventListener('keyup', handleKeyUp)\n if (target === window) {\n target.removeEventListener('blur', handleBlur)\n }\n },\n }\n}\n\n/** Common key codes for convenience */\nexport const Keys = {\n // Arrow keys\n ArrowUp: 'arrowup',\n ArrowDown: 'arrowdown',\n ArrowLeft: 'arrowleft',\n ArrowRight: 'arrowright',\n\n // WASD\n KeyW: 'keyw',\n KeyA: 'keya',\n KeyS: 'keys',\n KeyD: 'keyd',\n\n // Common keys\n Space: 'space',\n Enter: 'enter',\n Escape: 'escape',\n ShiftLeft: 'shiftleft',\n ShiftRight: 'shiftright',\n ControlLeft: 'controlleft',\n ControlRight: 'controlright',\n AltLeft: 'altleft',\n AltRight: 'altright',\n Tab: 'tab',\n\n // Numbers\n Digit0: 'digit0',\n Digit1: 'digit1',\n Digit2: 'digit2',\n Digit3: 'digit3',\n Digit4: 'digit4',\n Digit5: 'digit5',\n Digit6: 'digit6',\n Digit7: 'digit7',\n Digit8: 'digit8',\n Digit9: 'digit9',\n} as const\n","/**\n * mouse - Reactive mouse input handler\n *\n * Creates a mouse input handler with reactive signals.\n * Philosophy: No hooks, just factory functions that return signal-based state.\n */\n\nimport { signal, type Signal } from '../core/signal'\n\nexport interface Vec2 {\n x: number\n y: number\n}\n\nexport interface MouseState {\n /** Current mouse position relative to target */\n readonly position: Signal<Vec2>\n /** Mouse position delta since last frame */\n readonly delta: Signal<Vec2>\n /** Check if a mouse button is pressed (0=left, 1=middle, 2=right) */\n isPressed(button: number): boolean\n /** Check if left mouse button is pressed */\n isLeftPressed(): boolean\n /** Check if right mouse button is pressed */\n isRightPressed(): boolean\n /** Check if middle mouse button is pressed */\n isMiddlePressed(): boolean\n /** Wheel delta (positive = scroll down) */\n readonly wheelDelta: Signal<number>\n /** Clear frame state (call at end of frame) */\n clearFrameState(): void\n /** Cleanup event listeners */\n dispose(): void\n}\n\nexport interface MouseOptions {\n /** Element to track mouse relative to (default: window) */\n target?: EventTarget\n /** Canvas element for coordinate calculation (if different from target) */\n canvas?: HTMLCanvasElement\n}\n\n/**\n * Create a mouse input handler with reactive state\n *\n * @example\n * ```tsx\n * const m = mouse()\n *\n * effect(() => {\n * console.log('Mouse at:', m.position.value)\n * if (m.isLeftPressed()) {\n * draw(m.position.value)\n * }\n * })\n * ```\n */\nexport function mouse(options: MouseOptions = {}): MouseState {\n const { target = window, canvas } = options\n\n const position = signal<Vec2>({ x: 0, y: 0 })\n const delta = signal<Vec2>({ x: 0, y: 0 })\n const wheelDelta = signal<number>(0)\n const buttons = signal<Set<number>>(new Set())\n\n let lastX = 0\n let lastY = 0\n let frameDeltaX = 0\n let frameDeltaY = 0\n let frameWheelDelta = 0\n\n function getCanvasCoordinates(event: MouseEvent): Vec2 {\n if (canvas) {\n const rect = canvas.getBoundingClientRect()\n const scaleX = canvas.width / rect.width\n const scaleY = canvas.height / rect.height\n return {\n x: (event.clientX - rect.left) * scaleX,\n y: (event.clientY - rect.top) * scaleY,\n }\n }\n return {\n x: event.clientX,\n y: event.clientY,\n }\n }\n\n function handleMouseMove(e: Event): void {\n const event = e as MouseEvent\n const coords = getCanvasCoordinates(event)\n\n frameDeltaX += coords.x - lastX\n frameDeltaY += coords.y - lastY\n lastX = coords.x\n lastY = coords.y\n\n position.value = coords\n delta.value = { x: frameDeltaX, y: frameDeltaY }\n }\n\n function handleMouseDown(e: Event): void {\n const event = e as MouseEvent\n const newButtons = new Set(buttons.value)\n newButtons.add(event.button)\n buttons.value = newButtons\n }\n\n function handleMouseUp(e: Event): void {\n const event = e as MouseEvent\n const newButtons = new Set(buttons.value)\n newButtons.delete(event.button)\n buttons.value = newButtons\n }\n\n function handleWheel(e: Event): void {\n const event = e as WheelEvent\n frameWheelDelta += Math.sign(event.deltaY)\n wheelDelta.value = frameWheelDelta\n }\n\n function handleMouseLeave(): void {\n buttons.value = new Set()\n }\n\n function handleContextMenu(_e: Event): void {\n // Prevent context menu in game contexts if needed\n // e.preventDefault()\n }\n\n // Add event listeners\n const eventTarget = canvas || target\n eventTarget.addEventListener('mousemove', handleMouseMove)\n eventTarget.addEventListener('mousedown', handleMouseDown)\n eventTarget.addEventListener('mouseup', handleMouseUp)\n eventTarget.addEventListener('wheel', handleWheel, { passive: true })\n eventTarget.addEventListener('mouseleave', handleMouseLeave)\n eventTarget.addEventListener('contextmenu', handleContextMenu)\n\n // Handle mouse up outside target\n if (target !== window) {\n window.addEventListener('mouseup', handleMouseUp)\n }\n\n return {\n get position() {\n return position\n },\n\n get delta() {\n return delta\n },\n\n get wheelDelta() {\n return wheelDelta\n },\n\n isPressed(button: number): boolean {\n return buttons.value.has(button)\n },\n\n isLeftPressed(): boolean {\n return buttons.value.has(0)\n },\n\n isRightPressed(): boolean {\n return buttons.value.has(2)\n },\n\n isMiddlePressed(): boolean {\n return buttons.value.has(1)\n },\n\n clearFrameState(): void {\n frameDeltaX = 0\n frameDeltaY = 0\n frameWheelDelta = 0\n delta.value = { x: 0, y: 0 }\n wheelDelta.value = 0\n },\n\n dispose(): void {\n eventTarget.removeEventListener('mousemove', handleMouseMove)\n eventTarget.removeEventListener('mousedown', handleMouseDown)\n eventTarget.removeEventListener('mouseup', handleMouseUp)\n eventTarget.removeEventListener('wheel', handleWheel)\n eventTarget.removeEventListener('mouseleave', handleMouseLeave)\n eventTarget.removeEventListener('contextmenu', handleContextMenu)\n\n if (target !== window) {\n window.removeEventListener('mouseup', handleMouseUp)\n }\n },\n }\n}\n\n/** Mouse button constants */\nexport const MouseButton = {\n Left: 0,\n Middle: 1,\n Right: 2,\n} as const\n"]}
1
+ {"version":3,"sources":["../src/interactive/loop.ts","../src/interactive/keyboard.ts","../src/interactive/mouse.ts"],"names":["createLoop","options","fixedFps","onUpdate","onFixedUpdate","onRender","fixedDelta","running","rafId","lastTime","accumulator","fps","frameCount","fpsTime","isFirstFrame","loop","currentTime","time","delta","alpha","keyboard","target","keys","anyKeyPressed","SignalNode","justPressed","justReleased","normalizeKey","key","getKeySignal","code","s","handleKeyDown","handleKeyUp","any","sig","handleBlur","pressed","signal","Keys","mouse","canvas","position","wheelDelta","buttons","lastX","lastY","frameDeltaX","frameDeltaY","frameWheelDelta","getCanvasCoordinates","event","rect","scaleX","scaleY","handleMouseMove","e","coords","handleMouseDown","newButtons","handleMouseUp","handleWheel","handleMouseLeave","handleContextMenu","_e","eventTarget","button","MouseButton"],"mappings":"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","file":"interactive.js","sourcesContent":["/**\n * Loop - Core animation/game loop implementation with delta time and fixed timestep\n */\n\nexport interface LoopOptions {\n /** Target FPS for fixed update (default: 60) */\n fixedFps?: number\n /** Called every frame with delta time in seconds */\n onUpdate?: (delta: number) => void\n /** Called at fixed intervals (for physics) */\n onFixedUpdate?: (fixedDelta: number) => void\n /** Called every frame for rendering */\n onRender?: (alpha: number) => void\n}\n\nexport interface Loop {\n /** Start the loop */\n start(): void\n /** Stop the loop */\n stop(): void\n /** Check if loop is running */\n isRunning(): boolean\n /** Get current FPS */\n getFps(): number\n}\n\n/**\n * Create an animation loop with delta time and optional fixed timestep\n */\nexport function createLoop(options: LoopOptions = {}): Loop {\n const { fixedFps = 60, onUpdate, onFixedUpdate, onRender } = options\n\n const fixedDelta = 1 / fixedFps\n let running = false\n let rafId: number | undefined\n let lastTime = 0\n let accumulator = 0\n let fps = 0\n let frameCount = 0\n let fpsTime = 0\n let isFirstFrame = true\n\n function loop(currentTime: number): void {\n if (!running) return\n\n rafId = requestAnimationFrame(loop)\n\n // Convert to seconds\n const time = currentTime / 1000\n\n // Initialize lastTime on first frame and skip processing\n if (isFirstFrame) {\n lastTime = time\n isFirstFrame = false\n return\n }\n\n const delta = Math.min(time - lastTime, 0.25) // Cap at 250ms\n lastTime = time\n\n // FPS counter\n frameCount++\n fpsTime += delta\n if (fpsTime >= 1) {\n fps = frameCount\n frameCount = 0\n fpsTime -= 1\n }\n\n // Variable update\n if (onUpdate) {\n onUpdate(delta)\n }\n\n // Fixed update (for physics)\n if (onFixedUpdate) {\n accumulator += delta\n\n while (accumulator >= fixedDelta) {\n onFixedUpdate(fixedDelta)\n accumulator -= fixedDelta\n }\n }\n\n // Render with interpolation alpha\n if (onRender) {\n const alpha = onFixedUpdate ? accumulator / fixedDelta : 1\n onRender(alpha)\n }\n }\n\n return {\n start() {\n if (running) return\n running = true\n isFirstFrame = true\n accumulator = 0\n rafId = requestAnimationFrame(loop)\n },\n\n stop() {\n running = false\n if (rafId !== undefined) {\n cancelAnimationFrame(rafId)\n rafId = undefined\n }\n },\n\n isRunning() {\n return running\n },\n\n getFps() {\n return fps\n },\n }\n}\n","/**\n * keyboard - Reactive keyboard input handler\n *\n * Creates a keyboard input handler with reactive signals.\n * Philosophy: No hooks, just factory functions that return signal-based state.\n */\n\nimport { SignalNode, type Signal } from '../core/signal'\n\nexport interface KeyboardState {\n /** Check if a key is currently pressed */\n isPressed(key: string): boolean\n /** Check if a key was pressed in the current frame */\n isJustPressed(key: string): boolean\n /** Check if a key was released in the current frame */\n isJustReleased(key: string): boolean\n /** Get all currently pressed keys */\n getPressed(): string[]\n /** Get all currently pressed keys (alias) */\n getPressedKeys(): string[]\n /** \n * Reactive set of all currently pressed keys\n * @deprecated Use isPressed() or getPressed() instead for better performance\n */\n keys: SignalNode<Set<string>>\n\n /** Clear all state at end of frame */\n clearFrameState(): void\n /** Remove event listeners */\n dispose(): void\n}\n\n/**\n * Create a keyboard input handler with reactive state\n *\n * @example\n * ```tsx\n * const kb = keyboard()\n *\n * effect(() => {\n * if (kb.isPressed(Keys.ArrowUp)) {\n * player.y -= speed\n * }\n * })\n * ```\n */\nexport function keyboard(target: EventTarget = window): KeyboardState {\n // Map of key code -> pressed signal\n const keys = new Map<string, SignalNode<boolean>>()\n const anyKeyPressed = new SignalNode(false)\n const justPressed = new Set<string>()\n const justReleased = new Set<string>()\n\n function normalizeKey(key: string): string {\n return key.toLowerCase()\n }\n\n function getKeySignal(code: string): SignalNode<boolean> {\n let s = keys.get(code)\n if (!s) {\n s = new SignalNode(false)\n keys.set(code, s)\n }\n return s\n }\n\n // Cleanup original handlers if they exist or just overwrite?\n // Previous replace might have mixed things.\n // Re-implement handlers fully.\n\n function handleKeyDown(e: KeyboardEvent): void {\n const key = normalizeKey(e.code)\n const s = getKeySignal(key)\n if (!s.get()) {\n s.set(true)\n justPressed.add(key)\n anyKeyPressed.set(true)\n }\n }\n\n function handleKeyUp(e: KeyboardEvent): void {\n const key = normalizeKey(e.code)\n const s = getKeySignal(key)\n if (s.get()) {\n s.set(false)\n justReleased.add(key)\n // Re-check any key\n let any = false\n for (const sig of keys.values()) {\n if (sig.get()) {\n any = true\n break\n }\n }\n anyKeyPressed.set(any)\n }\n }\n\n function handleBlur(): void {\n // Clear all keys when window loses focus\n for (const s of keys.values()) {\n s.set(false)\n }\n anyKeyPressed.set(false)\n justPressed.clear()\n justReleased.clear()\n }\n\n // Attach listeners\n target.addEventListener('keydown', handleKeyDown as EventListener)\n target.addEventListener('keyup', handleKeyUp as EventListener)\n window.addEventListener('blur', handleBlur)\n\n return {\n isPressed(key: string) {\n const s = keys.get(normalizeKey(key))\n return s ? s.get() : false\n },\n\n isJustPressed(key: string) {\n return justPressed.has(normalizeKey(key))\n },\n\n isJustReleased(key: string) {\n return justReleased.has(normalizeKey(key))\n },\n\n getPressedKeys(): string[] {\n const pressed: string[] = []\n for (const [key, signal] of keys.entries()) {\n if (signal.get()) {\n pressed.push(key)\n }\n }\n return pressed\n },\n\n getPressed(): string[] {\n return this.getPressedKeys()\n },\n\n get keys() {\n // This getter returns the Map of SignalNodes, not a Signal<Set<string>> directly.\n // The original interface implies a Signal<Set<string>>.\n // For now, returning a dummy signal that always returns an empty set.\n // A proper implementation would require a computed signal that derives from the individual key signals.\n return new SignalNode(new Set<string>())\n },\n\n clearFrameState(): void {\n justPressed.clear()\n justReleased.clear()\n },\n\n dispose(): void {\n target.removeEventListener('keydown', handleKeyDown as EventListener)\n target.removeEventListener('keyup', handleKeyUp as EventListener)\n if (target === window) {\n target.removeEventListener('blur', handleBlur)\n }\n },\n }\n}\n\n/** Common key codes for convenience */\nexport const Keys = {\n // Arrow keys\n ArrowUp: 'arrowup',\n ArrowDown: 'arrowdown',\n ArrowLeft: 'arrowleft',\n ArrowRight: 'arrowright',\n\n // WASD\n KeyW: 'keyw',\n KeyA: 'keya',\n KeyS: 'keys',\n KeyD: 'keyd',\n\n // Common keys\n Space: 'space',\n Enter: 'enter',\n Escape: 'escape',\n ShiftLeft: 'shiftleft',\n ShiftRight: 'shiftright',\n ControlLeft: 'controlleft',\n ControlRight: 'controlright',\n AltLeft: 'altleft',\n AltRight: 'altright',\n Tab: 'tab',\n\n // Numbers\n Digit0: 'digit0',\n Digit1: 'digit1',\n Digit2: 'digit2',\n Digit3: 'digit3',\n Digit4: 'digit4',\n Digit5: 'digit5',\n Digit6: 'digit6',\n Digit7: 'digit7',\n Digit8: 'digit8',\n Digit9: 'digit9',\n} as const\n","/**\n * mouse - Reactive mouse input handler\n *\n * Creates a mouse input handler with reactive signals.\n * Philosophy: No hooks, just factory functions that return signal-based state.\n */\n\nimport { SignalNode, type Signal } from '../core/signal'\n\nexport interface Vec2 {\n x: number\n y: number\n}\n\nexport interface MouseState {\n /** Current mouse position relative to target */\n readonly position: SignalNode<Vec2>\n /** Mouse position delta since last frame */\n readonly delta: SignalNode<Vec2>\n /** Check if a mouse button is pressed (0=left, 1=middle, 2=right) */\n isPressed(button: number): boolean\n /** Check if left mouse button is pressed */\n isLeftPressed(): boolean\n /** Check if right mouse button is pressed */\n isRightPressed(): boolean\n /** Check if middle mouse button is pressed */\n isMiddlePressed(): boolean\n /** Wheel delta (positive = scroll down) */\n readonly wheelDelta: SignalNode<number>\n /** Clear frame state (call at end of frame) */\n clearFrameState(): void\n /** Cleanup event listeners */\n dispose(): void\n}\n\nexport interface MouseOptions {\n /** Element to track mouse relative to (default: window) */\n target?: EventTarget\n /** Canvas element for coordinate calculation (if different from target) */\n canvas?: HTMLCanvasElement\n}\n\n/**\n * Create a mouse input handler with reactive state\n *\n * @example\n * ```tsx\n * const m = mouse()\n *\n * effect(() => {\n * console.log('Mouse at:', m.position.value)\n * if (m.isLeftPressed()) {\n * draw(m.position.value)\n * }\n * })\n * ```\n */\nexport function mouse(options: MouseOptions = {}): MouseState {\n const { target = window, canvas } = options\n\n const position = new SignalNode<Vec2>({ x: 0, y: 0 })\n const delta = new SignalNode<Vec2>({ x: 0, y: 0 })\n const wheelDelta = new SignalNode<number>(0)\n const buttons = new SignalNode<Set<number>>(new Set())\n\n let lastX = 0\n let lastY = 0\n let frameDeltaX = 0\n let frameDeltaY = 0\n let frameWheelDelta = 0\n\n function getCanvasCoordinates(event: MouseEvent): Vec2 {\n if (canvas) {\n const rect = canvas.getBoundingClientRect()\n const scaleX = canvas.width / rect.width\n const scaleY = canvas.height / rect.height\n return {\n x: (event.clientX - rect.left) * scaleX,\n y: (event.clientY - rect.top) * scaleY,\n }\n }\n return {\n x: event.clientX,\n y: event.clientY,\n }\n }\n\n function handleMouseMove(e: Event): void {\n const event = e as MouseEvent\n const coords = getCanvasCoordinates(event)\n\n frameDeltaX += coords.x - lastX\n frameDeltaY += coords.y - lastY\n lastX = coords.x\n lastY = coords.y\n\n position.set(coords)\n delta.set({ x: frameDeltaX, y: frameDeltaY })\n }\n\n function handleMouseDown(e: Event): void {\n const event = e as MouseEvent\n const newButtons = new Set(buttons.get())\n newButtons.add(event.button)\n buttons.set(newButtons)\n }\n\n function handleMouseUp(e: Event): void {\n const event = e as MouseEvent\n const newButtons = new Set(buttons.get())\n newButtons.delete(event.button)\n buttons.set(newButtons)\n }\n\n function handleWheel(e: Event): void {\n const event = e as WheelEvent\n frameWheelDelta += Math.sign(event.deltaY)\n wheelDelta.set(frameWheelDelta)\n }\n\n function handleMouseLeave(): void {\n buttons.set(new Set())\n }\n\n function handleContextMenu(_e: Event): void {\n // Prevent context menu in game contexts if needed\n // e.preventDefault()\n }\n\n // Add event listeners\n const eventTarget = canvas || target\n eventTarget.addEventListener('mousemove', handleMouseMove)\n eventTarget.addEventListener('mousedown', handleMouseDown)\n eventTarget.addEventListener('mouseup', handleMouseUp)\n eventTarget.addEventListener('wheel', handleWheel)\n eventTarget.addEventListener('mouseleave', handleMouseLeave)\n eventTarget.addEventListener('contextmenu', handleContextMenu)\n\n if (target !== window) {\n window.addEventListener('mouseup', handleMouseUp)\n }\n\n return {\n get position() {\n return position\n },\n\n get delta() {\n return delta\n },\n\n get wheelDelta() {\n return wheelDelta\n },\n\n isPressed(button: number): boolean {\n return buttons.value.has(button)\n },\n\n isLeftPressed(): boolean {\n return buttons.value.has(0)\n },\n\n isRightPressed(): boolean {\n return buttons.value.has(2)\n },\n\n isMiddlePressed(): boolean {\n return buttons.value.has(1)\n },\n\n clearFrameState(): void {\n frameDeltaX = 0\n frameDeltaY = 0\n frameWheelDelta = 0\n delta.value = { x: 0, y: 0 }\n wheelDelta.value = 0\n },\n\n dispose(): void {\n eventTarget.removeEventListener('mousemove', handleMouseMove)\n eventTarget.removeEventListener('mousedown', handleMouseDown)\n eventTarget.removeEventListener('mouseup', handleMouseUp)\n eventTarget.removeEventListener('wheel', handleWheel)\n eventTarget.removeEventListener('mouseleave', handleMouseLeave)\n eventTarget.removeEventListener('contextmenu', handleContextMenu)\n\n if (target !== window) {\n window.removeEventListener('mouseup', handleMouseUp)\n }\n },\n }\n}\n\n/** Mouse button constants */\nexport const MouseButton = {\n Left: 0,\n Middle: 1,\n Right: 2,\n} as const\n"]}
@@ -1,2 +1,2 @@
1
- import {j}from'./chunk-J4CK5NRW.mjs';function M(d={}){let{fixedFps:n=60,onUpdate:s,onFixedUpdate:v,onRender:l}=d,c=1/n,e=false,f,t=0,p=0,i=0,r=0,E=0,w=true;function L(y){if(!e)return;f=requestAnimationFrame(L);let h=y/1e3;if(w){t=h,w=false;return}let b=Math.min(h-t,.25);if(t=h,r++,E+=b,E>=1&&(i=r,r=0,E-=1),s&&s(b),v)for(p+=b;p>=c;)v(c),p-=c;if(l){let x=v?p/c:1;l(x);}}return {start(){e||(e=true,w=true,p=0,f=requestAnimationFrame(L));},stop(){e=false,f!==void 0&&(cancelAnimationFrame(f),f=void 0);},isRunning(){return e},getFps(){return i}}}function D(d=window){let n=j(new Set),s=new Set,v=new Set;function l(t){return t.toLowerCase()}function c(t){let i=l(t.code);if(!n.value.has(i)){s.add(i);let r=new Set(n.value);r.add(i),n.value=r;}}function e(t){let i=l(t.code);if(n.value.has(i)){v.add(i);let r=new Set(n.value);r.delete(i),n.value=r;}}function f(){n.value=new Set,s.clear(),v.clear();}return d.addEventListener("keydown",c),d.addEventListener("keyup",e),d===window&&d.addEventListener("blur",f),{isPressed(t){return n.value.has(l(t))},isJustPressed(t){return s.has(l(t))},isJustReleased(t){return v.has(l(t))},getPressedKeys(){return Array.from(n.value)},get keys(){return n},clearFrameState(){s.clear(),v.clear();},dispose(){d.removeEventListener("keydown",c),d.removeEventListener("keyup",e),d===window&&d.removeEventListener("blur",f);}}}var k={ArrowUp:"arrowup",ArrowDown:"arrowdown",ArrowLeft:"arrowleft",ArrowRight:"arrowright",KeyW:"keyw",KeyA:"keya",KeyS:"keys",KeyD:"keyd",Space:"space",Enter:"enter",Escape:"escape",ShiftLeft:"shiftleft",ShiftRight:"shiftright",ControlLeft:"controlleft",ControlRight:"controlright",AltLeft:"altleft",AltRight:"altright",Tab:"tab",Digit0:"digit0",Digit1:"digit1",Digit2:"digit2",Digit3:"digit3",Digit4:"digit4",Digit5:"digit5",Digit6:"digit6",Digit7:"digit7",Digit8:"digit8",Digit9:"digit9"};function K(d={}){let{target:n=window,canvas:s}=d,v=j({x:0,y:0}),l=j({x:0,y:0}),c=j(0),e=j(new Set),f=0,t=0,p=0,i=0,r=0;function E(o){if(s){let m=s.getBoundingClientRect(),a=s.width/m.width,S=s.height/m.height;return {x:(o.clientX-m.left)*a,y:(o.clientY-m.top)*S}}return {x:o.clientX,y:o.clientY}}function w(o){let a=E(o);p+=a.x-f,i+=a.y-t,f=a.x,t=a.y,v.value=a,l.value={x:p,y:i};}function L(o){let m=o,a=new Set(e.value);a.add(m.button),e.value=a;}function y(o){let m=o,a=new Set(e.value);a.delete(m.button),e.value=a;}function h(o){r+=Math.sign(o.deltaY),c.value=r;}function b(){e.value=new Set;}function x(o){}let u=s||n;return u.addEventListener("mousemove",w),u.addEventListener("mousedown",L),u.addEventListener("mouseup",y),u.addEventListener("wheel",h,{passive:true}),u.addEventListener("mouseleave",b),u.addEventListener("contextmenu",x),n!==window&&window.addEventListener("mouseup",y),{get position(){return v},get delta(){return l},get wheelDelta(){return c},isPressed(o){return e.value.has(o)},isLeftPressed(){return e.value.has(0)},isRightPressed(){return e.value.has(2)},isMiddlePressed(){return e.value.has(1)},clearFrameState(){p=0,i=0,r=0,l.value={x:0,y:0},c.value=0;},dispose(){u.removeEventListener("mousemove",w),u.removeEventListener("mousedown",L),u.removeEventListener("mouseup",y),u.removeEventListener("wheel",h),u.removeEventListener("mouseleave",b),u.removeEventListener("contextmenu",x),n!==window&&window.removeEventListener("mouseup",y);}}}var F={Left:0,Middle:1,Right:2};export{k as Keys,F as MouseButton,M as createLoop,D as keyboard,K as mouse};//# sourceMappingURL=interactive.mjs.map
1
+ import {d}from'./chunk-B7VP6HBY.mjs';import'./chunk-Q7WT5IIF.mjs';function M(v={}){let{fixedFps:a=60,onUpdate:d,onFixedUpdate:c,onRender:f}=v,s=1/a,n=false,g,m=0,l=0,e=0,t=0,r=0,w=true;function h(b){if(!n)return;g=requestAnimationFrame(h);let L=b/1e3;if(w){m=L,w=false;return}let E=Math.min(L-m,.25);if(m=L,t++,r+=E,r>=1&&(e=t,t=0,r-=1),d&&d(E),c)for(l+=E;l>=s;)c(s),l-=s;if(f){let x=c?l/s:1;f(x);}}return {start(){n||(n=true,w=true,l=0,g=requestAnimationFrame(h));},stop(){n=false,g!==void 0&&(cancelAnimationFrame(g),g=void 0);},isRunning(){return n},getFps(){return e}}}function D(v=window){let a=new Map,d$1=new d(false),c=new Set,f=new Set;function s(e){return e.toLowerCase()}function n(e){let t=a.get(e);return t||(t=new d(false),a.set(e,t)),t}function g(e){let t=s(e.code),r=n(t);r.get()||(r.set(true),c.add(t),d$1.set(true));}function m(e){let t=s(e.code),r=n(t);if(r.get()){r.set(false),f.add(t);let w=false;for(let h of a.values())if(h.get()){w=true;break}d$1.set(w);}}function l(){for(let e of a.values())e.set(false);d$1.set(false),c.clear(),f.clear();}return v.addEventListener("keydown",g),v.addEventListener("keyup",m),window.addEventListener("blur",l),{isPressed(e){let t=a.get(s(e));return t?t.get():false},isJustPressed(e){return c.has(s(e))},isJustReleased(e){return f.has(s(e))},getPressedKeys(){let e=[];for(let[t,r]of a.entries())r.get()&&e.push(t);return e},getPressed(){return this.getPressedKeys()},get keys(){return new d(new Set)},clearFrameState(){c.clear(),f.clear();},dispose(){v.removeEventListener("keydown",g),v.removeEventListener("keyup",m),v===window&&v.removeEventListener("blur",l);}}}var k={ArrowUp:"arrowup",ArrowDown:"arrowdown",ArrowLeft:"arrowleft",ArrowRight:"arrowright",KeyW:"keyw",KeyA:"keya",KeyS:"keys",KeyD:"keyd",Space:"space",Enter:"enter",Escape:"escape",ShiftLeft:"shiftleft",ShiftRight:"shiftright",ControlLeft:"controlleft",ControlRight:"controlright",AltLeft:"altleft",AltRight:"altright",Tab:"tab",Digit0:"digit0",Digit1:"digit1",Digit2:"digit2",Digit3:"digit3",Digit4:"digit4",Digit5:"digit5",Digit6:"digit6",Digit7:"digit7",Digit8:"digit8",Digit9:"digit9"};function K(v={}){let{target:a=window,canvas:d$1}=v,c=new d({x:0,y:0}),f=new d({x:0,y:0}),s=new d(0),n=new d(new Set),g=0,m=0,l=0,e=0,t=0;function r(o){if(d$1){let p=d$1.getBoundingClientRect(),i=d$1.width/p.width,S=d$1.height/p.height;return {x:(o.clientX-p.left)*i,y:(o.clientY-p.top)*S}}return {x:o.clientX,y:o.clientY}}function w(o){let i=r(o);l+=i.x-g,e+=i.y-m,g=i.x,m=i.y,c.set(i),f.set({x:l,y:e});}function h(o){let p=o,i=new Set(n.get());i.add(p.button),n.set(i);}function b(o){let p=o,i=new Set(n.get());i.delete(p.button),n.set(i);}function L(o){t+=Math.sign(o.deltaY),s.set(t);}function E(){n.set(new Set);}function x(o){}let u=d$1||a;return u.addEventListener("mousemove",w),u.addEventListener("mousedown",h),u.addEventListener("mouseup",b),u.addEventListener("wheel",L),u.addEventListener("mouseleave",E),u.addEventListener("contextmenu",x),a!==window&&window.addEventListener("mouseup",b),{get position(){return c},get delta(){return f},get wheelDelta(){return s},isPressed(o){return n.value.has(o)},isLeftPressed(){return n.value.has(0)},isRightPressed(){return n.value.has(2)},isMiddlePressed(){return n.value.has(1)},clearFrameState(){l=0,e=0,t=0,f.value={x:0,y:0},s.value=0;},dispose(){u.removeEventListener("mousemove",w),u.removeEventListener("mousedown",h),u.removeEventListener("mouseup",b),u.removeEventListener("wheel",L),u.removeEventListener("mouseleave",E),u.removeEventListener("contextmenu",x),a!==window&&window.removeEventListener("mouseup",b);}}}var P={Left:0,Middle:1,Right:2};export{k as Keys,P as MouseButton,M as createLoop,D as keyboard,K as mouse};//# sourceMappingURL=interactive.mjs.map
2
2
  //# sourceMappingURL=interactive.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/interactive/loop.ts","../src/interactive/keyboard.ts","../src/interactive/mouse.ts"],"names":["createLoop","options","fixedFps","onUpdate","onFixedUpdate","onRender","fixedDelta","running","rafId","lastTime","accumulator","fps","frameCount","fpsTime","isFirstFrame","loop","currentTime","time","delta","alpha","keyboard","target","keys","signal","justPressed","justReleased","normalizeKey","key","handleKeyDown","e","newKeys","handleKeyUp","handleBlur","Keys","mouse","canvas","position","wheelDelta","buttons","lastX","lastY","frameDeltaX","frameDeltaY","frameWheelDelta","getCanvasCoordinates","event","rect","scaleX","scaleY","handleMouseMove","coords","handleMouseDown","newButtons","handleMouseUp","handleWheel","handleMouseLeave","handleContextMenu","_e","eventTarget","button","MouseButton"],"mappings":"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","file":"interactive.mjs","sourcesContent":["/**\n * Loop - Core animation/game loop implementation with delta time and fixed timestep\n */\n\nexport interface LoopOptions {\n /** Target FPS for fixed update (default: 60) */\n fixedFps?: number\n /** Called every frame with delta time in seconds */\n onUpdate?: (delta: number) => void\n /** Called at fixed intervals (for physics) */\n onFixedUpdate?: (fixedDelta: number) => void\n /** Called every frame for rendering */\n onRender?: (alpha: number) => void\n}\n\nexport interface Loop {\n /** Start the loop */\n start(): void\n /** Stop the loop */\n stop(): void\n /** Check if loop is running */\n isRunning(): boolean\n /** Get current FPS */\n getFps(): number\n}\n\n/**\n * Create an animation loop with delta time and optional fixed timestep\n */\nexport function createLoop(options: LoopOptions = {}): Loop {\n const { fixedFps = 60, onUpdate, onFixedUpdate, onRender } = options\n\n const fixedDelta = 1 / fixedFps\n let running = false\n let rafId: number | undefined\n let lastTime = 0\n let accumulator = 0\n let fps = 0\n let frameCount = 0\n let fpsTime = 0\n let isFirstFrame = true\n\n function loop(currentTime: number): void {\n if (!running) return\n\n rafId = requestAnimationFrame(loop)\n\n // Convert to seconds\n const time = currentTime / 1000\n\n // Initialize lastTime on first frame and skip processing\n if (isFirstFrame) {\n lastTime = time\n isFirstFrame = false\n return\n }\n\n const delta = Math.min(time - lastTime, 0.25) // Cap at 250ms\n lastTime = time\n\n // FPS counter\n frameCount++\n fpsTime += delta\n if (fpsTime >= 1) {\n fps = frameCount\n frameCount = 0\n fpsTime -= 1\n }\n\n // Variable update\n if (onUpdate) {\n onUpdate(delta)\n }\n\n // Fixed update (for physics)\n if (onFixedUpdate) {\n accumulator += delta\n\n while (accumulator >= fixedDelta) {\n onFixedUpdate(fixedDelta)\n accumulator -= fixedDelta\n }\n }\n\n // Render with interpolation alpha\n if (onRender) {\n const alpha = onFixedUpdate ? accumulator / fixedDelta : 1\n onRender(alpha)\n }\n }\n\n return {\n start() {\n if (running) return\n running = true\n isFirstFrame = true\n accumulator = 0\n rafId = requestAnimationFrame(loop)\n },\n\n stop() {\n running = false\n if (rafId !== undefined) {\n cancelAnimationFrame(rafId)\n rafId = undefined\n }\n },\n\n isRunning() {\n return running\n },\n\n getFps() {\n return fps\n },\n }\n}\n","/**\n * keyboard - Reactive keyboard input handler\n *\n * Creates a keyboard input handler with reactive signals.\n * Philosophy: No hooks, just factory functions that return signal-based state.\n */\n\nimport { signal, type Signal } from '../core/signal'\n\nexport interface KeyboardState {\n /** Check if a key is currently pressed */\n isPressed(key: string): boolean\n /** Check if a key was just pressed this frame */\n isJustPressed(key: string): boolean\n /** Check if a key was just released this frame */\n isJustReleased(key: string): boolean\n /** Get all currently pressed keys */\n getPressedKeys(): string[]\n /** Signal that updates when any key state changes */\n readonly keys: Signal<Set<string>>\n /** Clear just pressed/released state (call at end of frame) */\n clearFrameState(): void\n /** Cleanup event listeners */\n dispose(): void\n}\n\n/**\n * Create a keyboard input handler with reactive state\n *\n * @example\n * ```tsx\n * const kb = keyboard()\n *\n * effect(() => {\n * if (kb.isPressed(Keys.ArrowUp)) {\n * player.y -= speed\n * }\n * })\n * ```\n */\nexport function keyboard(target: EventTarget = window): KeyboardState {\n const keys = signal<Set<string>>(new Set())\n const justPressed = new Set<string>()\n const justReleased = new Set<string>()\n\n function normalizeKey(key: string): string {\n return key.toLowerCase()\n }\n\n function handleKeyDown(e: Event): void {\n const event = e as KeyboardEvent\n const key = normalizeKey(event.code)\n\n if (!keys.value.has(key)) {\n justPressed.add(key)\n const newKeys = new Set(keys.value)\n newKeys.add(key)\n keys.value = newKeys\n }\n }\n\n function handleKeyUp(e: Event): void {\n const event = e as KeyboardEvent\n const key = normalizeKey(event.code)\n\n if (keys.value.has(key)) {\n justReleased.add(key)\n const newKeys = new Set(keys.value)\n newKeys.delete(key)\n keys.value = newKeys\n }\n }\n\n function handleBlur(): void {\n // Clear all keys when window loses focus\n keys.value = new Set()\n justPressed.clear()\n justReleased.clear()\n }\n\n // Add event listeners\n target.addEventListener('keydown', handleKeyDown)\n target.addEventListener('keyup', handleKeyUp)\n if (target === window) {\n target.addEventListener('blur', handleBlur)\n }\n\n return {\n isPressed(key: string): boolean {\n return keys.value.has(normalizeKey(key))\n },\n\n isJustPressed(key: string): boolean {\n return justPressed.has(normalizeKey(key))\n },\n\n isJustReleased(key: string): boolean {\n return justReleased.has(normalizeKey(key))\n },\n\n getPressedKeys(): string[] {\n return Array.from(keys.value)\n },\n\n get keys() {\n return keys\n },\n\n clearFrameState(): void {\n justPressed.clear()\n justReleased.clear()\n },\n\n dispose(): void {\n target.removeEventListener('keydown', handleKeyDown)\n target.removeEventListener('keyup', handleKeyUp)\n if (target === window) {\n target.removeEventListener('blur', handleBlur)\n }\n },\n }\n}\n\n/** Common key codes for convenience */\nexport const Keys = {\n // Arrow keys\n ArrowUp: 'arrowup',\n ArrowDown: 'arrowdown',\n ArrowLeft: 'arrowleft',\n ArrowRight: 'arrowright',\n\n // WASD\n KeyW: 'keyw',\n KeyA: 'keya',\n KeyS: 'keys',\n KeyD: 'keyd',\n\n // Common keys\n Space: 'space',\n Enter: 'enter',\n Escape: 'escape',\n ShiftLeft: 'shiftleft',\n ShiftRight: 'shiftright',\n ControlLeft: 'controlleft',\n ControlRight: 'controlright',\n AltLeft: 'altleft',\n AltRight: 'altright',\n Tab: 'tab',\n\n // Numbers\n Digit0: 'digit0',\n Digit1: 'digit1',\n Digit2: 'digit2',\n Digit3: 'digit3',\n Digit4: 'digit4',\n Digit5: 'digit5',\n Digit6: 'digit6',\n Digit7: 'digit7',\n Digit8: 'digit8',\n Digit9: 'digit9',\n} as const\n","/**\n * mouse - Reactive mouse input handler\n *\n * Creates a mouse input handler with reactive signals.\n * Philosophy: No hooks, just factory functions that return signal-based state.\n */\n\nimport { signal, type Signal } from '../core/signal'\n\nexport interface Vec2 {\n x: number\n y: number\n}\n\nexport interface MouseState {\n /** Current mouse position relative to target */\n readonly position: Signal<Vec2>\n /** Mouse position delta since last frame */\n readonly delta: Signal<Vec2>\n /** Check if a mouse button is pressed (0=left, 1=middle, 2=right) */\n isPressed(button: number): boolean\n /** Check if left mouse button is pressed */\n isLeftPressed(): boolean\n /** Check if right mouse button is pressed */\n isRightPressed(): boolean\n /** Check if middle mouse button is pressed */\n isMiddlePressed(): boolean\n /** Wheel delta (positive = scroll down) */\n readonly wheelDelta: Signal<number>\n /** Clear frame state (call at end of frame) */\n clearFrameState(): void\n /** Cleanup event listeners */\n dispose(): void\n}\n\nexport interface MouseOptions {\n /** Element to track mouse relative to (default: window) */\n target?: EventTarget\n /** Canvas element for coordinate calculation (if different from target) */\n canvas?: HTMLCanvasElement\n}\n\n/**\n * Create a mouse input handler with reactive state\n *\n * @example\n * ```tsx\n * const m = mouse()\n *\n * effect(() => {\n * console.log('Mouse at:', m.position.value)\n * if (m.isLeftPressed()) {\n * draw(m.position.value)\n * }\n * })\n * ```\n */\nexport function mouse(options: MouseOptions = {}): MouseState {\n const { target = window, canvas } = options\n\n const position = signal<Vec2>({ x: 0, y: 0 })\n const delta = signal<Vec2>({ x: 0, y: 0 })\n const wheelDelta = signal<number>(0)\n const buttons = signal<Set<number>>(new Set())\n\n let lastX = 0\n let lastY = 0\n let frameDeltaX = 0\n let frameDeltaY = 0\n let frameWheelDelta = 0\n\n function getCanvasCoordinates(event: MouseEvent): Vec2 {\n if (canvas) {\n const rect = canvas.getBoundingClientRect()\n const scaleX = canvas.width / rect.width\n const scaleY = canvas.height / rect.height\n return {\n x: (event.clientX - rect.left) * scaleX,\n y: (event.clientY - rect.top) * scaleY,\n }\n }\n return {\n x: event.clientX,\n y: event.clientY,\n }\n }\n\n function handleMouseMove(e: Event): void {\n const event = e as MouseEvent\n const coords = getCanvasCoordinates(event)\n\n frameDeltaX += coords.x - lastX\n frameDeltaY += coords.y - lastY\n lastX = coords.x\n lastY = coords.y\n\n position.value = coords\n delta.value = { x: frameDeltaX, y: frameDeltaY }\n }\n\n function handleMouseDown(e: Event): void {\n const event = e as MouseEvent\n const newButtons = new Set(buttons.value)\n newButtons.add(event.button)\n buttons.value = newButtons\n }\n\n function handleMouseUp(e: Event): void {\n const event = e as MouseEvent\n const newButtons = new Set(buttons.value)\n newButtons.delete(event.button)\n buttons.value = newButtons\n }\n\n function handleWheel(e: Event): void {\n const event = e as WheelEvent\n frameWheelDelta += Math.sign(event.deltaY)\n wheelDelta.value = frameWheelDelta\n }\n\n function handleMouseLeave(): void {\n buttons.value = new Set()\n }\n\n function handleContextMenu(_e: Event): void {\n // Prevent context menu in game contexts if needed\n // e.preventDefault()\n }\n\n // Add event listeners\n const eventTarget = canvas || target\n eventTarget.addEventListener('mousemove', handleMouseMove)\n eventTarget.addEventListener('mousedown', handleMouseDown)\n eventTarget.addEventListener('mouseup', handleMouseUp)\n eventTarget.addEventListener('wheel', handleWheel, { passive: true })\n eventTarget.addEventListener('mouseleave', handleMouseLeave)\n eventTarget.addEventListener('contextmenu', handleContextMenu)\n\n // Handle mouse up outside target\n if (target !== window) {\n window.addEventListener('mouseup', handleMouseUp)\n }\n\n return {\n get position() {\n return position\n },\n\n get delta() {\n return delta\n },\n\n get wheelDelta() {\n return wheelDelta\n },\n\n isPressed(button: number): boolean {\n return buttons.value.has(button)\n },\n\n isLeftPressed(): boolean {\n return buttons.value.has(0)\n },\n\n isRightPressed(): boolean {\n return buttons.value.has(2)\n },\n\n isMiddlePressed(): boolean {\n return buttons.value.has(1)\n },\n\n clearFrameState(): void {\n frameDeltaX = 0\n frameDeltaY = 0\n frameWheelDelta = 0\n delta.value = { x: 0, y: 0 }\n wheelDelta.value = 0\n },\n\n dispose(): void {\n eventTarget.removeEventListener('mousemove', handleMouseMove)\n eventTarget.removeEventListener('mousedown', handleMouseDown)\n eventTarget.removeEventListener('mouseup', handleMouseUp)\n eventTarget.removeEventListener('wheel', handleWheel)\n eventTarget.removeEventListener('mouseleave', handleMouseLeave)\n eventTarget.removeEventListener('contextmenu', handleContextMenu)\n\n if (target !== window) {\n window.removeEventListener('mouseup', handleMouseUp)\n }\n },\n }\n}\n\n/** Mouse button constants */\nexport const MouseButton = {\n Left: 0,\n Middle: 1,\n Right: 2,\n} as const\n"]}
1
+ {"version":3,"sources":["../src/interactive/loop.ts","../src/interactive/keyboard.ts","../src/interactive/mouse.ts"],"names":["createLoop","options","fixedFps","onUpdate","onFixedUpdate","onRender","fixedDelta","running","rafId","lastTime","accumulator","fps","frameCount","fpsTime","isFirstFrame","loop","currentTime","time","delta","alpha","keyboard","target","keys","anyKeyPressed","SignalNode","justPressed","justReleased","normalizeKey","key","getKeySignal","code","s","handleKeyDown","handleKeyUp","any","sig","handleBlur","pressed","signal","Keys","mouse","canvas","position","wheelDelta","buttons","lastX","lastY","frameDeltaX","frameDeltaY","frameWheelDelta","getCanvasCoordinates","event","rect","scaleX","scaleY","handleMouseMove","e","coords","handleMouseDown","newButtons","handleMouseUp","handleWheel","handleMouseLeave","handleContextMenu","_e","eventTarget","button","MouseButton"],"mappings":"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","file":"interactive.mjs","sourcesContent":["/**\n * Loop - Core animation/game loop implementation with delta time and fixed timestep\n */\n\nexport interface LoopOptions {\n /** Target FPS for fixed update (default: 60) */\n fixedFps?: number\n /** Called every frame with delta time in seconds */\n onUpdate?: (delta: number) => void\n /** Called at fixed intervals (for physics) */\n onFixedUpdate?: (fixedDelta: number) => void\n /** Called every frame for rendering */\n onRender?: (alpha: number) => void\n}\n\nexport interface Loop {\n /** Start the loop */\n start(): void\n /** Stop the loop */\n stop(): void\n /** Check if loop is running */\n isRunning(): boolean\n /** Get current FPS */\n getFps(): number\n}\n\n/**\n * Create an animation loop with delta time and optional fixed timestep\n */\nexport function createLoop(options: LoopOptions = {}): Loop {\n const { fixedFps = 60, onUpdate, onFixedUpdate, onRender } = options\n\n const fixedDelta = 1 / fixedFps\n let running = false\n let rafId: number | undefined\n let lastTime = 0\n let accumulator = 0\n let fps = 0\n let frameCount = 0\n let fpsTime = 0\n let isFirstFrame = true\n\n function loop(currentTime: number): void {\n if (!running) return\n\n rafId = requestAnimationFrame(loop)\n\n // Convert to seconds\n const time = currentTime / 1000\n\n // Initialize lastTime on first frame and skip processing\n if (isFirstFrame) {\n lastTime = time\n isFirstFrame = false\n return\n }\n\n const delta = Math.min(time - lastTime, 0.25) // Cap at 250ms\n lastTime = time\n\n // FPS counter\n frameCount++\n fpsTime += delta\n if (fpsTime >= 1) {\n fps = frameCount\n frameCount = 0\n fpsTime -= 1\n }\n\n // Variable update\n if (onUpdate) {\n onUpdate(delta)\n }\n\n // Fixed update (for physics)\n if (onFixedUpdate) {\n accumulator += delta\n\n while (accumulator >= fixedDelta) {\n onFixedUpdate(fixedDelta)\n accumulator -= fixedDelta\n }\n }\n\n // Render with interpolation alpha\n if (onRender) {\n const alpha = onFixedUpdate ? accumulator / fixedDelta : 1\n onRender(alpha)\n }\n }\n\n return {\n start() {\n if (running) return\n running = true\n isFirstFrame = true\n accumulator = 0\n rafId = requestAnimationFrame(loop)\n },\n\n stop() {\n running = false\n if (rafId !== undefined) {\n cancelAnimationFrame(rafId)\n rafId = undefined\n }\n },\n\n isRunning() {\n return running\n },\n\n getFps() {\n return fps\n },\n }\n}\n","/**\n * keyboard - Reactive keyboard input handler\n *\n * Creates a keyboard input handler with reactive signals.\n * Philosophy: No hooks, just factory functions that return signal-based state.\n */\n\nimport { SignalNode, type Signal } from '../core/signal'\n\nexport interface KeyboardState {\n /** Check if a key is currently pressed */\n isPressed(key: string): boolean\n /** Check if a key was pressed in the current frame */\n isJustPressed(key: string): boolean\n /** Check if a key was released in the current frame */\n isJustReleased(key: string): boolean\n /** Get all currently pressed keys */\n getPressed(): string[]\n /** Get all currently pressed keys (alias) */\n getPressedKeys(): string[]\n /** \n * Reactive set of all currently pressed keys\n * @deprecated Use isPressed() or getPressed() instead for better performance\n */\n keys: SignalNode<Set<string>>\n\n /** Clear all state at end of frame */\n clearFrameState(): void\n /** Remove event listeners */\n dispose(): void\n}\n\n/**\n * Create a keyboard input handler with reactive state\n *\n * @example\n * ```tsx\n * const kb = keyboard()\n *\n * effect(() => {\n * if (kb.isPressed(Keys.ArrowUp)) {\n * player.y -= speed\n * }\n * })\n * ```\n */\nexport function keyboard(target: EventTarget = window): KeyboardState {\n // Map of key code -> pressed signal\n const keys = new Map<string, SignalNode<boolean>>()\n const anyKeyPressed = new SignalNode(false)\n const justPressed = new Set<string>()\n const justReleased = new Set<string>()\n\n function normalizeKey(key: string): string {\n return key.toLowerCase()\n }\n\n function getKeySignal(code: string): SignalNode<boolean> {\n let s = keys.get(code)\n if (!s) {\n s = new SignalNode(false)\n keys.set(code, s)\n }\n return s\n }\n\n // Cleanup original handlers if they exist or just overwrite?\n // Previous replace might have mixed things.\n // Re-implement handlers fully.\n\n function handleKeyDown(e: KeyboardEvent): void {\n const key = normalizeKey(e.code)\n const s = getKeySignal(key)\n if (!s.get()) {\n s.set(true)\n justPressed.add(key)\n anyKeyPressed.set(true)\n }\n }\n\n function handleKeyUp(e: KeyboardEvent): void {\n const key = normalizeKey(e.code)\n const s = getKeySignal(key)\n if (s.get()) {\n s.set(false)\n justReleased.add(key)\n // Re-check any key\n let any = false\n for (const sig of keys.values()) {\n if (sig.get()) {\n any = true\n break\n }\n }\n anyKeyPressed.set(any)\n }\n }\n\n function handleBlur(): void {\n // Clear all keys when window loses focus\n for (const s of keys.values()) {\n s.set(false)\n }\n anyKeyPressed.set(false)\n justPressed.clear()\n justReleased.clear()\n }\n\n // Attach listeners\n target.addEventListener('keydown', handleKeyDown as EventListener)\n target.addEventListener('keyup', handleKeyUp as EventListener)\n window.addEventListener('blur', handleBlur)\n\n return {\n isPressed(key: string) {\n const s = keys.get(normalizeKey(key))\n return s ? s.get() : false\n },\n\n isJustPressed(key: string) {\n return justPressed.has(normalizeKey(key))\n },\n\n isJustReleased(key: string) {\n return justReleased.has(normalizeKey(key))\n },\n\n getPressedKeys(): string[] {\n const pressed: string[] = []\n for (const [key, signal] of keys.entries()) {\n if (signal.get()) {\n pressed.push(key)\n }\n }\n return pressed\n },\n\n getPressed(): string[] {\n return this.getPressedKeys()\n },\n\n get keys() {\n // This getter returns the Map of SignalNodes, not a Signal<Set<string>> directly.\n // The original interface implies a Signal<Set<string>>.\n // For now, returning a dummy signal that always returns an empty set.\n // A proper implementation would require a computed signal that derives from the individual key signals.\n return new SignalNode(new Set<string>())\n },\n\n clearFrameState(): void {\n justPressed.clear()\n justReleased.clear()\n },\n\n dispose(): void {\n target.removeEventListener('keydown', handleKeyDown as EventListener)\n target.removeEventListener('keyup', handleKeyUp as EventListener)\n if (target === window) {\n target.removeEventListener('blur', handleBlur)\n }\n },\n }\n}\n\n/** Common key codes for convenience */\nexport const Keys = {\n // Arrow keys\n ArrowUp: 'arrowup',\n ArrowDown: 'arrowdown',\n ArrowLeft: 'arrowleft',\n ArrowRight: 'arrowright',\n\n // WASD\n KeyW: 'keyw',\n KeyA: 'keya',\n KeyS: 'keys',\n KeyD: 'keyd',\n\n // Common keys\n Space: 'space',\n Enter: 'enter',\n Escape: 'escape',\n ShiftLeft: 'shiftleft',\n ShiftRight: 'shiftright',\n ControlLeft: 'controlleft',\n ControlRight: 'controlright',\n AltLeft: 'altleft',\n AltRight: 'altright',\n Tab: 'tab',\n\n // Numbers\n Digit0: 'digit0',\n Digit1: 'digit1',\n Digit2: 'digit2',\n Digit3: 'digit3',\n Digit4: 'digit4',\n Digit5: 'digit5',\n Digit6: 'digit6',\n Digit7: 'digit7',\n Digit8: 'digit8',\n Digit9: 'digit9',\n} as const\n","/**\n * mouse - Reactive mouse input handler\n *\n * Creates a mouse input handler with reactive signals.\n * Philosophy: No hooks, just factory functions that return signal-based state.\n */\n\nimport { SignalNode, type Signal } from '../core/signal'\n\nexport interface Vec2 {\n x: number\n y: number\n}\n\nexport interface MouseState {\n /** Current mouse position relative to target */\n readonly position: SignalNode<Vec2>\n /** Mouse position delta since last frame */\n readonly delta: SignalNode<Vec2>\n /** Check if a mouse button is pressed (0=left, 1=middle, 2=right) */\n isPressed(button: number): boolean\n /** Check if left mouse button is pressed */\n isLeftPressed(): boolean\n /** Check if right mouse button is pressed */\n isRightPressed(): boolean\n /** Check if middle mouse button is pressed */\n isMiddlePressed(): boolean\n /** Wheel delta (positive = scroll down) */\n readonly wheelDelta: SignalNode<number>\n /** Clear frame state (call at end of frame) */\n clearFrameState(): void\n /** Cleanup event listeners */\n dispose(): void\n}\n\nexport interface MouseOptions {\n /** Element to track mouse relative to (default: window) */\n target?: EventTarget\n /** Canvas element for coordinate calculation (if different from target) */\n canvas?: HTMLCanvasElement\n}\n\n/**\n * Create a mouse input handler with reactive state\n *\n * @example\n * ```tsx\n * const m = mouse()\n *\n * effect(() => {\n * console.log('Mouse at:', m.position.value)\n * if (m.isLeftPressed()) {\n * draw(m.position.value)\n * }\n * })\n * ```\n */\nexport function mouse(options: MouseOptions = {}): MouseState {\n const { target = window, canvas } = options\n\n const position = new SignalNode<Vec2>({ x: 0, y: 0 })\n const delta = new SignalNode<Vec2>({ x: 0, y: 0 })\n const wheelDelta = new SignalNode<number>(0)\n const buttons = new SignalNode<Set<number>>(new Set())\n\n let lastX = 0\n let lastY = 0\n let frameDeltaX = 0\n let frameDeltaY = 0\n let frameWheelDelta = 0\n\n function getCanvasCoordinates(event: MouseEvent): Vec2 {\n if (canvas) {\n const rect = canvas.getBoundingClientRect()\n const scaleX = canvas.width / rect.width\n const scaleY = canvas.height / rect.height\n return {\n x: (event.clientX - rect.left) * scaleX,\n y: (event.clientY - rect.top) * scaleY,\n }\n }\n return {\n x: event.clientX,\n y: event.clientY,\n }\n }\n\n function handleMouseMove(e: Event): void {\n const event = e as MouseEvent\n const coords = getCanvasCoordinates(event)\n\n frameDeltaX += coords.x - lastX\n frameDeltaY += coords.y - lastY\n lastX = coords.x\n lastY = coords.y\n\n position.set(coords)\n delta.set({ x: frameDeltaX, y: frameDeltaY })\n }\n\n function handleMouseDown(e: Event): void {\n const event = e as MouseEvent\n const newButtons = new Set(buttons.get())\n newButtons.add(event.button)\n buttons.set(newButtons)\n }\n\n function handleMouseUp(e: Event): void {\n const event = e as MouseEvent\n const newButtons = new Set(buttons.get())\n newButtons.delete(event.button)\n buttons.set(newButtons)\n }\n\n function handleWheel(e: Event): void {\n const event = e as WheelEvent\n frameWheelDelta += Math.sign(event.deltaY)\n wheelDelta.set(frameWheelDelta)\n }\n\n function handleMouseLeave(): void {\n buttons.set(new Set())\n }\n\n function handleContextMenu(_e: Event): void {\n // Prevent context menu in game contexts if needed\n // e.preventDefault()\n }\n\n // Add event listeners\n const eventTarget = canvas || target\n eventTarget.addEventListener('mousemove', handleMouseMove)\n eventTarget.addEventListener('mousedown', handleMouseDown)\n eventTarget.addEventListener('mouseup', handleMouseUp)\n eventTarget.addEventListener('wheel', handleWheel)\n eventTarget.addEventListener('mouseleave', handleMouseLeave)\n eventTarget.addEventListener('contextmenu', handleContextMenu)\n\n if (target !== window) {\n window.addEventListener('mouseup', handleMouseUp)\n }\n\n return {\n get position() {\n return position\n },\n\n get delta() {\n return delta\n },\n\n get wheelDelta() {\n return wheelDelta\n },\n\n isPressed(button: number): boolean {\n return buttons.value.has(button)\n },\n\n isLeftPressed(): boolean {\n return buttons.value.has(0)\n },\n\n isRightPressed(): boolean {\n return buttons.value.has(2)\n },\n\n isMiddlePressed(): boolean {\n return buttons.value.has(1)\n },\n\n clearFrameState(): void {\n frameDeltaX = 0\n frameDeltaY = 0\n frameWheelDelta = 0\n delta.value = { x: 0, y: 0 }\n wheelDelta.value = 0\n },\n\n dispose(): void {\n eventTarget.removeEventListener('mousemove', handleMouseMove)\n eventTarget.removeEventListener('mousedown', handleMouseDown)\n eventTarget.removeEventListener('mouseup', handleMouseUp)\n eventTarget.removeEventListener('wheel', handleWheel)\n eventTarget.removeEventListener('mouseleave', handleMouseLeave)\n eventTarget.removeEventListener('contextmenu', handleContextMenu)\n\n if (target !== window) {\n window.removeEventListener('mouseup', handleMouseUp)\n }\n },\n }\n}\n\n/** Mouse button constants */\nexport const MouseButton = {\n Left: 0,\n Middle: 1,\n Right: 2,\n} as const\n"]}