star-canvas 0.1.7 → 0.1.8

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/PROMPT.md CHANGED
@@ -44,40 +44,26 @@ Import `game` and wrap your code in it. The `game` function handles DOM readines
44
44
  ```ts
45
45
  import { game } from 'star-canvas';
46
46
 
47
- game(({ ctx, width, height, on, loop, ui, canvas }) => {
48
- // ctx: The 2D canvas context
49
- // width, height: The logical size (CSS pixels) - READ-ONLY
50
- // on: Safe, delegated event listener
51
- // loop: Stable game loop (with dt)
52
- // ui: Safe overlay for HTML
53
- // canvas: The <canvas> element
54
-
55
- // 1. Draw on the canvas
56
- loop((dt) => {
57
- ctx.clearRect(0, 0, width, height);
58
- ctx.fillStyle = '#3b82f6'; // blue-500
59
- ctx.fillRect(width / 2 - 25, height / 2 - 25, 50, 50);
60
- });
47
+ game((g) => {
48
+ const { ctx, width, height } = g;
49
+ let score = 0;
61
50
 
62
- // 2. Render HTML to the safe UI overlay
63
- ui.render(`
64
- <div class="absolute top-4 left-4 text-white">
65
- <button id="start-btn" class="px-4 py-2 bg-blue-500 rounded">
66
- Click Me
67
- </button>
68
- </div>
69
- `);
70
-
71
- // 3. Listen for button clicks — on() auto-enables pointer-events for the target
72
- on('click', '#start-btn', () => {
73
- console.log('Button clicked!');
74
- });
51
+ // Game loop input, update, draw
52
+ g.loop((dt) => {
53
+ // Input: check g.tap each frame (null if no tap)
54
+ if (g.tap) {
55
+ score++;
56
+ }
75
57
 
76
- // 4. For canvas games: listen for taps on canvas
77
- // Taps pass through the UI overlay to the canvas layer
78
- // Elements targeted by on() are automatically interactive
79
- canvas.addEventListener('pointerdown', (e) => {
80
- console.log('Canvas/screen tapped!', e);
58
+ // Draw
59
+ ctx.fillStyle = '#0f172a';
60
+ ctx.fillRect(0, 0, width, height);
61
+ ctx.fillStyle = '#3b82f6';
62
+ ctx.font = 'bold 32px sans-serif';
63
+ ctx.textAlign = 'center';
64
+ ctx.fillText(`Score: ${score}`, width / 2, height / 2);
65
+ ctx.fillText('TAP ANYWHERE', width / 2, height / 2 + 40);
66
+ ctx.textAlign = 'left';
81
67
  });
82
68
  });
83
69
  ```
@@ -105,7 +91,8 @@ The 2D drawing context. Its transform is already scaled for DPR. You **always dr
105
91
 
106
92
  The `<canvas>` element itself.
107
93
 
108
- - **Use this for gameplay input listeners** (e.g., `pointerdown`, `pointermove`).
94
+ - For drawing, use `ctx` (the 2D context).
95
+ - For input, use `g.tap` / `g.pointer` / `g.released` in the game loop (see Input Polling below).
109
96
 
110
97
  ### `width: number` (getter)
111
98
 
@@ -140,7 +127,60 @@ A safe manager for your HTML overlay, stacked on top of the canvas.
140
127
  - `ui.el(selector)`: Scoped `querySelector` for the UI root.
141
128
  - `ui.all(selector)`: Scoped `querySelectorAll` for the UI root.
142
129
 
143
- **Auto-detection:** When you add `canvas.addEventListener('pointerdown', ...)`, the SDK automatically makes UI click-through so taps reach the canvas. Elements targeted by `on()` are automatically interactive — no extra CSS classes needed. Native `<button>` and `<a>` elements are also always interactive.
130
+ ### Input Polling: `tap`, `pointer`, `released`
131
+
132
+ **The standard way to handle input.** Read these in your game loop — no event handlers needed.
133
+
134
+ ```ts
135
+ g.loop((dt) => {
136
+ if (g.tap) {
137
+ // Tap/click happened this frame. g.tap.x, g.tap.y are canvas-space.
138
+ }
139
+ if (g.pointer.down) {
140
+ // Pointer is held. g.pointer.x, g.pointer.y track current position.
141
+ }
142
+ if (g.released) {
143
+ // Pointer released this frame.
144
+ }
145
+ });
146
+ ```
147
+
148
+ - **`g.tap`** — `{ x, y, time } | null`. Non-null on the frame a pointerdown occurs. Cleared next frame.
149
+ - **`g.pointer`** — `{ x, y, down }`. Always available. Tracks current pointer position and held state.
150
+ - **`g.released`** — `{ x, y, time } | null`. Non-null on the frame a pointerup occurs. Cleared next frame.
151
+ - **`g.taps`** — `Array<{ x, y, id, time }>`. All taps this frame (multi-touch).
152
+ - **`g.pointers`** — `Array<{ x, y, id, down }>`. All active pointers (multi-touch).
153
+
154
+ **Why polling?** One code path with if/else for priority. No conflicting event handlers, no registration order bugs. This is how Unity, Godot, and every game engine handles input.
155
+
156
+ **Button priority pattern:**
157
+ ```ts
158
+ if (g.tap) {
159
+ if (state === 'gameover' && inRect(g.tap, leaderboardBtn)) {
160
+ leaderboard.show(); // Checked first — highest priority
161
+ } else if (state === 'gameover') {
162
+ startGame(); // Generic tap — lower priority
163
+ } else if (state === 'playing') {
164
+ jump();
165
+ }
166
+ }
167
+ ```
168
+
169
+ **Hold-to-fire with cooldown:**
170
+ ```ts
171
+ let fireCooldown = 0;
172
+ const FIRE_RATE = 0.15; // seconds between shots
173
+
174
+ g.loop((dt) => {
175
+ fireCooldown -= dt;
176
+ if (g.pointer.down && fireCooldown <= 0) {
177
+ spawnBullet();
178
+ fireCooldown = FIRE_RATE; // Slow shotgun: 0.8, fast minigun: 0.05
179
+ }
180
+ });
181
+ ```
182
+
183
+ Taps on interactive UI elements (from `on()` or native `<button>`) are automatically suppressed from `g.tap`.
144
184
 
145
185
  ### Cursor Management
146
186
 
@@ -285,7 +325,8 @@ For puzzle games, card games, match-3, mobile-style games - use portrait preset.
285
325
  ```ts
286
326
  import { game } from 'star-canvas';
287
327
 
288
- game(({ ctx, width, height, loop, canvas, toStagePoint }) => {
328
+ game((g) => {
329
+ const { ctx, width, height } = g;
289
330
  // width = 360, height = 640 (always, with letterboxing)
290
331
  const cellSize = 40;
291
332
  const gridCols = 8;
@@ -296,12 +337,17 @@ game(({ ctx, width, height, loop, canvas, toStagePoint }) => {
296
337
  const gridX = (width - gridWidth) / 2;
297
338
  const gridY = 80;
298
339
 
299
- canvas.addEventListener('pointerdown', (e) => {
300
- const { x, y } = toStagePoint(e);
301
- // Handle tap on grid...
302
- });
340
+ g.loop((dt) => {
341
+ // Input
342
+ if (g.tap) {
343
+ const col = Math.floor((g.tap.x - gridX) / cellSize);
344
+ const row = Math.floor((g.tap.y - gridY) / cellSize);
345
+ if (col >= 0 && col < gridCols && row >= 0 && row < gridRows) {
346
+ // Handle tap on grid cell...
347
+ }
348
+ }
303
349
 
304
- loop((dt) => {
350
+ // Draw
305
351
  ctx.fillStyle = '#111827';
306
352
  ctx.fillRect(0, 0, width, height);
307
353
 
@@ -326,16 +372,19 @@ For games that need different dimensions (e.g., pixel art at 320×180).
326
372
  ```ts
327
373
  import { game } from 'star-canvas';
328
374
 
329
- game(({ ctx, width, height, loop, toStagePoint, canvas }) => {
375
+ game((g) => {
376
+ const { ctx, width, height } = g;
330
377
  // Custom 320×180 resolution (retro pixel art style)
331
378
  const player = { x: 160, y: 90 }; // Center
332
379
 
333
- canvas.addEventListener('pointerdown', (e) => {
334
- const { x, y } = toStagePoint(e);
335
- console.log('Tapped at:', x, y); // Always 0-320, 0-180
336
- });
380
+ g.loop((dt) => {
381
+ // Input g.tap coords are already in canvas-space (0-320, 0-180)
382
+ if (g.tap) {
383
+ player.x = g.tap.x;
384
+ player.y = g.tap.y;
385
+ }
337
386
 
338
- loop((dt) => {
387
+ // Draw
339
388
  ctx.fillStyle = '#111827';
340
389
  ctx.fillRect(0, 0, width, height);
341
390
 
@@ -345,7 +394,9 @@ game(({ ctx, width, height, loop, toStagePoint, canvas }) => {
345
394
  }, { width: 320, height: 180 }); // Custom resolution with letterboxing
346
395
  ```
347
396
 
348
- ### Recipe 5: Complex Game with Canvas + UI + Events (like FLOW)
397
+ ### Recipe 5: Complex Game with Canvas + Leaderboard
398
+
399
+ **Key pattern:** All input via `g.tap` in the loop. if/else for priority — check buttons first.
349
400
 
350
401
  ```ts
351
402
  import { game } from 'star-canvas';
@@ -353,78 +404,75 @@ import { createLeaderboard } from 'star-leaderboard';
353
404
 
354
405
  const leaderboard = createLeaderboard({ gameId: '<gameId from .starrc>' });
355
406
 
356
- game(({ ctx, width, height, loop, ui, on, canvas, toStagePoint }) => {
407
+ game((g) => {
408
+ const { ctx, width, height } = g;
357
409
  let score = 0;
358
410
  let state = 'menu';
359
411
 
360
- function handleTap() {
361
- if (state === 'menu' || state === 'gameover') {
362
- startGame();
363
- } else if (state === 'playing') {
364
- // ... (player float logic) ...
365
- }
412
+ const lbBtn = { x: width / 2 - 120, y: height / 2 + 20, w: 240, h: 50 };
413
+ const restartBtn = { x: width / 2 - 120, y: height / 2 + 90, w: 240, h: 50 };
414
+
415
+ function inRect(pt, r) {
416
+ return pt.x >= r.x && pt.x <= r.x + r.w && pt.y >= r.y && pt.y <= r.y + r.h;
366
417
  }
367
418
 
368
- // 1. Listen for screen taps - this makes UI click-through automatically
369
- canvas.addEventListener('pointerdown', handleTap);
419
+ function drawButton(text, r, color) {
420
+ ctx.fillStyle = color || '#7c3aed';
421
+ ctx.fillRect(r.x, r.y, r.w, r.h);
422
+ ctx.fillStyle = '#fff';
423
+ ctx.font = 'bold 16px sans-serif';
424
+ ctx.textAlign = 'center';
425
+ ctx.fillText(text, r.x + r.w / 2, r.y + r.h / 2 + 6);
426
+ }
370
427
 
371
- // 2. Listen for button clicks on() auto-enables pointer-events for the selector
372
- on('click', '#leaderboard-btn', (e) => {
373
- e.stopPropagation();
374
- leaderboard.show();
375
- });
428
+ function startGame() { state = 'playing'; score = 0; }
429
+
430
+ function endGame() {
431
+ state = 'gameover';
432
+ leaderboard.submit(score);
433
+ }
376
434
 
377
- // 3. Render UI — elements targeted by on() are automatically interactive
378
- let lastState = null;
379
- let lastScore = -1;
435
+ g.loop((dt) => {
436
+ // --- Input (polling) ---
437
+ if (g.tap) {
438
+ if (state === 'gameover') {
439
+ if (inRect(g.tap, lbBtn)) leaderboard.show();
440
+ else if (inRect(g.tap, restartBtn)) startGame();
441
+ } else if (state === 'menu') {
442
+ startGame();
443
+ } else if (state === 'playing') {
444
+ // ... (player jump/action logic) ...
445
+ }
446
+ }
380
447
 
381
- function updateUI() {
382
- // CRITICAL: Only render when state/score changes, NOT every frame
383
- // Calling ui.render() in the loop breaks buttons (DOM recreation)
384
- if (state === lastState && score === lastScore) return;
385
- lastState = state;
386
- lastScore = score;
448
+ // --- Draw ---
449
+ ctx.fillStyle = '#111827';
450
+ ctx.fillRect(0, 0, width, height);
387
451
 
388
452
  if (state === 'menu') {
389
- ui.render(`
390
- <div class="h-full flex flex-col items-center justify-center text-white">
391
- <h1 class="text-6xl font-bold mb-4">FLOW</h1>
392
- <div class="text-2xl animate-pulse">TAP TO START</div>
393
- </div>`);
453
+ ctx.fillStyle = '#fff';
454
+ ctx.font = 'bold 48px sans-serif';
455
+ ctx.textAlign = 'center';
456
+ ctx.fillText('FLOW', width / 2, height / 2 - 20);
457
+ ctx.font = '24px sans-serif';
458
+ ctx.fillText('TAP TO START', width / 2, height / 2 + 30);
394
459
  } else if (state === 'playing') {
395
- ui.render(`
396
- <div class="absolute top-8 left-1/2 -translate-x-1/2 text-white">
397
- <div class="text-5xl font-bold">\${score}</div>
398
- </div>`);
460
+ ctx.fillStyle = '#fff';
461
+ ctx.font = 'bold 48px sans-serif';
462
+ ctx.textAlign = 'center';
463
+ ctx.fillText(String(score), width / 2, 60);
399
464
  } else if (state === 'gameover') {
400
- ui.render(`
401
- <div class="h-full flex flex-col items-center justify-center text-white">
402
- <div class="text-3xl mb-4">GAME OVER</div>
403
- <div class="text-6xl mb-4">\${score}</div>
404
- <button id="leaderboard-btn" class="px-6 py-3 mb-4 bg-gradient-to-r from-blue-600 to-purple-600 rounded-xl font-bold shadow-lg shadow-blue-500/20">
405
- VIEW LEADERBOARD
406
- </button>
407
- <div class="text-xl animate-pulse">TAP TO RESTART</div>
408
- </div>`);
465
+ ctx.fillStyle = '#fff';
466
+ ctx.font = '24px sans-serif';
467
+ ctx.textAlign = 'center';
468
+ ctx.fillText('GAME OVER', width / 2, height / 2 - 60);
469
+ ctx.font = 'bold 48px sans-serif';
470
+ ctx.fillText(String(score), width / 2, height / 2 - 10);
471
+ drawButton('VIEW LEADERBOARD', lbBtn, '#7c3aed');
472
+ drawButton('PLAY AGAIN', restartBtn, '#374151');
409
473
  }
410
- }
411
-
412
- // 4. Call updateUI when state changes (NOT every frame)
413
- updateUI();
414
-
415
- // Update when state transitions happen
416
- function startGame() {
417
- state = 'playing';
418
- score = 0;
419
- updateUI();
420
- }
421
-
422
- function endGame() {
423
- state = 'gameover';
424
- // Submit score to leaderboard
425
- leaderboard.submit(score);
426
- updateUI();
427
- }
474
+ ctx.textAlign = 'left';
475
+ });
428
476
  });
429
477
  ```
430
478
 
package/dist/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";var A=Object.defineProperty;var V=Object.getOwnPropertyDescriptor;var J=Object.getOwnPropertyNames;var K=Object.prototype.hasOwnProperty;var Q=(a,r)=>{for(var s in r)A(a,s,{get:r[s],enumerable:!0})},Z=(a,r,s,t)=>{if(r&&typeof r=="object"||typeof r=="function")for(let l of J(r))!K.call(a,l)&&l!==s&&A(a,l,{get:()=>r[l],enumerable:!(t=V(r,l))||t.enumerable});return a};var ee=a=>Z(A({},"__esModule",{value:!0}),a);var re={};Q(re,{createDragState:()=>q,game:()=>oe,version:()=>te});module.exports=ee(re);var te="0.8.0",Y={landscape:{width:640,height:360},portrait:{width:360,height:640},responsive:{}};function q(a){let r=null,s=0,t=0;return{point(l){return a(l)},grab(l,c){let{x:b,y:T}=a(l);r=c,s=b-c.x,t=T-c.y},move(l){if(r){let{x:c,y:b}=a(l);r.x=c-s,r.y=b-t}},release(){let l=r;return r=null,l},get dragging(){return r}}}function F(){return typeof window<"u"&&typeof document<"u"}function ne(){if(!F()||document.getElementById("star-canvas-base"))return;let a=document.createElement("style");a.id="star-canvas-base",a.textContent=`
1
+ "use strict";var P=Object.defineProperty;var ee=Object.getOwnPropertyDescriptor;var te=Object.getOwnPropertyNames;var ne=Object.prototype.hasOwnProperty;var re=(a,o)=>{for(var s in o)P(a,s,{get:o[s],enumerable:!0})},oe=(a,o,s,n)=>{if(o&&typeof o=="object"||typeof o=="function")for(let l of te(o))!ne.call(a,l)&&l!==s&&P(a,l,{get:()=>o[l],enumerable:!(n=ee(o,l))||n.enumerable});return a};var ie=a=>oe(P({},"__esModule",{value:!0}),a);var de={};re(de,{createDragState:()=>U,game:()=>le,version:()=>ae});module.exports=ie(de);var ae="0.8.0",W={landscape:{width:640,height:360},portrait:{width:360,height:640},responsive:{}};function U(a){let o=null,s=0,n=0;return{point(l){return a(l)},grab(l,d){let{x,y:I}=a(l);o=d,s=x-d.x,n=I-d.y},move(l){if(o){let{x:d,y:x}=a(l);o.x=d-s,o.y=x-n}},release(){let l=o;return o=null,l},get dragging(){return o}}}function j(){return typeof window<"u"&&typeof document<"u"}function se(){if(!j()||document.getElementById("star-canvas-base"))return;let a=document.createElement("style");a.id="star-canvas-base",a.textContent=`
2
2
  html, body { height: 100%; }
3
3
  body {
4
4
  margin: 0; min-height: 100dvh; overflow: hidden; background: #000;
@@ -29,4 +29,4 @@
29
29
  .star-ui textarea, .star-ui [data-interactive] {
30
30
  pointer-events: auto;
31
31
  }
32
- `,document.head.appendChild(a)}function oe(a,r={}){F()&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>$(a,r),{once:!0}):queueMicrotask(()=>$(a,r)))}function $(a,r){if(ne(),window.__STAR_DOM__?.destroy)try{window.__STAR_DOM__.destroy()}catch{}document.querySelectorAll(".star-ui, .star-canvas, .star-input").forEach(e=>e.remove());let s=document.createElement("div");s.className="star-ui",document.body.appendChild(s);let t=document.createElement("canvas");t.className="star-canvas",document.body.appendChild(t);let l=t.getContext("2d",r.contextAttributes??{alpha:!0});if(!l)throw new Error("[star-canvas] Failed to get 2D context");if(r.preventContextMenu!==!1){let e=n=>n.preventDefault();t.addEventListener("contextmenu",e)}let c=document.createElement("div");c.className="star-input",document.body.appendChild(c);let b=[],T=[],O=[],R=null,w=null;Object.defineProperty(t,"onclick",{get:()=>R,set:e=>{w&&(c.removeEventListener("click",w),w=null),R=e,e&&(w=n=>{S(n)||e.call(t,n)},c.addEventListener("click",w))}});let N=t.addEventListener.bind(t),W=t.removeEventListener.bind(t),I=new WeakMap;function S(e){let n=e;if(n.clientX==null||n.clientY==null)return!1;let o=document.elementFromPoint(n.clientX,n.clientY);return o!==null&&o!==s&&s.contains(o)}let B=/^(click|pointerdown|mousedown|touchstart)$/;t.addEventListener=function(e,n,o){if(/^(click|pointer|mouse|touch)/.test(e)&&typeof n=="function"){let i=B.test(e),p=(d=>{i&&S(d)||n.call(t,d)});I.set(n,p),c.addEventListener(e,p,o)}else N(e,n,o)},t.removeEventListener=function(e,n,o){if(/^(click|pointer|mouse|touch)/.test(e)&&typeof n=="function"){let i=I.get(n);i&&(c.removeEventListener(e,i,o),I.delete(n))}else W(e,n,o)};let U=r.preset??"landscape",D=Y[U]??Y.landscape,L=r.width??D.width,M=r.height??D.height,k=r.maxPixelRatio??2,P=r.pixelRatio??"device",H=r.fit??"contain",x=1,u=L??1,m=M??1,G=new Set,C=null,g=[];function j(){return Math.min(typeof P=="number"?Math.max(1,P):Math.max(1,window.devicePixelRatio||1),k)}function y(){x=j();let e=document.body.getBoundingClientRect(),n=Math.max(1,Math.floor(e.width||window.innerWidth||800)),o=Math.max(1,Math.floor(e.height||window.innerHeight||600));if(L&&M){u=L,m=M;let d=u/m,v=n/o,h=1;if(H==="contain"?h=v>d?o/m:n/u:H==="cover"&&(h=v>d?n/u:o/m),H==="stretch")t.style.width="100%",t.style.height="100%";else{let f=Math.floor(u*h),X=Math.floor(m*h);t.style.width=`${f}px`,t.style.height=`${X}px`,t.style.position="absolute",t.style.left=`${Math.floor((n-f)/2)}px`,t.style.top=`${Math.floor((o-X)/2)}px`}}else if(M){m=M;let d=n/o;u=Math.floor(m*d),t.style.width="100%",t.style.height="100%",t.style.position="absolute",t.style.left="0",t.style.top="0"}else if(L){u=L;let d=n/o;m=Math.floor(u/d),t.style.width="100%",t.style.height="100%",t.style.position="absolute",t.style.left="0",t.style.top="0"}else u=n,m=o,t.style.width=`${u}px`,t.style.height=`${m}px`,t.style.position="absolute",t.style.left="0",t.style.top="0";let i=Math.max(1,Math.floor(u*x)),p=Math.max(1,Math.floor(m*x));t.width!==i&&(t.width=i),t.height!==p&&(t.height=p),l.setTransform(x,0,0,x,0,0),G.forEach(d=>d())}let z=new ResizeObserver(y);z.observe(document.body),g.push(()=>z.disconnect()),window.addEventListener("resize",y),g.push(()=>window.removeEventListener("resize",y));let _=window.visualViewport;_&&(_.addEventListener("resize",y),g.push(()=>_.removeEventListener("resize",y)));let E={stage:document.body,canvas:t,ctx:l,get width(){return u},get height(){return m},get dpr(){return x},resize:y,toStagePoint:e=>{let n=t.getBoundingClientRect(),o=(e.clientX-n.left)*(u/n.width),i=(e.clientY-n.top)*(m/n.height);return o=Math.max(0,Math.min(u,o)),i=Math.max(0,Math.min(m,i)),{x:o,y:i}},createDrag:()=>q(E.toStagePoint),onTap:e=>{b.push(e)},onMove:e=>{T.push(e)},onRelease:e=>{O.push(e)},on:(e,n,o,i)=>{if(/^(click|pointer|mouse|touch)/.test(e))try{let v=n.split(",").map(f=>`.star-ui ${f.trim()}`).join(", "),h=document.createElement("style");h.textContent=`${v} { pointer-events: auto; }`,document.head.appendChild(h),g.push(()=>h.remove())}catch{}let p=v=>{let f=v.target?.closest?.(n);f&&o.call(f,v)};document.addEventListener(e,p,i);let d=()=>document.removeEventListener(e,p,i);return g.push(d),d},loop:e=>{let o=0,i=!1,p=0,d=v=>{if(!i)return;let h=p?Math.min((v-p)/1e3,.1):0;p=v;try{e(h,v)}catch(f){console.error("[star-canvas] Game loop error:",f)}o=requestAnimationFrame(d)};return C={get running(){return i},start(){i||(i=!0,p=performance.now(),o=requestAnimationFrame(d))},stop(){i&&(i=!1,cancelAnimationFrame(o))}},C.start(),C},ui:{root:s,render:e=>{s.innerHTML!==e&&(s.innerHTML=e)},el:e=>s.querySelector(e),all:e=>s.querySelectorAll(e)},destroy:()=>{C?.stop(),g.forEach(e=>e()),g=[],G.clear(),t.parentElement&&t.parentElement.removeChild(t),s.parentElement&&s.parentElement.removeChild(s),c.parentElement&&c.parentElement.removeChild(c)},scoped:e=>{l.save();try{e()}finally{l.restore()}}};c.addEventListener("pointerdown",e=>{if(S(e))return;let n={...E.toStagePoint(e),event:e};b.forEach(o=>o(n))}),c.addEventListener("pointermove",e=>{let n={...E.toStagePoint(e),event:e};T.forEach(o=>o(n))}),c.addEventListener("pointerup",e=>{let n={...E.toStagePoint(e),event:e};O.forEach(o=>o(n))}),window.__STAR_DOM__={destroy:E.destroy},requestAnimationFrame(()=>requestAnimationFrame(()=>{y();try{a(E)}catch(e){console.error("[star-canvas] Game initialization failed:",e)}}))}0&&(module.exports={createDragState,game,version});
32
+ `,document.head.appendChild(a)}function le(a,o={}){j()&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>B(a,o),{once:!0}):queueMicrotask(()=>B(a,o)))}function B(a,o){if(se(),window.__STAR_DOM__?.destroy)try{window.__STAR_DOM__.destroy()}catch{}document.querySelectorAll(".star-ui, .star-canvas, .star-input").forEach(e=>e.remove());let s=document.createElement("div");s.className="star-ui",document.body.appendChild(s);let n=document.createElement("canvas");n.className="star-canvas",document.body.appendChild(n);let l=n.getContext("2d",o.contextAttributes??{alpha:!0});if(!l)throw new Error("[star-canvas] Failed to get 2D context");if(o.preventContextMenu!==!1){let e=t=>t.preventDefault();n.addEventListener("contextmenu",e)}let d=document.createElement("div");d.className="star-input",document.body.appendChild(d);let x=[],I=[],G=[],A=null,w={x:0,y:0,down:!1},S=null,R=[],L=new Map,z=null,M=null;Object.defineProperty(n,"onclick",{get:()=>z,set:e=>{M&&(d.removeEventListener("click",M),M=null),z=e,e&&(M=t=>{O(t)||e.call(n,t)},d.addEventListener("click",M))}});let V=n.addEventListener.bind(n),J=n.removeEventListener.bind(n),H=new WeakMap;function O(e){let t=e;if(t.clientX==null||t.clientY==null)return!1;let r=document.elementFromPoint(t.clientX,t.clientY);return r!==null&&r!==s&&s.contains(r)}let K=/^(click|pointerdown|mousedown|touchstart)$/;n.addEventListener=function(e,t,r){if(/^(click|pointer|mouse|touch)/.test(e)&&typeof t=="function"){let i=K.test(e),c=(u=>{i&&O(u)||t.call(n,u)});H.set(t,c),d.addEventListener(e,c,r)}else V(e,t,r)},n.removeEventListener=function(e,t,r){if(/^(click|pointer|mouse|touch)/.test(e)&&typeof t=="function"){let i=H.get(t);i&&(d.removeEventListener(e,i,r),H.delete(t))}else J(e,t,r)};let Q=o.preset??"landscape",X=W[Q]??W.landscape,T=o.width??X.width,C=o.height??X.height,Y=o.maxPixelRatio??2,$=o.pixelRatio??"device",D=o.fit??"contain",g=1,m=T??1,p=C??1,q=new Set,_=null,b=[];function Z(){return Math.min(typeof $=="number"?Math.max(1,$):Math.max(1,window.devicePixelRatio||1),Y)}function f(){g=Z();let e=document.body.getBoundingClientRect(),t=Math.max(1,Math.floor(e.width||window.innerWidth||800)),r=Math.max(1,Math.floor(e.height||window.innerHeight||600));if(T&&C){m=T,p=C;let u=m/p,v=t/r,h=1;if(D==="contain"?h=v>u?r/p:t/m:D==="cover"&&(h=v>u?t/m:r/p),D==="stretch")n.style.width="100%",n.style.height="100%";else{let y=Math.floor(m*h),N=Math.floor(p*h);n.style.width=`${y}px`,n.style.height=`${N}px`,n.style.position="absolute",n.style.left=`${Math.floor((t-y)/2)}px`,n.style.top=`${Math.floor((r-N)/2)}px`}}else if(C){p=C;let u=t/r;m=Math.floor(p*u),n.style.width="100%",n.style.height="100%",n.style.position="absolute",n.style.left="0",n.style.top="0"}else if(T){m=T;let u=t/r;p=Math.floor(m/u),n.style.width="100%",n.style.height="100%",n.style.position="absolute",n.style.left="0",n.style.top="0"}else m=t,p=r,n.style.width=`${m}px`,n.style.height=`${p}px`,n.style.position="absolute",n.style.left="0",n.style.top="0";let i=Math.max(1,Math.floor(m*g)),c=Math.max(1,Math.floor(p*g));n.width!==i&&(n.width=i),n.height!==c&&(n.height=c),l.setTransform(g,0,0,g,0,0),q.forEach(u=>u())}let F=new ResizeObserver(f);F.observe(document.body),b.push(()=>F.disconnect()),window.addEventListener("resize",f),b.push(()=>window.removeEventListener("resize",f));let k=window.visualViewport;k&&(k.addEventListener("resize",f),b.push(()=>k.removeEventListener("resize",f)));let E={stage:document.body,canvas:n,ctx:l,get width(){return m},get height(){return p},get dpr(){return g},resize:f,toStagePoint:e=>{let t=n.getBoundingClientRect(),r=(e.clientX-t.left)*(m/t.width),i=(e.clientY-t.top)*(p/t.height);return r=Math.max(0,Math.min(m,r)),i=Math.max(0,Math.min(p,i)),{x:r,y:i}},createDrag:()=>U(E.toStagePoint),onTap:e=>{x.push(e)},onMove:e=>{I.push(e)},onRelease:e=>{G.push(e)},get tap(){return A},get pointer(){return w},get released(){return S},get taps(){return R},get pointers(){return Array.from(L.values())},on:(e,t,r,i)=>{if(/^(click|pointer|mouse|touch)/.test(e))try{let v=t.split(",").map(y=>`.star-ui ${y.trim()}`).join(", "),h=document.createElement("style");h.textContent=`${v} { pointer-events: auto; }`,document.head.appendChild(h),b.push(()=>h.remove())}catch{}let c=v=>{let y=v.target?.closest?.(t);y&&r.call(y,v)};document.addEventListener(e,c,i);let u=()=>document.removeEventListener(e,c,i);return b.push(u),u},loop:e=>{let r=0,i=!1,c=0,u=v=>{if(!i)return;let h=c?Math.min((v-c)/1e3,.1):0;c=v;try{e(h,v)}catch(y){console.error("[star-canvas] Game loop error:",y)}A=null,S=null,R=[],r=requestAnimationFrame(u)};return _={get running(){return i},start(){i||(i=!0,c=performance.now(),r=requestAnimationFrame(u))},stop(){i&&(i=!1,cancelAnimationFrame(r))}},_.start(),_},ui:{root:s,render:e=>{s.innerHTML!==e&&(s.innerHTML=e)},el:e=>s.querySelector(e),all:e=>s.querySelectorAll(e)},destroy:()=>{_?.stop(),b.forEach(e=>e()),b=[],q.clear(),n.parentElement&&n.parentElement.removeChild(n),s.parentElement&&s.parentElement.removeChild(s),d.parentElement&&d.parentElement.removeChild(d)},scoped:e=>{l.save();try{e()}finally{l.restore()}}};d.addEventListener("pointerdown",e=>{let t=E.toStagePoint(e);if(w={x:t.x,y:t.y,down:!0},L.set(e.pointerId,{x:t.x,y:t.y,id:e.pointerId,down:!0}),O(e))return;A={x:t.x,y:t.y,time:e.timeStamp},R.push({x:t.x,y:t.y,id:e.pointerId,time:e.timeStamp});let r={...t,event:e};x.forEach(i=>i(r))}),d.addEventListener("pointermove",e=>{let t=E.toStagePoint(e);w={x:t.x,y:t.y,down:w.down};let r=L.get(e.pointerId);r&&L.set(e.pointerId,{...r,x:t.x,y:t.y});let i={...t,event:e};I.forEach(c=>c(i))}),d.addEventListener("pointerup",e=>{let t=E.toStagePoint(e);w={x:t.x,y:t.y,down:!1},S={x:t.x,y:t.y,time:e.timeStamp},L.delete(e.pointerId);let r={...t,event:e};G.forEach(i=>i(r))}),window.__STAR_DOM__={destroy:E.destroy},requestAnimationFrame(()=>requestAnimationFrame(()=>{f();try{a(E)}catch(e){console.error("[star-canvas] Game initialization failed:",e)}}))}0&&(module.exports={createDragState,game,version});
package/dist/index.d.cts CHANGED
@@ -83,6 +83,38 @@ interface GameContext {
83
83
  onMove: (handler: InputHandler) => void;
84
84
  /** Register handler for pointer release (fires on pointerup). Works anywhere on screen including letterbox. */
85
85
  onRelease: (handler: InputHandler) => void;
86
+ /** Tap this frame (pointerdown). Null if none. Canvas-space coords. Read in your game loop. */
87
+ readonly tap: {
88
+ x: number;
89
+ y: number;
90
+ time: number;
91
+ } | null;
92
+ /** Current primary pointer state. Always available, updated on every move. */
93
+ readonly pointer: {
94
+ x: number;
95
+ y: number;
96
+ down: boolean;
97
+ };
98
+ /** Release this frame (pointerup). Null if none. Canvas-space coords. */
99
+ readonly released: {
100
+ x: number;
101
+ y: number;
102
+ time: number;
103
+ } | null;
104
+ /** All new taps this frame (multi-touch). Empty array if none. */
105
+ readonly taps: ReadonlyArray<{
106
+ x: number;
107
+ y: number;
108
+ id: number;
109
+ time: number;
110
+ }>;
111
+ /** All active pointers (multi-touch). Empty array if none. */
112
+ readonly pointers: ReadonlyArray<{
113
+ x: number;
114
+ y: number;
115
+ id: number;
116
+ down: boolean;
117
+ }>;
86
118
  /** Re-calculates stage size (rarely needed). */
87
119
  resize: () => void;
88
120
  /** Cleans up all listeners and observers. */
package/dist/index.d.ts CHANGED
@@ -83,6 +83,38 @@ interface GameContext {
83
83
  onMove: (handler: InputHandler) => void;
84
84
  /** Register handler for pointer release (fires on pointerup). Works anywhere on screen including letterbox. */
85
85
  onRelease: (handler: InputHandler) => void;
86
+ /** Tap this frame (pointerdown). Null if none. Canvas-space coords. Read in your game loop. */
87
+ readonly tap: {
88
+ x: number;
89
+ y: number;
90
+ time: number;
91
+ } | null;
92
+ /** Current primary pointer state. Always available, updated on every move. */
93
+ readonly pointer: {
94
+ x: number;
95
+ y: number;
96
+ down: boolean;
97
+ };
98
+ /** Release this frame (pointerup). Null if none. Canvas-space coords. */
99
+ readonly released: {
100
+ x: number;
101
+ y: number;
102
+ time: number;
103
+ } | null;
104
+ /** All new taps this frame (multi-touch). Empty array if none. */
105
+ readonly taps: ReadonlyArray<{
106
+ x: number;
107
+ y: number;
108
+ id: number;
109
+ time: number;
110
+ }>;
111
+ /** All active pointers (multi-touch). Empty array if none. */
112
+ readonly pointers: ReadonlyArray<{
113
+ x: number;
114
+ y: number;
115
+ id: number;
116
+ down: boolean;
117
+ }>;
86
118
  /** Re-calculates stage size (rarely needed). */
87
119
  resize: () => void;
88
120
  /** Cleans up all listeners and observers. */
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- var V="0.8.0",X={landscape:{width:640,height:360},portrait:{width:360,height:640},responsive:{}};function U(p){let i=null,l=0,t=0;return{point(u){return p(u)},grab(u,a){let{x:b,y:T}=p(u);i=a,l=b-a.x,t=T-a.y},move(u){if(i){let{x:a,y:b}=p(u);i.x=a-l,i.y=b-t}},release(){let u=i;return i=null,u},get dragging(){return i}}}function $(){return typeof window<"u"&&typeof document<"u"}function j(){if(!$()||document.getElementById("star-canvas-base"))return;let p=document.createElement("style");p.id="star-canvas-base",p.textContent=`
1
+ var ee="0.8.0",N={landscape:{width:640,height:360},portrait:{width:360,height:640},responsive:{}};function Q(p){let i=null,l=0,n=0;return{point(m){return p(m)},grab(m,a){let{x,y:I}=p(m);i=a,l=x-a.x,n=I-a.y},move(m){if(i){let{x:a,y:x}=p(m);i.x=a-l,i.y=x-n}},release(){let m=i;return i=null,m},get dragging(){return i}}}function B(){return typeof window<"u"&&typeof document<"u"}function Z(){if(!B()||document.getElementById("star-canvas-base"))return;let p=document.createElement("style");p.id="star-canvas-base",p.textContent=`
2
2
  html, body { height: 100%; }
3
3
  body {
4
4
  margin: 0; min-height: 100dvh; overflow: hidden; background: #000;
@@ -29,4 +29,4 @@ var V="0.8.0",X={landscape:{width:640,height:360},portrait:{width:360,height:640
29
29
  .star-ui textarea, .star-ui [data-interactive] {
30
30
  pointer-events: auto;
31
31
  }
32
- `,document.head.appendChild(p)}function J(p,i={}){$()&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>Y(p,i),{once:!0}):queueMicrotask(()=>Y(p,i)))}function Y(p,i){if(j(),window.__STAR_DOM__?.destroy)try{window.__STAR_DOM__.destroy()}catch{}document.querySelectorAll(".star-ui, .star-canvas, .star-input").forEach(e=>e.remove());let l=document.createElement("div");l.className="star-ui",document.body.appendChild(l);let t=document.createElement("canvas");t.className="star-canvas",document.body.appendChild(t);let u=t.getContext("2d",i.contextAttributes??{alpha:!0});if(!u)throw new Error("[star-canvas] Failed to get 2D context");if(i.preventContextMenu!==!1){let e=n=>n.preventDefault();t.addEventListener("contextmenu",e)}let a=document.createElement("div");a.className="star-input",document.body.appendChild(a);let b=[],T=[],A=[],O=null,w=null;Object.defineProperty(t,"onclick",{get:()=>O,set:e=>{w&&(a.removeEventListener("click",w),w=null),O=e,e&&(w=n=>{S(n)||e.call(t,n)},a.addEventListener("click",w))}});let q=t.addEventListener.bind(t),F=t.removeEventListener.bind(t),I=new WeakMap;function S(e){let n=e;if(n.clientX==null||n.clientY==null)return!1;let o=document.elementFromPoint(n.clientX,n.clientY);return o!==null&&o!==l&&l.contains(o)}let N=/^(click|pointerdown|mousedown|touchstart)$/;t.addEventListener=function(e,n,o){if(/^(click|pointer|mouse|touch)/.test(e)&&typeof n=="function"){let r=N.test(e),m=(s=>{r&&S(s)||n.call(t,s)});I.set(n,m),a.addEventListener(e,m,o)}else q(e,n,o)},t.removeEventListener=function(e,n,o){if(/^(click|pointer|mouse|touch)/.test(e)&&typeof n=="function"){let r=I.get(n);r&&(a.removeEventListener(e,r,o),I.delete(n))}else F(e,n,o)};let W=i.preset??"landscape",R=X[W]??X.landscape,L=i.width??R.width,M=i.height??R.height,D=i.maxPixelRatio??2,k=i.pixelRatio??"device",H=i.fit??"contain",x=1,c=L??1,d=M??1,P=new Set,C=null,g=[];function B(){return Math.min(typeof k=="number"?Math.max(1,k):Math.max(1,window.devicePixelRatio||1),D)}function y(){x=B();let e=document.body.getBoundingClientRect(),n=Math.max(1,Math.floor(e.width||window.innerWidth||800)),o=Math.max(1,Math.floor(e.height||window.innerHeight||600));if(L&&M){c=L,d=M;let s=c/d,v=n/o,h=1;if(H==="contain"?h=v>s?o/d:n/c:H==="cover"&&(h=v>s?n/c:o/d),H==="stretch")t.style.width="100%",t.style.height="100%";else{let f=Math.floor(c*h),z=Math.floor(d*h);t.style.width=`${f}px`,t.style.height=`${z}px`,t.style.position="absolute",t.style.left=`${Math.floor((n-f)/2)}px`,t.style.top=`${Math.floor((o-z)/2)}px`}}else if(M){d=M;let s=n/o;c=Math.floor(d*s),t.style.width="100%",t.style.height="100%",t.style.position="absolute",t.style.left="0",t.style.top="0"}else if(L){c=L;let s=n/o;d=Math.floor(c/s),t.style.width="100%",t.style.height="100%",t.style.position="absolute",t.style.left="0",t.style.top="0"}else c=n,d=o,t.style.width=`${c}px`,t.style.height=`${d}px`,t.style.position="absolute",t.style.left="0",t.style.top="0";let r=Math.max(1,Math.floor(c*x)),m=Math.max(1,Math.floor(d*x));t.width!==r&&(t.width=r),t.height!==m&&(t.height=m),u.setTransform(x,0,0,x,0,0),P.forEach(s=>s())}let G=new ResizeObserver(y);G.observe(document.body),g.push(()=>G.disconnect()),window.addEventListener("resize",y),g.push(()=>window.removeEventListener("resize",y));let _=window.visualViewport;_&&(_.addEventListener("resize",y),g.push(()=>_.removeEventListener("resize",y)));let E={stage:document.body,canvas:t,ctx:u,get width(){return c},get height(){return d},get dpr(){return x},resize:y,toStagePoint:e=>{let n=t.getBoundingClientRect(),o=(e.clientX-n.left)*(c/n.width),r=(e.clientY-n.top)*(d/n.height);return o=Math.max(0,Math.min(c,o)),r=Math.max(0,Math.min(d,r)),{x:o,y:r}},createDrag:()=>U(E.toStagePoint),onTap:e=>{b.push(e)},onMove:e=>{T.push(e)},onRelease:e=>{A.push(e)},on:(e,n,o,r)=>{if(/^(click|pointer|mouse|touch)/.test(e))try{let v=n.split(",").map(f=>`.star-ui ${f.trim()}`).join(", "),h=document.createElement("style");h.textContent=`${v} { pointer-events: auto; }`,document.head.appendChild(h),g.push(()=>h.remove())}catch{}let m=v=>{let f=v.target?.closest?.(n);f&&o.call(f,v)};document.addEventListener(e,m,r);let s=()=>document.removeEventListener(e,m,r);return g.push(s),s},loop:e=>{let o=0,r=!1,m=0,s=v=>{if(!r)return;let h=m?Math.min((v-m)/1e3,.1):0;m=v;try{e(h,v)}catch(f){console.error("[star-canvas] Game loop error:",f)}o=requestAnimationFrame(s)};return C={get running(){return r},start(){r||(r=!0,m=performance.now(),o=requestAnimationFrame(s))},stop(){r&&(r=!1,cancelAnimationFrame(o))}},C.start(),C},ui:{root:l,render:e=>{l.innerHTML!==e&&(l.innerHTML=e)},el:e=>l.querySelector(e),all:e=>l.querySelectorAll(e)},destroy:()=>{C?.stop(),g.forEach(e=>e()),g=[],P.clear(),t.parentElement&&t.parentElement.removeChild(t),l.parentElement&&l.parentElement.removeChild(l),a.parentElement&&a.parentElement.removeChild(a)},scoped:e=>{u.save();try{e()}finally{u.restore()}}};a.addEventListener("pointerdown",e=>{if(S(e))return;let n={...E.toStagePoint(e),event:e};b.forEach(o=>o(n))}),a.addEventListener("pointermove",e=>{let n={...E.toStagePoint(e),event:e};T.forEach(o=>o(n))}),a.addEventListener("pointerup",e=>{let n={...E.toStagePoint(e),event:e};A.forEach(o=>o(n))}),window.__STAR_DOM__={destroy:E.destroy},requestAnimationFrame(()=>requestAnimationFrame(()=>{y();try{p(E)}catch(e){console.error("[star-canvas] Game initialization failed:",e)}}))}export{U as createDragState,J as game,V as version};
32
+ `,document.head.appendChild(p)}function te(p,i={}){B()&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>W(p,i),{once:!0}):queueMicrotask(()=>W(p,i)))}function W(p,i){if(Z(),window.__STAR_DOM__?.destroy)try{window.__STAR_DOM__.destroy()}catch{}document.querySelectorAll(".star-ui, .star-canvas, .star-input").forEach(e=>e.remove());let l=document.createElement("div");l.className="star-ui",document.body.appendChild(l);let n=document.createElement("canvas");n.className="star-canvas",document.body.appendChild(n);let m=n.getContext("2d",i.contextAttributes??{alpha:!0});if(!m)throw new Error("[star-canvas] Failed to get 2D context");if(i.preventContextMenu!==!1){let e=t=>t.preventDefault();n.addEventListener("contextmenu",e)}let a=document.createElement("div");a.className="star-input",document.body.appendChild(a);let x=[],I=[],P=[],A=null,w={x:0,y:0,down:!1},S=null,R=[],L=new Map,G=null,M=null;Object.defineProperty(n,"onclick",{get:()=>G,set:e=>{M&&(a.removeEventListener("click",M),M=null),G=e,e&&(M=t=>{O(t)||e.call(n,t)},a.addEventListener("click",M))}});let U=n.addEventListener.bind(n),j=n.removeEventListener.bind(n),H=new WeakMap;function O(e){let t=e;if(t.clientX==null||t.clientY==null)return!1;let r=document.elementFromPoint(t.clientX,t.clientY);return r!==null&&r!==l&&l.contains(r)}let V=/^(click|pointerdown|mousedown|touchstart)$/;n.addEventListener=function(e,t,r){if(/^(click|pointer|mouse|touch)/.test(e)&&typeof t=="function"){let o=V.test(e),d=(s=>{o&&O(s)||t.call(n,s)});H.set(t,d),a.addEventListener(e,d,r)}else U(e,t,r)},n.removeEventListener=function(e,t,r){if(/^(click|pointer|mouse|touch)/.test(e)&&typeof t=="function"){let o=H.get(t);o&&(a.removeEventListener(e,o,r),H.delete(t))}else j(e,t,r)};let J=i.preset??"landscape",z=N[J]??N.landscape,T=i.width??z.width,C=i.height??z.height,X=i.maxPixelRatio??2,Y=i.pixelRatio??"device",D=i.fit??"contain",g=1,u=T??1,c=C??1,$=new Set,_=null,b=[];function K(){return Math.min(typeof Y=="number"?Math.max(1,Y):Math.max(1,window.devicePixelRatio||1),X)}function f(){g=K();let e=document.body.getBoundingClientRect(),t=Math.max(1,Math.floor(e.width||window.innerWidth||800)),r=Math.max(1,Math.floor(e.height||window.innerHeight||600));if(T&&C){u=T,c=C;let s=u/c,v=t/r,h=1;if(D==="contain"?h=v>s?r/c:t/u:D==="cover"&&(h=v>s?t/u:r/c),D==="stretch")n.style.width="100%",n.style.height="100%";else{let y=Math.floor(u*h),F=Math.floor(c*h);n.style.width=`${y}px`,n.style.height=`${F}px`,n.style.position="absolute",n.style.left=`${Math.floor((t-y)/2)}px`,n.style.top=`${Math.floor((r-F)/2)}px`}}else if(C){c=C;let s=t/r;u=Math.floor(c*s),n.style.width="100%",n.style.height="100%",n.style.position="absolute",n.style.left="0",n.style.top="0"}else if(T){u=T;let s=t/r;c=Math.floor(u/s),n.style.width="100%",n.style.height="100%",n.style.position="absolute",n.style.left="0",n.style.top="0"}else u=t,c=r,n.style.width=`${u}px`,n.style.height=`${c}px`,n.style.position="absolute",n.style.left="0",n.style.top="0";let o=Math.max(1,Math.floor(u*g)),d=Math.max(1,Math.floor(c*g));n.width!==o&&(n.width=o),n.height!==d&&(n.height=d),m.setTransform(g,0,0,g,0,0),$.forEach(s=>s())}let q=new ResizeObserver(f);q.observe(document.body),b.push(()=>q.disconnect()),window.addEventListener("resize",f),b.push(()=>window.removeEventListener("resize",f));let k=window.visualViewport;k&&(k.addEventListener("resize",f),b.push(()=>k.removeEventListener("resize",f)));let E={stage:document.body,canvas:n,ctx:m,get width(){return u},get height(){return c},get dpr(){return g},resize:f,toStagePoint:e=>{let t=n.getBoundingClientRect(),r=(e.clientX-t.left)*(u/t.width),o=(e.clientY-t.top)*(c/t.height);return r=Math.max(0,Math.min(u,r)),o=Math.max(0,Math.min(c,o)),{x:r,y:o}},createDrag:()=>Q(E.toStagePoint),onTap:e=>{x.push(e)},onMove:e=>{I.push(e)},onRelease:e=>{P.push(e)},get tap(){return A},get pointer(){return w},get released(){return S},get taps(){return R},get pointers(){return Array.from(L.values())},on:(e,t,r,o)=>{if(/^(click|pointer|mouse|touch)/.test(e))try{let v=t.split(",").map(y=>`.star-ui ${y.trim()}`).join(", "),h=document.createElement("style");h.textContent=`${v} { pointer-events: auto; }`,document.head.appendChild(h),b.push(()=>h.remove())}catch{}let d=v=>{let y=v.target?.closest?.(t);y&&r.call(y,v)};document.addEventListener(e,d,o);let s=()=>document.removeEventListener(e,d,o);return b.push(s),s},loop:e=>{let r=0,o=!1,d=0,s=v=>{if(!o)return;let h=d?Math.min((v-d)/1e3,.1):0;d=v;try{e(h,v)}catch(y){console.error("[star-canvas] Game loop error:",y)}A=null,S=null,R=[],r=requestAnimationFrame(s)};return _={get running(){return o},start(){o||(o=!0,d=performance.now(),r=requestAnimationFrame(s))},stop(){o&&(o=!1,cancelAnimationFrame(r))}},_.start(),_},ui:{root:l,render:e=>{l.innerHTML!==e&&(l.innerHTML=e)},el:e=>l.querySelector(e),all:e=>l.querySelectorAll(e)},destroy:()=>{_?.stop(),b.forEach(e=>e()),b=[],$.clear(),n.parentElement&&n.parentElement.removeChild(n),l.parentElement&&l.parentElement.removeChild(l),a.parentElement&&a.parentElement.removeChild(a)},scoped:e=>{m.save();try{e()}finally{m.restore()}}};a.addEventListener("pointerdown",e=>{let t=E.toStagePoint(e);if(w={x:t.x,y:t.y,down:!0},L.set(e.pointerId,{x:t.x,y:t.y,id:e.pointerId,down:!0}),O(e))return;A={x:t.x,y:t.y,time:e.timeStamp},R.push({x:t.x,y:t.y,id:e.pointerId,time:e.timeStamp});let r={...t,event:e};x.forEach(o=>o(r))}),a.addEventListener("pointermove",e=>{let t=E.toStagePoint(e);w={x:t.x,y:t.y,down:w.down};let r=L.get(e.pointerId);r&&L.set(e.pointerId,{...r,x:t.x,y:t.y});let o={...t,event:e};I.forEach(d=>d(o))}),a.addEventListener("pointerup",e=>{let t=E.toStagePoint(e);w={x:t.x,y:t.y,down:!1},S={x:t.x,y:t.y,time:e.timeStamp},L.delete(e.pointerId);let r={...t,event:e};P.forEach(o=>o(r))}),window.__STAR_DOM__={destroy:E.destroy},requestAnimationFrame(()=>requestAnimationFrame(()=>{f();try{p(E)}catch(e){console.error("[star-canvas] Game initialization failed:",e)}}))}export{Q as createDragState,te as game,ee as version};
package/dist/legacy.cjs CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";var H=Object.defineProperty;var K=Object.getOwnPropertyDescriptor;var Q=Object.getOwnPropertyNames;var Z=Object.prototype.hasOwnProperty;var ee=(i,r)=>{for(var a in r)H(i,a,{get:r[a],enumerable:!0})},te=(i,r,a,t)=>{if(r&&typeof r=="object"||typeof r=="function")for(let l of Q(r))!Z.call(i,l)&&l!==a&&H(i,l,{get:()=>r[l],enumerable:!(t=K(r,l))||t.enumerable});return i};var ne=i=>te(H({},"__esModule",{value:!0}),i);var ie={};ee(ie,{createDragState:()=>_,game:()=>re,version:()=>F});module.exports=ne(ie);var F="0.8.0",$={landscape:{width:640,height:360},portrait:{width:360,height:640},responsive:{}};function _(i){let r=null,a=0,t=0;return{point(l){return i(l)},grab(l,c){let{x:b,y:T}=i(l);r=c,a=b-c.x,t=T-c.y},move(l){if(r){let{x:c,y:b}=i(l);r.x=c-a,r.y=b-t}},release(){let l=r;return r=null,l},get dragging(){return r}}}function N(){return typeof window<"u"&&typeof document<"u"}function oe(){if(!N()||document.getElementById("star-canvas-base"))return;let i=document.createElement("style");i.id="star-canvas-base",i.textContent=`
1
+ "use strict";var k=Object.defineProperty;var ne=Object.getOwnPropertyDescriptor;var re=Object.getOwnPropertyNames;var oe=Object.prototype.hasOwnProperty;var ie=(a,o)=>{for(var s in o)k(a,s,{get:o[s],enumerable:!0})},ae=(a,o,s,n)=>{if(o&&typeof o=="object"||typeof o=="function")for(let l of re(o))!oe.call(a,l)&&l!==s&&k(a,l,{get:()=>o[l],enumerable:!(n=ne(o,l))||n.enumerable});return a};var se=a=>ae(k({},"__esModule",{value:!0}),a);var ue={};ie(ue,{createDragState:()=>P,game:()=>de,version:()=>j});module.exports=se(ue);var j="0.8.0",U={landscape:{width:640,height:360},portrait:{width:360,height:640},responsive:{}};function P(a){let o=null,s=0,n=0;return{point(l){return a(l)},grab(l,d){let{x,y:I}=a(l);o=d,s=x-d.x,n=I-d.y},move(l){if(o){let{x:d,y:x}=a(l);o.x=d-s,o.y=x-n}},release(){let l=o;return o=null,l},get dragging(){return o}}}function V(){return typeof window<"u"&&typeof document<"u"}function le(){if(!V()||document.getElementById("star-canvas-base"))return;let a=document.createElement("style");a.id="star-canvas-base",a.textContent=`
2
2
  html, body { height: 100%; }
3
3
  body {
4
4
  margin: 0; min-height: 100dvh; overflow: hidden; background: #000;
@@ -29,4 +29,4 @@
29
29
  .star-ui textarea, .star-ui [data-interactive] {
30
30
  pointer-events: auto;
31
31
  }
32
- `,document.head.appendChild(i)}function W(i,r={}){N()&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>q(i,r),{once:!0}):queueMicrotask(()=>q(i,r)))}function q(i,r){if(oe(),window.__STAR_DOM__?.destroy)try{window.__STAR_DOM__.destroy()}catch{}document.querySelectorAll(".star-ui, .star-canvas, .star-input").forEach(e=>e.remove());let a=document.createElement("div");a.className="star-ui",document.body.appendChild(a);let t=document.createElement("canvas");t.className="star-canvas",document.body.appendChild(t);let l=t.getContext("2d",r.contextAttributes??{alpha:!0});if(!l)throw new Error("[star-canvas] Failed to get 2D context");if(r.preventContextMenu!==!1){let e=n=>n.preventDefault();t.addEventListener("contextmenu",e)}let c=document.createElement("div");c.className="star-input",document.body.appendChild(c);let b=[],T=[],A=[],D=null,w=null;Object.defineProperty(t,"onclick",{get:()=>D,set:e=>{w&&(c.removeEventListener("click",w),w=null),D=e,e&&(w=n=>{O(n)||e.call(t,n)},c.addEventListener("click",w))}});let U=t.addEventListener.bind(t),B=t.removeEventListener.bind(t),G=new WeakMap;function O(e){let n=e;if(n.clientX==null||n.clientY==null)return!1;let o=document.elementFromPoint(n.clientX,n.clientY);return o!==null&&o!==a&&a.contains(o)}let j=/^(click|pointerdown|mousedown|touchstart)$/;t.addEventListener=function(e,n,o){if(/^(click|pointer|mouse|touch)/.test(e)&&typeof n=="function"){let s=j.test(e),p=(d=>{s&&O(d)||n.call(t,d)});G.set(n,p),c.addEventListener(e,p,o)}else U(e,n,o)},t.removeEventListener=function(e,n,o){if(/^(click|pointer|mouse|touch)/.test(e)&&typeof n=="function"){let s=G.get(n);s&&(c.removeEventListener(e,s,o),G.delete(n))}else B(e,n,o)};let V=r.preset??"landscape",R=$[V]??$.landscape,L=r.width??R.width,M=r.height??R.height,k=r.maxPixelRatio??2,P=r.pixelRatio??"device",S=r.fit??"contain",x=1,u=L??1,m=M??1,z=new Set,C=null,g=[];function J(){return Math.min(typeof P=="number"?Math.max(1,P):Math.max(1,window.devicePixelRatio||1),k)}function y(){x=J();let e=document.body.getBoundingClientRect(),n=Math.max(1,Math.floor(e.width||window.innerWidth||800)),o=Math.max(1,Math.floor(e.height||window.innerHeight||600));if(L&&M){u=L,m=M;let d=u/m,v=n/o,h=1;if(S==="contain"?h=v>d?o/m:n/u:S==="cover"&&(h=v>d?n/u:o/m),S==="stretch")t.style.width="100%",t.style.height="100%";else{let f=Math.floor(u*h),Y=Math.floor(m*h);t.style.width=`${f}px`,t.style.height=`${Y}px`,t.style.position="absolute",t.style.left=`${Math.floor((n-f)/2)}px`,t.style.top=`${Math.floor((o-Y)/2)}px`}}else if(M){m=M;let d=n/o;u=Math.floor(m*d),t.style.width="100%",t.style.height="100%",t.style.position="absolute",t.style.left="0",t.style.top="0"}else if(L){u=L;let d=n/o;m=Math.floor(u/d),t.style.width="100%",t.style.height="100%",t.style.position="absolute",t.style.left="0",t.style.top="0"}else u=n,m=o,t.style.width=`${u}px`,t.style.height=`${m}px`,t.style.position="absolute",t.style.left="0",t.style.top="0";let s=Math.max(1,Math.floor(u*x)),p=Math.max(1,Math.floor(m*x));t.width!==s&&(t.width=s),t.height!==p&&(t.height=p),l.setTransform(x,0,0,x,0,0),z.forEach(d=>d())}let X=new ResizeObserver(y);X.observe(document.body),g.push(()=>X.disconnect()),window.addEventListener("resize",y),g.push(()=>window.removeEventListener("resize",y));let I=window.visualViewport;I&&(I.addEventListener("resize",y),g.push(()=>I.removeEventListener("resize",y)));let E={stage:document.body,canvas:t,ctx:l,get width(){return u},get height(){return m},get dpr(){return x},resize:y,toStagePoint:e=>{let n=t.getBoundingClientRect(),o=(e.clientX-n.left)*(u/n.width),s=(e.clientY-n.top)*(m/n.height);return o=Math.max(0,Math.min(u,o)),s=Math.max(0,Math.min(m,s)),{x:o,y:s}},createDrag:()=>_(E.toStagePoint),onTap:e=>{b.push(e)},onMove:e=>{T.push(e)},onRelease:e=>{A.push(e)},on:(e,n,o,s)=>{if(/^(click|pointer|mouse|touch)/.test(e))try{let v=n.split(",").map(f=>`.star-ui ${f.trim()}`).join(", "),h=document.createElement("style");h.textContent=`${v} { pointer-events: auto; }`,document.head.appendChild(h),g.push(()=>h.remove())}catch{}let p=v=>{let f=v.target?.closest?.(n);f&&o.call(f,v)};document.addEventListener(e,p,s);let d=()=>document.removeEventListener(e,p,s);return g.push(d),d},loop:e=>{let o=0,s=!1,p=0,d=v=>{if(!s)return;let h=p?Math.min((v-p)/1e3,.1):0;p=v;try{e(h,v)}catch(f){console.error("[star-canvas] Game loop error:",f)}o=requestAnimationFrame(d)};return C={get running(){return s},start(){s||(s=!0,p=performance.now(),o=requestAnimationFrame(d))},stop(){s&&(s=!1,cancelAnimationFrame(o))}},C.start(),C},ui:{root:a,render:e=>{a.innerHTML!==e&&(a.innerHTML=e)},el:e=>a.querySelector(e),all:e=>a.querySelectorAll(e)},destroy:()=>{C?.stop(),g.forEach(e=>e()),g=[],z.clear(),t.parentElement&&t.parentElement.removeChild(t),a.parentElement&&a.parentElement.removeChild(a),c.parentElement&&c.parentElement.removeChild(c)},scoped:e=>{l.save();try{e()}finally{l.restore()}}};c.addEventListener("pointerdown",e=>{if(O(e))return;let n={...E.toStagePoint(e),event:e};b.forEach(o=>o(n))}),c.addEventListener("pointermove",e=>{let n={...E.toStagePoint(e),event:e};T.forEach(o=>o(n))}),c.addEventListener("pointerup",e=>{let n={...E.toStagePoint(e),event:e};A.forEach(o=>o(n))}),window.__STAR_DOM__={destroy:E.destroy},requestAnimationFrame(()=>requestAnimationFrame(()=>{y();try{i(E)}catch(e){console.error("[star-canvas] Game initialization failed:",e)}}))}function re(i,r={}){let a={preset:"responsive",...r};return W(i,a)}0&&(module.exports={createDragState,game,version});
32
+ `,document.head.appendChild(a)}function J(a,o={}){V()&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>B(a,o),{once:!0}):queueMicrotask(()=>B(a,o)))}function B(a,o){if(le(),window.__STAR_DOM__?.destroy)try{window.__STAR_DOM__.destroy()}catch{}document.querySelectorAll(".star-ui, .star-canvas, .star-input").forEach(e=>e.remove());let s=document.createElement("div");s.className="star-ui",document.body.appendChild(s);let n=document.createElement("canvas");n.className="star-canvas",document.body.appendChild(n);let l=n.getContext("2d",o.contextAttributes??{alpha:!0});if(!l)throw new Error("[star-canvas] Failed to get 2D context");if(o.preventContextMenu!==!1){let e=t=>t.preventDefault();n.addEventListener("contextmenu",e)}let d=document.createElement("div");d.className="star-input",document.body.appendChild(d);let x=[],I=[],z=[],_=null,w={x:0,y:0,down:!1},A=null,G=[],L=new Map,X=null,M=null;Object.defineProperty(n,"onclick",{get:()=>X,set:e=>{M&&(d.removeEventListener("click",M),M=null),X=e,e&&(M=t=>{R(t)||e.call(n,t)},d.addEventListener("click",M))}});let K=n.addEventListener.bind(n),Q=n.removeEventListener.bind(n),O=new WeakMap;function R(e){let t=e;if(t.clientX==null||t.clientY==null)return!1;let r=document.elementFromPoint(t.clientX,t.clientY);return r!==null&&r!==s&&s.contains(r)}let Z=/^(click|pointerdown|mousedown|touchstart)$/;n.addEventListener=function(e,t,r){if(/^(click|pointer|mouse|touch)/.test(e)&&typeof t=="function"){let i=Z.test(e),c=(u=>{i&&R(u)||t.call(n,u)});O.set(t,c),d.addEventListener(e,c,r)}else K(e,t,r)},n.removeEventListener=function(e,t,r){if(/^(click|pointer|mouse|touch)/.test(e)&&typeof t=="function"){let i=O.get(t);i&&(d.removeEventListener(e,i,r),O.delete(t))}else Q(e,t,r)};let ee=o.preset??"landscape",Y=U[ee]??U.landscape,T=o.width??Y.width,C=o.height??Y.height,$=o.maxPixelRatio??2,q=o.pixelRatio??"device",H=o.fit??"contain",g=1,m=T??1,p=C??1,F=new Set,S=null,b=[];function te(){return Math.min(typeof q=="number"?Math.max(1,q):Math.max(1,window.devicePixelRatio||1),$)}function f(){g=te();let e=document.body.getBoundingClientRect(),t=Math.max(1,Math.floor(e.width||window.innerWidth||800)),r=Math.max(1,Math.floor(e.height||window.innerHeight||600));if(T&&C){m=T,p=C;let u=m/p,v=t/r,h=1;if(H==="contain"?h=v>u?r/p:t/m:H==="cover"&&(h=v>u?t/m:r/p),H==="stretch")n.style.width="100%",n.style.height="100%";else{let y=Math.floor(m*h),W=Math.floor(p*h);n.style.width=`${y}px`,n.style.height=`${W}px`,n.style.position="absolute",n.style.left=`${Math.floor((t-y)/2)}px`,n.style.top=`${Math.floor((r-W)/2)}px`}}else if(C){p=C;let u=t/r;m=Math.floor(p*u),n.style.width="100%",n.style.height="100%",n.style.position="absolute",n.style.left="0",n.style.top="0"}else if(T){m=T;let u=t/r;p=Math.floor(m/u),n.style.width="100%",n.style.height="100%",n.style.position="absolute",n.style.left="0",n.style.top="0"}else m=t,p=r,n.style.width=`${m}px`,n.style.height=`${p}px`,n.style.position="absolute",n.style.left="0",n.style.top="0";let i=Math.max(1,Math.floor(m*g)),c=Math.max(1,Math.floor(p*g));n.width!==i&&(n.width=i),n.height!==c&&(n.height=c),l.setTransform(g,0,0,g,0,0),F.forEach(u=>u())}let N=new ResizeObserver(f);N.observe(document.body),b.push(()=>N.disconnect()),window.addEventListener("resize",f),b.push(()=>window.removeEventListener("resize",f));let D=window.visualViewport;D&&(D.addEventListener("resize",f),b.push(()=>D.removeEventListener("resize",f)));let E={stage:document.body,canvas:n,ctx:l,get width(){return m},get height(){return p},get dpr(){return g},resize:f,toStagePoint:e=>{let t=n.getBoundingClientRect(),r=(e.clientX-t.left)*(m/t.width),i=(e.clientY-t.top)*(p/t.height);return r=Math.max(0,Math.min(m,r)),i=Math.max(0,Math.min(p,i)),{x:r,y:i}},createDrag:()=>P(E.toStagePoint),onTap:e=>{x.push(e)},onMove:e=>{I.push(e)},onRelease:e=>{z.push(e)},get tap(){return _},get pointer(){return w},get released(){return A},get taps(){return G},get pointers(){return Array.from(L.values())},on:(e,t,r,i)=>{if(/^(click|pointer|mouse|touch)/.test(e))try{let v=t.split(",").map(y=>`.star-ui ${y.trim()}`).join(", "),h=document.createElement("style");h.textContent=`${v} { pointer-events: auto; }`,document.head.appendChild(h),b.push(()=>h.remove())}catch{}let c=v=>{let y=v.target?.closest?.(t);y&&r.call(y,v)};document.addEventListener(e,c,i);let u=()=>document.removeEventListener(e,c,i);return b.push(u),u},loop:e=>{let r=0,i=!1,c=0,u=v=>{if(!i)return;let h=c?Math.min((v-c)/1e3,.1):0;c=v;try{e(h,v)}catch(y){console.error("[star-canvas] Game loop error:",y)}_=null,A=null,G=[],r=requestAnimationFrame(u)};return S={get running(){return i},start(){i||(i=!0,c=performance.now(),r=requestAnimationFrame(u))},stop(){i&&(i=!1,cancelAnimationFrame(r))}},S.start(),S},ui:{root:s,render:e=>{s.innerHTML!==e&&(s.innerHTML=e)},el:e=>s.querySelector(e),all:e=>s.querySelectorAll(e)},destroy:()=>{S?.stop(),b.forEach(e=>e()),b=[],F.clear(),n.parentElement&&n.parentElement.removeChild(n),s.parentElement&&s.parentElement.removeChild(s),d.parentElement&&d.parentElement.removeChild(d)},scoped:e=>{l.save();try{e()}finally{l.restore()}}};d.addEventListener("pointerdown",e=>{let t=E.toStagePoint(e);if(w={x:t.x,y:t.y,down:!0},L.set(e.pointerId,{x:t.x,y:t.y,id:e.pointerId,down:!0}),R(e))return;_={x:t.x,y:t.y,time:e.timeStamp},G.push({x:t.x,y:t.y,id:e.pointerId,time:e.timeStamp});let r={...t,event:e};x.forEach(i=>i(r))}),d.addEventListener("pointermove",e=>{let t=E.toStagePoint(e);w={x:t.x,y:t.y,down:w.down};let r=L.get(e.pointerId);r&&L.set(e.pointerId,{...r,x:t.x,y:t.y});let i={...t,event:e};I.forEach(c=>c(i))}),d.addEventListener("pointerup",e=>{let t=E.toStagePoint(e);w={x:t.x,y:t.y,down:!1},A={x:t.x,y:t.y,time:e.timeStamp},L.delete(e.pointerId);let r={...t,event:e};z.forEach(i=>i(r))}),window.__STAR_DOM__={destroy:E.destroy},requestAnimationFrame(()=>requestAnimationFrame(()=>{f();try{a(E)}catch(e){console.error("[star-canvas] Game initialization failed:",e)}}))}function de(a,o={}){let s={preset:"responsive",...o};return J(a,s)}0&&(module.exports={createDragState,game,version});
package/dist/legacy.mjs CHANGED
@@ -1,4 +1,4 @@
1
- var V="0.8.0",X={landscape:{width:640,height:360},portrait:{width:360,height:640},responsive:{}};function $(c){let r=null,s=0,t=0;return{point(m){return c(m)},grab(m,a){let{x:b,y:T}=c(m);r=a,s=b-a.x,t=T-a.y},move(m){if(r){let{x:a,y:b}=c(m);r.x=a-s,r.y=b-t}},release(){let m=r;return r=null,m},get dragging(){return r}}}function q(){return typeof window<"u"&&typeof document<"u"}function J(){if(!q()||document.getElementById("star-canvas-base"))return;let c=document.createElement("style");c.id="star-canvas-base",c.textContent=`
1
+ var ee="0.8.0",N={landscape:{width:640,height:360},portrait:{width:360,height:640},responsive:{}};function U(u){let i=null,s=0,n=0;return{point(p){return u(p)},grab(p,a){let{x,y:I}=u(p);i=a,s=x-a.x,n=I-a.y},move(p){if(i){let{x:a,y:x}=u(p);i.x=a-s,i.y=x-n}},release(){let p=i;return i=null,p},get dragging(){return i}}}function B(){return typeof window<"u"&&typeof document<"u"}function te(){if(!B()||document.getElementById("star-canvas-base"))return;let u=document.createElement("style");u.id="star-canvas-base",u.textContent=`
2
2
  html, body { height: 100%; }
3
3
  body {
4
4
  margin: 0; min-height: 100dvh; overflow: hidden; background: #000;
@@ -29,4 +29,4 @@ var V="0.8.0",X={landscape:{width:640,height:360},portrait:{width:360,height:640
29
29
  .star-ui textarea, .star-ui [data-interactive] {
30
30
  pointer-events: auto;
31
31
  }
32
- `,document.head.appendChild(c)}function F(c,r={}){q()&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>Y(c,r),{once:!0}):queueMicrotask(()=>Y(c,r)))}function Y(c,r){if(J(),window.__STAR_DOM__?.destroy)try{window.__STAR_DOM__.destroy()}catch{}document.querySelectorAll(".star-ui, .star-canvas, .star-input").forEach(e=>e.remove());let s=document.createElement("div");s.className="star-ui",document.body.appendChild(s);let t=document.createElement("canvas");t.className="star-canvas",document.body.appendChild(t);let m=t.getContext("2d",r.contextAttributes??{alpha:!0});if(!m)throw new Error("[star-canvas] Failed to get 2D context");if(r.preventContextMenu!==!1){let e=n=>n.preventDefault();t.addEventListener("contextmenu",e)}let a=document.createElement("div");a.className="star-input",document.body.appendChild(a);let b=[],T=[],H=[],_=null,w=null;Object.defineProperty(t,"onclick",{get:()=>_,set:e=>{w&&(a.removeEventListener("click",w),w=null),_=e,e&&(w=n=>{O(n)||e.call(t,n)},a.addEventListener("click",w))}});let N=t.addEventListener.bind(t),W=t.removeEventListener.bind(t),G=new WeakMap;function O(e){let n=e;if(n.clientX==null||n.clientY==null)return!1;let o=document.elementFromPoint(n.clientX,n.clientY);return o!==null&&o!==s&&s.contains(o)}let U=/^(click|pointerdown|mousedown|touchstart)$/;t.addEventListener=function(e,n,o){if(/^(click|pointer|mouse|touch)/.test(e)&&typeof n=="function"){let i=U.test(e),p=(l=>{i&&O(l)||n.call(t,l)});G.set(n,p),a.addEventListener(e,p,o)}else N(e,n,o)},t.removeEventListener=function(e,n,o){if(/^(click|pointer|mouse|touch)/.test(e)&&typeof n=="function"){let i=G.get(n);i&&(a.removeEventListener(e,i,o),G.delete(n))}else W(e,n,o)};let B=r.preset??"landscape",A=X[B]??X.landscape,L=r.width??A.width,M=r.height??A.height,D=r.maxPixelRatio??2,R=r.pixelRatio??"device",S=r.fit??"contain",x=1,d=L??1,u=M??1,k=new Set,C=null,g=[];function j(){return Math.min(typeof R=="number"?Math.max(1,R):Math.max(1,window.devicePixelRatio||1),D)}function y(){x=j();let e=document.body.getBoundingClientRect(),n=Math.max(1,Math.floor(e.width||window.innerWidth||800)),o=Math.max(1,Math.floor(e.height||window.innerHeight||600));if(L&&M){d=L,u=M;let l=d/u,v=n/o,h=1;if(S==="contain"?h=v>l?o/u:n/d:S==="cover"&&(h=v>l?n/d:o/u),S==="stretch")t.style.width="100%",t.style.height="100%";else{let f=Math.floor(d*h),z=Math.floor(u*h);t.style.width=`${f}px`,t.style.height=`${z}px`,t.style.position="absolute",t.style.left=`${Math.floor((n-f)/2)}px`,t.style.top=`${Math.floor((o-z)/2)}px`}}else if(M){u=M;let l=n/o;d=Math.floor(u*l),t.style.width="100%",t.style.height="100%",t.style.position="absolute",t.style.left="0",t.style.top="0"}else if(L){d=L;let l=n/o;u=Math.floor(d/l),t.style.width="100%",t.style.height="100%",t.style.position="absolute",t.style.left="0",t.style.top="0"}else d=n,u=o,t.style.width=`${d}px`,t.style.height=`${u}px`,t.style.position="absolute",t.style.left="0",t.style.top="0";let i=Math.max(1,Math.floor(d*x)),p=Math.max(1,Math.floor(u*x));t.width!==i&&(t.width=i),t.height!==p&&(t.height=p),m.setTransform(x,0,0,x,0,0),k.forEach(l=>l())}let P=new ResizeObserver(y);P.observe(document.body),g.push(()=>P.disconnect()),window.addEventListener("resize",y),g.push(()=>window.removeEventListener("resize",y));let I=window.visualViewport;I&&(I.addEventListener("resize",y),g.push(()=>I.removeEventListener("resize",y)));let E={stage:document.body,canvas:t,ctx:m,get width(){return d},get height(){return u},get dpr(){return x},resize:y,toStagePoint:e=>{let n=t.getBoundingClientRect(),o=(e.clientX-n.left)*(d/n.width),i=(e.clientY-n.top)*(u/n.height);return o=Math.max(0,Math.min(d,o)),i=Math.max(0,Math.min(u,i)),{x:o,y:i}},createDrag:()=>$(E.toStagePoint),onTap:e=>{b.push(e)},onMove:e=>{T.push(e)},onRelease:e=>{H.push(e)},on:(e,n,o,i)=>{if(/^(click|pointer|mouse|touch)/.test(e))try{let v=n.split(",").map(f=>`.star-ui ${f.trim()}`).join(", "),h=document.createElement("style");h.textContent=`${v} { pointer-events: auto; }`,document.head.appendChild(h),g.push(()=>h.remove())}catch{}let p=v=>{let f=v.target?.closest?.(n);f&&o.call(f,v)};document.addEventListener(e,p,i);let l=()=>document.removeEventListener(e,p,i);return g.push(l),l},loop:e=>{let o=0,i=!1,p=0,l=v=>{if(!i)return;let h=p?Math.min((v-p)/1e3,.1):0;p=v;try{e(h,v)}catch(f){console.error("[star-canvas] Game loop error:",f)}o=requestAnimationFrame(l)};return C={get running(){return i},start(){i||(i=!0,p=performance.now(),o=requestAnimationFrame(l))},stop(){i&&(i=!1,cancelAnimationFrame(o))}},C.start(),C},ui:{root:s,render:e=>{s.innerHTML!==e&&(s.innerHTML=e)},el:e=>s.querySelector(e),all:e=>s.querySelectorAll(e)},destroy:()=>{C?.stop(),g.forEach(e=>e()),g=[],k.clear(),t.parentElement&&t.parentElement.removeChild(t),s.parentElement&&s.parentElement.removeChild(s),a.parentElement&&a.parentElement.removeChild(a)},scoped:e=>{m.save();try{e()}finally{m.restore()}}};a.addEventListener("pointerdown",e=>{if(O(e))return;let n={...E.toStagePoint(e),event:e};b.forEach(o=>o(n))}),a.addEventListener("pointermove",e=>{let n={...E.toStagePoint(e),event:e};T.forEach(o=>o(n))}),a.addEventListener("pointerup",e=>{let n={...E.toStagePoint(e),event:e};H.forEach(o=>o(n))}),window.__STAR_DOM__={destroy:E.destroy},requestAnimationFrame(()=>requestAnimationFrame(()=>{y();try{c(E)}catch(e){console.error("[star-canvas] Game initialization failed:",e)}}))}function Z(c,r={}){let s={preset:"responsive",...r};return F(c,s)}export{$ as createDragState,Z as game,V as version};
32
+ `,document.head.appendChild(u)}function j(u,i={}){B()&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>W(u,i),{once:!0}):queueMicrotask(()=>W(u,i)))}function W(u,i){if(te(),window.__STAR_DOM__?.destroy)try{window.__STAR_DOM__.destroy()}catch{}document.querySelectorAll(".star-ui, .star-canvas, .star-input").forEach(e=>e.remove());let s=document.createElement("div");s.className="star-ui",document.body.appendChild(s);let n=document.createElement("canvas");n.className="star-canvas",document.body.appendChild(n);let p=n.getContext("2d",i.contextAttributes??{alpha:!0});if(!p)throw new Error("[star-canvas] Failed to get 2D context");if(i.preventContextMenu!==!1){let e=t=>t.preventDefault();n.addEventListener("contextmenu",e)}let a=document.createElement("div");a.className="star-input",document.body.appendChild(a);let x=[],I=[],k=[],_=null,w={x:0,y:0,down:!1},A=null,G=[],L=new Map,P=null,M=null;Object.defineProperty(n,"onclick",{get:()=>P,set:e=>{M&&(a.removeEventListener("click",M),M=null),P=e,e&&(M=t=>{R(t)||e.call(n,t)},a.addEventListener("click",M))}});let V=n.addEventListener.bind(n),J=n.removeEventListener.bind(n),O=new WeakMap;function R(e){let t=e;if(t.clientX==null||t.clientY==null)return!1;let r=document.elementFromPoint(t.clientX,t.clientY);return r!==null&&r!==s&&s.contains(r)}let K=/^(click|pointerdown|mousedown|touchstart)$/;n.addEventListener=function(e,t,r){if(/^(click|pointer|mouse|touch)/.test(e)&&typeof t=="function"){let o=K.test(e),d=(l=>{o&&R(l)||t.call(n,l)});O.set(t,d),a.addEventListener(e,d,r)}else V(e,t,r)},n.removeEventListener=function(e,t,r){if(/^(click|pointer|mouse|touch)/.test(e)&&typeof t=="function"){let o=O.get(t);o&&(a.removeEventListener(e,o,r),O.delete(t))}else J(e,t,r)};let Q=i.preset??"landscape",z=N[Q]??N.landscape,T=i.width??z.width,C=i.height??z.height,X=i.maxPixelRatio??2,Y=i.pixelRatio??"device",H=i.fit??"contain",g=1,c=T??1,m=C??1,$=new Set,S=null,b=[];function Z(){return Math.min(typeof Y=="number"?Math.max(1,Y):Math.max(1,window.devicePixelRatio||1),X)}function f(){g=Z();let e=document.body.getBoundingClientRect(),t=Math.max(1,Math.floor(e.width||window.innerWidth||800)),r=Math.max(1,Math.floor(e.height||window.innerHeight||600));if(T&&C){c=T,m=C;let l=c/m,v=t/r,h=1;if(H==="contain"?h=v>l?r/m:t/c:H==="cover"&&(h=v>l?t/c:r/m),H==="stretch")n.style.width="100%",n.style.height="100%";else{let y=Math.floor(c*h),F=Math.floor(m*h);n.style.width=`${y}px`,n.style.height=`${F}px`,n.style.position="absolute",n.style.left=`${Math.floor((t-y)/2)}px`,n.style.top=`${Math.floor((r-F)/2)}px`}}else if(C){m=C;let l=t/r;c=Math.floor(m*l),n.style.width="100%",n.style.height="100%",n.style.position="absolute",n.style.left="0",n.style.top="0"}else if(T){c=T;let l=t/r;m=Math.floor(c/l),n.style.width="100%",n.style.height="100%",n.style.position="absolute",n.style.left="0",n.style.top="0"}else c=t,m=r,n.style.width=`${c}px`,n.style.height=`${m}px`,n.style.position="absolute",n.style.left="0",n.style.top="0";let o=Math.max(1,Math.floor(c*g)),d=Math.max(1,Math.floor(m*g));n.width!==o&&(n.width=o),n.height!==d&&(n.height=d),p.setTransform(g,0,0,g,0,0),$.forEach(l=>l())}let q=new ResizeObserver(f);q.observe(document.body),b.push(()=>q.disconnect()),window.addEventListener("resize",f),b.push(()=>window.removeEventListener("resize",f));let D=window.visualViewport;D&&(D.addEventListener("resize",f),b.push(()=>D.removeEventListener("resize",f)));let E={stage:document.body,canvas:n,ctx:p,get width(){return c},get height(){return m},get dpr(){return g},resize:f,toStagePoint:e=>{let t=n.getBoundingClientRect(),r=(e.clientX-t.left)*(c/t.width),o=(e.clientY-t.top)*(m/t.height);return r=Math.max(0,Math.min(c,r)),o=Math.max(0,Math.min(m,o)),{x:r,y:o}},createDrag:()=>U(E.toStagePoint),onTap:e=>{x.push(e)},onMove:e=>{I.push(e)},onRelease:e=>{k.push(e)},get tap(){return _},get pointer(){return w},get released(){return A},get taps(){return G},get pointers(){return Array.from(L.values())},on:(e,t,r,o)=>{if(/^(click|pointer|mouse|touch)/.test(e))try{let v=t.split(",").map(y=>`.star-ui ${y.trim()}`).join(", "),h=document.createElement("style");h.textContent=`${v} { pointer-events: auto; }`,document.head.appendChild(h),b.push(()=>h.remove())}catch{}let d=v=>{let y=v.target?.closest?.(t);y&&r.call(y,v)};document.addEventListener(e,d,o);let l=()=>document.removeEventListener(e,d,o);return b.push(l),l},loop:e=>{let r=0,o=!1,d=0,l=v=>{if(!o)return;let h=d?Math.min((v-d)/1e3,.1):0;d=v;try{e(h,v)}catch(y){console.error("[star-canvas] Game loop error:",y)}_=null,A=null,G=[],r=requestAnimationFrame(l)};return S={get running(){return o},start(){o||(o=!0,d=performance.now(),r=requestAnimationFrame(l))},stop(){o&&(o=!1,cancelAnimationFrame(r))}},S.start(),S},ui:{root:s,render:e=>{s.innerHTML!==e&&(s.innerHTML=e)},el:e=>s.querySelector(e),all:e=>s.querySelectorAll(e)},destroy:()=>{S?.stop(),b.forEach(e=>e()),b=[],$.clear(),n.parentElement&&n.parentElement.removeChild(n),s.parentElement&&s.parentElement.removeChild(s),a.parentElement&&a.parentElement.removeChild(a)},scoped:e=>{p.save();try{e()}finally{p.restore()}}};a.addEventListener("pointerdown",e=>{let t=E.toStagePoint(e);if(w={x:t.x,y:t.y,down:!0},L.set(e.pointerId,{x:t.x,y:t.y,id:e.pointerId,down:!0}),R(e))return;_={x:t.x,y:t.y,time:e.timeStamp},G.push({x:t.x,y:t.y,id:e.pointerId,time:e.timeStamp});let r={...t,event:e};x.forEach(o=>o(r))}),a.addEventListener("pointermove",e=>{let t=E.toStagePoint(e);w={x:t.x,y:t.y,down:w.down};let r=L.get(e.pointerId);r&&L.set(e.pointerId,{...r,x:t.x,y:t.y});let o={...t,event:e};I.forEach(d=>d(o))}),a.addEventListener("pointerup",e=>{let t=E.toStagePoint(e);w={x:t.x,y:t.y,down:!1},A={x:t.x,y:t.y,time:e.timeStamp},L.delete(e.pointerId);let r={...t,event:e};k.forEach(o=>o(r))}),window.__STAR_DOM__={destroy:E.destroy},requestAnimationFrame(()=>requestAnimationFrame(()=>{f();try{u(E)}catch(e){console.error("[star-canvas] Game initialization failed:",e)}}))}function oe(u,i={}){let s={preset:"responsive",...i};return j(u,s)}export{U as createDragState,oe as game,ee as version};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "star-canvas",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "private": false,
5
5
  "description": "Canvas game utilities for reliable game initialization - part of Star SDK.",
6
6
  "type": "module",