@number10/phaserjsx 4.0.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +30 -0
  2. package/dist/chunk-C2EiDwsr.cjs +35 -0
  3. package/dist/clip/index.cjs +8 -0
  4. package/dist/clip/index.d.ts +3 -0
  5. package/dist/clip/index.d.ts.map +1 -0
  6. package/dist/clip/index.js +2 -0
  7. package/dist/clip/stencil-clip-depth.d.ts +10 -0
  8. package/dist/clip/stencil-clip-depth.d.ts.map +1 -0
  9. package/dist/clip/stencil-clip-extension.d.ts +18 -0
  10. package/dist/clip/stencil-clip-extension.d.ts.map +1 -0
  11. package/dist/clip/stencil-clip-fbo-bridge.d.ts +7 -0
  12. package/dist/clip/stencil-clip-fbo-bridge.d.ts.map +1 -0
  13. package/dist/clip/stencil-clip-renderer.d.ts +7 -0
  14. package/dist/clip/stencil-clip-renderer.d.ts.map +1 -0
  15. package/dist/clip/stencil-clip-state.d.ts +28 -0
  16. package/dist/clip/stencil-clip-state.d.ts.map +1 -0
  17. package/dist/clip/stencil-clip-types.d.ts +67 -0
  18. package/dist/clip/stencil-clip-types.d.ts.map +1 -0
  19. package/dist/clip/stencil-clip.d.ts +9 -42
  20. package/dist/clip/stencil-clip.d.ts.map +1 -1
  21. package/dist/clip-CHmjztBQ.cjs +705 -0
  22. package/dist/clip-CHmjztBQ.cjs.map +1 -0
  23. package/dist/clip-CPufWCSD.js +668 -0
  24. package/dist/clip-CPufWCSD.js.map +1 -0
  25. package/dist/components/appliers/applyBackground.d.ts +2 -1
  26. package/dist/components/appliers/applyBackground.d.ts.map +1 -1
  27. package/dist/components/appliers/applyTooltip.d.ts.map +1 -1
  28. package/dist/components/backgroundImage.d.ts +12 -0
  29. package/dist/components/backgroundImage.d.ts.map +1 -0
  30. package/dist/components/creators/createBackground.d.ts +2 -1
  31. package/dist/components/creators/createBackground.d.ts.map +1 -1
  32. package/dist/components/custom/Accordion.d.ts.map +1 -1
  33. package/dist/components/custom/DebugPanel.d.ts +30 -0
  34. package/dist/components/custom/DebugPanel.d.ts.map +1 -0
  35. package/dist/components/custom/Dropdown.d.ts.map +1 -1
  36. package/dist/components/custom/Toggle.d.ts.map +1 -1
  37. package/dist/components/custom/index.cjs +2 -1
  38. package/dist/components/custom/index.d.ts +1 -0
  39. package/dist/components/custom/index.d.ts.map +1 -1
  40. package/dist/components/custom/index.js +2 -2
  41. package/dist/components/primitives/graphics.d.ts +2 -2
  42. package/dist/components/primitives/view.d.ts.map +1 -1
  43. package/dist/{custom-oy3mBnrW.js → custom-BXDJDGOl.js} +439 -485
  44. package/dist/custom-BXDJDGOl.js.map +1 -0
  45. package/dist/{custom-BN31OAJq.cjs → custom-DTd4LxDn.cjs} +459 -518
  46. package/dist/custom-DTd4LxDn.cjs.map +1 -0
  47. package/dist/gestures/gesture-manager.d.ts +1 -1
  48. package/dist/hooks.d.ts +9 -8
  49. package/dist/hooks.d.ts.map +1 -1
  50. package/dist/index.cjs +105 -106
  51. package/dist/index.cjs.map +1 -1
  52. package/dist/index.d.ts +1 -0
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +96 -105
  55. package/dist/index.js.map +1 -1
  56. package/dist/layout/appliers/background-applier.d.ts.map +1 -1
  57. package/dist/layout/layout-engine.d.ts.map +1 -1
  58. package/dist/layout/types.d.ts +2 -1
  59. package/dist/layout/types.d.ts.map +1 -1
  60. package/dist/scene-backgrounds.d.ts +51 -1
  61. package/dist/scene-backgrounds.d.ts.map +1 -1
  62. package/package.json +8 -2
  63. package/dist/custom-BN31OAJq.cjs.map +0 -1
  64. package/dist/custom-oy3mBnrW.js.map +0 -1
@@ -16571,6 +16571,111 @@ var tileSpritePatcher = (_node, _prev, _next) => {
16571
16571
  throw new Error("TileSprite component not implemented yet. This is a placeholder for architecture planning.");
16572
16572
  };
16573
16573
  //#endregion
16574
+ //#region src/components/backgroundImage.ts
16575
+ var sceneBackgroundTextureCaches = /* @__PURE__ */ new WeakMap();
16576
+ function hasRenderableBackground(props) {
16577
+ const hasBackground = props.backgroundColor !== void 0;
16578
+ const hasBorder = (props.borderWidth ?? 0) > 0 && props.borderColor !== void 0;
16579
+ return hasBackground || hasBorder;
16580
+ }
16581
+ function drawBackground(graphics, props, width, height) {
16582
+ const bgColor = props.backgroundColor;
16583
+ const bgAlpha = props.backgroundAlpha ?? 1;
16584
+ const cornerRadius = props.cornerRadius ?? 0;
16585
+ const borderColor = props.borderColor;
16586
+ const borderWidth = props.borderWidth ?? 0;
16587
+ const borderAlpha = props.borderAlpha ?? 1;
16588
+ const hasBorder = borderWidth > 0 && borderColor !== void 0;
16589
+ if (bgColor !== void 0) graphics.fillStyle(bgColor, bgAlpha);
16590
+ if (hasBorder) graphics.lineStyle(borderWidth, borderColor, borderAlpha);
16591
+ if (cornerRadius !== 0) {
16592
+ if (bgColor !== void 0) graphics.fillRoundedRect(0, 0, width, height, cornerRadius);
16593
+ if (hasBorder) graphics.strokeRoundedRect(0, 0, width, height, cornerRadius);
16594
+ } else {
16595
+ if (bgColor !== void 0) graphics.fillRect(0, 0, width, height);
16596
+ if (hasBorder) graphics.strokeRect(0, 0, width, height);
16597
+ }
16598
+ }
16599
+ function getSceneBackgroundTextureCache(scene) {
16600
+ let cache = sceneBackgroundTextureCaches.get(scene);
16601
+ if (!cache) {
16602
+ cache = /* @__PURE__ */ new Map();
16603
+ sceneBackgroundTextureCaches.set(scene, cache);
16604
+ }
16605
+ return cache;
16606
+ }
16607
+ function hashCacheKey(value) {
16608
+ let hash = 2166136261;
16609
+ for (let i = 0; i < value.length; i++) {
16610
+ hash ^= value.charCodeAt(i);
16611
+ hash = Math.imul(hash, 16777619);
16612
+ }
16613
+ return (hash >>> 0).toString(36);
16614
+ }
16615
+ function normalizeCornerRadius$1(radius) {
16616
+ if (typeof radius === "number") return String(radius);
16617
+ if (!radius) return "0";
16618
+ return `${radius.tl ?? 0},${radius.tr ?? 0},${radius.bl ?? 0},${radius.br ?? 0}`;
16619
+ }
16620
+ function getBackgroundImageCacheKey(props, width, height) {
16621
+ return [
16622
+ Math.max(1, Math.ceil(width)),
16623
+ Math.max(1, Math.ceil(height)),
16624
+ props.backgroundColor ?? "none",
16625
+ props.backgroundAlpha ?? 1,
16626
+ normalizeCornerRadius$1(props.cornerRadius),
16627
+ props.borderWidth ?? 0,
16628
+ props.borderColor ?? "none",
16629
+ props.borderAlpha ?? 1
16630
+ ].join("|");
16631
+ }
16632
+ function createBackgroundImage(scene, props, width, height) {
16633
+ if (!hasRenderableBackground(props)) return;
16634
+ const textureWidth = Math.max(1, Math.ceil(width));
16635
+ const textureHeight = Math.max(1, Math.ceil(height));
16636
+ const cacheKey = getBackgroundImageCacheKey(props, textureWidth, textureHeight);
16637
+ const textureKey = `__phaserjsx_bg_${hashCacheKey(cacheKey)}`;
16638
+ const cache = getSceneBackgroundTextureCache(scene);
16639
+ let entry = cache.get(cacheKey);
16640
+ if (!entry || !scene.textures.exists(entry.textureKey)) {
16641
+ const graphics = scene.add.graphics();
16642
+ drawBackground(graphics, props, textureWidth, textureHeight);
16643
+ graphics.generateTexture(textureKey, textureWidth, textureHeight);
16644
+ graphics.destroy();
16645
+ entry = {
16646
+ textureKey,
16647
+ refs: 0
16648
+ };
16649
+ cache.set(cacheKey, entry);
16650
+ }
16651
+ entry.refs++;
16652
+ const background = scene.add.image(0, 0, entry.textureKey);
16653
+ background.setOrigin(0, 0);
16654
+ background.__isBackground = true;
16655
+ background.__backgroundCacheKey = cacheKey;
16656
+ background.__backgroundTextureKey = entry.textureKey;
16657
+ background.once("destroy", () => releaseBackgroundImageTexture(background));
16658
+ return background;
16659
+ }
16660
+ function releaseBackgroundImageTexture(background) {
16661
+ if (background.__backgroundTextureReleased) return;
16662
+ const scene = background.scene;
16663
+ const cacheKey = background.__backgroundCacheKey;
16664
+ background.__backgroundTextureReleased = true;
16665
+ if (!scene || !cacheKey) return;
16666
+ const cache = sceneBackgroundTextureCaches.get(scene);
16667
+ const entry = cache?.get(cacheKey);
16668
+ if (!cache || !entry) return;
16669
+ entry.refs--;
16670
+ if (entry.refs <= 0) {
16671
+ if (scene.textures.exists(entry.textureKey)) scene.textures.remove(entry.textureKey);
16672
+ cache.delete(cacheKey);
16673
+ }
16674
+ }
16675
+ function destroyBackgroundImage(background) {
16676
+ background.destroy();
16677
+ }
16678
+ //#endregion
16574
16679
  //#region src/components/appliers/applyBackground.ts
16575
16680
  /**
16576
16681
  * Applies background properties (color, alpha, corner radius, border)
@@ -16609,37 +16714,24 @@ function applyBackgroundProps(container, prev, next) {
16609
16714
  const nextHasGraphics = nextBgColor !== void 0 || nextHasBorder;
16610
16715
  if (prevHasGraphics && !nextHasGraphics) {
16611
16716
  if (container.__background) {
16612
- container.__background.destroy();
16717
+ destroyBackgroundImage(container.__background);
16613
16718
  delete container.__background;
16614
16719
  }
16615
16720
  } else if (!prevHasGraphics && nextHasGraphics) {
16616
16721
  if (container.scene) {
16617
- const background = container.scene.add.graphics();
16618
- if (nextBgColor !== void 0) background.fillStyle(nextBgColor, nextBgAlpha);
16619
- if (nextHasBorder) background.lineStyle(nextBorderWidth, nextBorderColor, nextBorderAlpha);
16620
- if (nextCornerRadius !== 0) {
16621
- if (nextBgColor !== void 0) background.fillRoundedRect(0, 0, nextWidth, nextHeight, nextCornerRadius);
16622
- if (nextHasBorder) background.strokeRoundedRect(0, 0, nextWidth, nextHeight, nextCornerRadius);
16623
- } else {
16624
- if (nextBgColor !== void 0) background.fillRect(0, 0, nextWidth, nextHeight);
16625
- if (nextHasBorder) background.strokeRect(0, 0, nextWidth, nextHeight);
16626
- }
16722
+ const background = createBackgroundImage(container.scene, next, nextWidth, nextHeight);
16723
+ if (!background) return;
16627
16724
  container.addAt(background, 0);
16628
16725
  container.__background = background;
16629
- background.__isBackground = true;
16630
16726
  }
16631
16727
  } else if (container.__background && nextHasGraphics) {
16632
16728
  if (prevBgColor !== nextBgColor || prevBgAlpha !== nextBgAlpha || prevWidth !== nextWidth || prevHeight !== nextHeight || prevCornerRadius !== nextCornerRadius || prevBorderWidth !== nextBorderWidth || prevBorderColor !== nextBorderColor || prevBorderAlpha !== nextBorderAlpha) {
16633
- container.__background.clear();
16634
- if (nextBgColor !== void 0) container.__background.fillStyle(nextBgColor, nextBgAlpha);
16635
- if (nextHasBorder) container.__background.lineStyle(nextBorderWidth, nextBorderColor, nextBorderAlpha);
16636
- if (nextCornerRadius !== 0) {
16637
- if (nextBgColor !== void 0) container.__background.fillRoundedRect(0, 0, nextWidth, nextHeight, nextCornerRadius);
16638
- if (nextHasBorder) container.__background.strokeRoundedRect(0, 0, nextWidth, nextHeight, nextCornerRadius);
16639
- } else {
16640
- if (nextBgColor !== void 0) container.__background.fillRect(0, 0, nextWidth, nextHeight);
16641
- if (nextHasBorder) container.__background.strokeRect(0, 0, nextWidth, nextHeight);
16642
- }
16729
+ const oldBackground = container.__background;
16730
+ const background = createBackgroundImage(container.scene, next, nextWidth, nextHeight);
16731
+ if (!background) return;
16732
+ container.addAt(background, 0);
16733
+ container.__background = background;
16734
+ destroyBackgroundImage(oldBackground);
16643
16735
  }
16644
16736
  }
16645
16737
  }
@@ -16856,7 +16948,7 @@ var GestureManager = class {
16856
16948
  /**
16857
16949
  * Handle global pointer down event
16858
16950
  * Registers all containers that were hit for move event tracking
16859
- * Only the topmost gets touch/longpress callbacks
16951
+ * Only the topmost gets touch/long-press callbacks
16860
16952
  */
16861
16953
  handlePointerDown(pointer) {
16862
16954
  const hitContainers = /* @__PURE__ */ new Set();
@@ -17410,403 +17502,6 @@ function applyGesturesProps(scene, container, prev, next) {
17410
17502
  }
17411
17503
  }
17412
17504
  //#endregion
17413
- //#region src/clip/stencil-clip.ts
17414
- /**
17415
- * WebGL stencil-buffer clip for Phaser 4 Containers.
17416
- *
17417
- * Supports arbitrary nesting via the INCR/DECR model: each clip level
17418
- * increments the stencil on enter and decrements on exit, so child clips are
17419
- * automatically intersected with their parent clips at the hardware level.
17420
- *
17421
- * Shape variants:
17422
- * - Plain rectangle (cornerRadius omitted or 0)
17423
- * - Rounded rectangle (uniform radius or per-corner object)
17424
- *
17425
- * A single SDF-based shader handles both variants. For plain rectangles
17426
- * u_radii is vec4(0) and the `discard` branch never fires — no overhead
17427
- * compared to a rectangle-only shader.
17428
- *
17429
- * Transforms (translate, scale, rotation) are fully supported: the quad
17430
- * corners are transformed through `container.getWorldTransformMatrix()` at
17431
- * render time, so no per-layout world-position tracking is needed.
17432
- */
17433
- /**
17434
- * Shared depth counter per GL context.
17435
- * Incremented before each clip's INCR pass, decremented after the DECR pass.
17436
- * Reset to 0 at the start of every frame via a `prerender` hook.
17437
- */
17438
- var _depthByGl = /* @__PURE__ */ new WeakMap();
17439
- function getDepth(gl) {
17440
- let d = _depthByGl.get(gl);
17441
- if (!d) {
17442
- d = { value: 0 };
17443
- _depthByGl.set(gl, d);
17444
- }
17445
- return d;
17446
- }
17447
- var _prerenderHooked = /* @__PURE__ */ new WeakSet();
17448
- /**
17449
- * Registers a per-frame `prerender` listener that resets the depth counter.
17450
- * Registered at most once per Phaser.Game instance.
17451
- */
17452
- function ensurePrerenderReset(gl, game) {
17453
- if (_prerenderHooked.has(game)) return;
17454
- _prerenderHooked.add(game);
17455
- game.events.on("prerender", () => {
17456
- const d = _depthByGl.get(gl);
17457
- if (d) d.value = 0;
17458
- const fbo = _fboPatchByGl.get(gl);
17459
- if (fbo) {
17460
- fbo.current = null;
17461
- fbo.saved = null;
17462
- }
17463
- });
17464
- }
17465
- var _fboPatchByGl = /* @__PURE__ */ new WeakMap();
17466
- /**
17467
- * Patches `gl.bindFramebuffer` once per GL context so that the stencil test
17468
- * is automatically disabled when Phaser switches to an off-screen FBO (for
17469
- * PostFX / RenderTexture rendering) and restored when switching back.
17470
- *
17471
- * Without this, a PostFX child rendered inside a stencil-clipped container
17472
- * would be invisible: the FBO's stencil buffer starts at 0 while the active
17473
- * stencil test requires `EQUAL(myDepth + 1)`, causing every fragment to fail.
17474
- *
17475
- * The patch is installed once per GL context and remains active for the
17476
- * lifetime of the renderer. It is a no-op when no stencil clip is active.
17477
- */
17478
- function ensureFboPatch(gl) {
17479
- if (_fboPatchByGl.has(gl)) return;
17480
- const state = {
17481
- current: null,
17482
- saved: null
17483
- };
17484
- _fboPatchByGl.set(gl, state);
17485
- const origBind = gl.bindFramebuffer.bind(gl);
17486
- gl.bindFramebuffer = (target, fb) => {
17487
- const wasMain = state.current === null;
17488
- const willBeMain = fb === null;
17489
- const enteringOffscreen = wasMain && !willBeMain;
17490
- const leavingOffscreen = !wasMain && willBeMain;
17491
- if (enteringOffscreen && gl.getParameter(gl.STENCIL_TEST)) {
17492
- state.saved = {
17493
- func: gl.getParameter(gl.STENCIL_FUNC),
17494
- ref: gl.getParameter(gl.STENCIL_REF),
17495
- valueMask: gl.getParameter(gl.STENCIL_VALUE_MASK),
17496
- fail: gl.getParameter(gl.STENCIL_FAIL),
17497
- zfail: gl.getParameter(gl.STENCIL_PASS_DEPTH_FAIL),
17498
- zpass: gl.getParameter(gl.STENCIL_PASS_DEPTH_PASS),
17499
- writeMask: gl.getParameter(gl.STENCIL_WRITEMASK)
17500
- };
17501
- gl.disable(gl.STENCIL_TEST);
17502
- }
17503
- origBind(target, fb);
17504
- state.current = fb;
17505
- if (leavingOffscreen && state.saved) {
17506
- gl.enable(gl.STENCIL_TEST);
17507
- gl.stencilFunc(state.saved.func, state.saved.ref, state.saved.valueMask);
17508
- gl.stencilOp(state.saved.fail, state.saved.zfail, state.saved.zpass);
17509
- gl.stencilMask(state.saved.writeMask);
17510
- state.saved = null;
17511
- }
17512
- };
17513
- }
17514
- /**
17515
- * Vertex attributes:
17516
- * a_ndc vec2 — NDC clip-space position (CPU-computed from world transform)
17517
- * a_loc vec2 — position relative to the clip rect's center (for SDF)
17518
- *
17519
- * All four corners are pre-transformed on the CPU so the vertex shader is a
17520
- * pure pass-through. This correctly handles translation, scale, and rotation
17521
- * without a matrix uniform.
17522
- */
17523
- var VERT_SRC = `
17524
- attribute vec2 a_ndc;
17525
- attribute vec2 a_loc;
17526
- varying vec2 v_loc;
17527
- void main(){gl_Position=vec4(a_ndc,0.,1.);v_loc=a_loc;}
17528
- `;
17529
- /**
17530
- * SDF rounded-rectangle fragment shader.
17531
- *
17532
- * sdRoundedBox uses the IQ per-corner-radius technique:
17533
- * r.xy = p.x > 0 ? r.yz : r.xw (right → tr/br, left → tl/bl)
17534
- * r.x = p.y > 0 ? r.y : r.x (bottom or top within that pair)
17535
- *
17536
- * u_radii layout: (tl, tr, br, bl).
17537
- *
17538
- * For plain rectangles u_radii = vec4(0.0) and sdRoundedBox returns ≤ 0 for
17539
- * all fragments inside the quad, so `discard` is never executed.
17540
- */
17541
- var FRAG_SRC = `
17542
- precision mediump float;
17543
- varying vec2 v_loc;
17544
- uniform vec2 u_halfSize;
17545
- uniform vec4 u_radii;
17546
- float sdRoundedBox(vec2 p,vec2 b,vec4 r){
17547
- r.xy=p.x>0.?r.yz:r.xw;
17548
- r.x =p.y>0.?r.y :r.x;
17549
- vec2 q=abs(p)-b+r.x;
17550
- return length(max(q,0.))+min(max(q.x,q.y),0.)-r.x;
17551
- }
17552
- void main(){
17553
- if(sdRoundedBox(v_loc,u_halfSize,u_radii)>0.)discard;
17554
- gl_FragColor=vec4(0.);
17555
- }
17556
- `;
17557
- var _progByGl = /* @__PURE__ */ new WeakMap();
17558
- /**
17559
- * Returns (or lazily creates) the SDF stencil shader program for a GL context.
17560
- * @param gl - The WebGL context.
17561
- * @returns Compiled and linked WebGLProgram.
17562
- */
17563
- function getStencilProg(gl) {
17564
- let prog = _progByGl.get(gl);
17565
- if (prog) return prog;
17566
- const vs = gl.createShader(gl.VERTEX_SHADER);
17567
- gl.shaderSource(vs, VERT_SRC);
17568
- gl.compileShader(vs);
17569
- const fs = gl.createShader(gl.FRAGMENT_SHADER);
17570
- gl.shaderSource(fs, FRAG_SRC);
17571
- gl.compileShader(fs);
17572
- prog = gl.createProgram();
17573
- gl.attachShader(prog, vs);
17574
- gl.attachShader(prog, fs);
17575
- gl.linkProgram(prog);
17576
- _progByGl.set(gl, prog);
17577
- return prog;
17578
- }
17579
- var _locsByProg = /* @__PURE__ */ new WeakMap();
17580
- /**
17581
- * Returns (or resolves and caches) the attribute/uniform locations for a program.
17582
- * @param gl - The WebGL context.
17583
- * @param prog - The shader program.
17584
- * @returns Cached locations.
17585
- */
17586
- function getShaderLocs(gl, prog) {
17587
- let l = _locsByProg.get(prog);
17588
- if (!l) {
17589
- l = {
17590
- ndc: gl.getAttribLocation(prog, "a_ndc"),
17591
- loc: gl.getAttribLocation(prog, "a_loc"),
17592
- halfSize: gl.getUniformLocation(prog, "u_halfSize"),
17593
- radii: gl.getUniformLocation(prog, "u_radii")
17594
- };
17595
- _locsByProg.set(prog, l);
17596
- }
17597
- return l;
17598
- }
17599
- /**
17600
- * Resolves the `cornerRadius` field to `[tl, tr, br, bl]` order matching the
17601
- * `u_radii` vec4 uniform layout.
17602
- * @param r - Raw corner radius value from the clip shape.
17603
- * @returns Tuple `[tl, tr, br, bl]`.
17604
- */
17605
- function resolveRadii(r) {
17606
- if (!r) return [
17607
- 0,
17608
- 0,
17609
- 0,
17610
- 0
17611
- ];
17612
- if (typeof r === "number") return [
17613
- r,
17614
- r,
17615
- r,
17616
- r
17617
- ];
17618
- return [
17619
- r.tl ?? 0,
17620
- r.tr ?? 0,
17621
- r.br ?? 0,
17622
- r.bl ?? 0
17623
- ];
17624
- }
17625
- /** Stride in bytes: 4 floats × 4 bytes. */
17626
- var STRIDE = 16;
17627
- /**
17628
- * Transforms the four clip-rect corners through the container's world matrix,
17629
- * uploads them to the vertex buffer, and draws the quad into the stencil.
17630
- *
17631
- * Vertex buffer layout (16 floats / 4 vertices, TRIANGLE_FAN, TL→TR→BR→BL):
17632
- * [ndcX, ndcY, locX, locY] × 4
17633
- * where locX/Y is the position relative to the clip rect's center (for SDF).
17634
- *
17635
- * The three GL states tracked by Phaser's `WebGLGlobalWrapper`
17636
- * (`CURRENT_PROGRAM`, `ARRAY_BUFFER_BINDING`, `VERTEX_ARRAY_BINDING`) are
17637
- * saved and restored so its internal caches stay consistent.
17638
- *
17639
- * @param gl - The (VAO-polyfilled) WebGL context.
17640
- * @param matrix - Container's current world transform matrix.
17641
- * @param offsetX - Top-left X of the clip rect in local space.
17642
- * @param offsetY - Top-left Y of the clip rect in local space.
17643
- * @param w - Clip rect width in local units.
17644
- * @param h - Clip rect height in local units.
17645
- * @param logW - Logical game width (`renderer.width`).
17646
- * @param logH - Logical game height (`renderer.height`).
17647
- * @param radii - Per-corner radii tuple `[tl, tr, br, bl]`.
17648
- * @param vertBuf - Persistent WebGLBuffer (64 bytes, DYNAMIC_DRAW).
17649
- * @param verts - Reusable Float32Array(16) to avoid per-frame allocation.
17650
- */
17651
- function drawMaskShape(gl, matrix, offsetX, offsetY, w, h, logW, logH, radii, vertBuf, verts) {
17652
- const { a, b, c, d, tx, ty } = matrix;
17653
- const hw = w / 2;
17654
- const hh = h / 2;
17655
- const cx = offsetX + hw;
17656
- const cy = offsetY + hh;
17657
- const corners = [
17658
- [cx - hw, cy - hh],
17659
- [cx + hw, cy - hh],
17660
- [cx + hw, cy + hh],
17661
- [cx - hw, cy + hh]
17662
- ];
17663
- for (let i = 0; i < 4; i++) {
17664
- const corner = corners[i];
17665
- const lx = corner[0];
17666
- const ly = corner[1];
17667
- const wx = a * lx + c * ly + tx;
17668
- const wy = b * lx + d * ly + ty;
17669
- verts[i * 4 + 0] = wx / logW * 2 - 1;
17670
- verts[i * 4 + 1] = 1 - wy / logH * 2;
17671
- verts[i * 4 + 2] = lx - cx;
17672
- verts[i * 4 + 3] = ly - cy;
17673
- }
17674
- const prevProg = gl.getParameter(gl.CURRENT_PROGRAM);
17675
- const prevBuf = gl.getParameter(gl.ARRAY_BUFFER_BINDING);
17676
- const prevVAO = gl.getParameter(34229);
17677
- gl.bindVertexArray(null);
17678
- gl.bindBuffer(gl.ARRAY_BUFFER, vertBuf);
17679
- gl.bufferSubData(gl.ARRAY_BUFFER, 0, verts);
17680
- const prog = getStencilProg(gl);
17681
- gl.useProgram(prog);
17682
- const locs = getShaderLocs(gl, prog);
17683
- gl.enableVertexAttribArray(locs.ndc);
17684
- gl.vertexAttribPointer(locs.ndc, 2, gl.FLOAT, false, STRIDE, 0);
17685
- gl.enableVertexAttribArray(locs.loc);
17686
- gl.vertexAttribPointer(locs.loc, 2, gl.FLOAT, false, STRIDE, 8);
17687
- gl.uniform2f(locs.halfSize, hw, hh);
17688
- gl.uniform4f(locs.radii, radii[0], radii[1], radii[2], radii[3]);
17689
- gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
17690
- gl.disableVertexAttribArray(locs.ndc);
17691
- gl.disableVertexAttribArray(locs.loc);
17692
- gl.bindBuffer(gl.ARRAY_BUFFER, prevBuf);
17693
- gl.useProgram(prevProg);
17694
- gl.bindVertexArray(prevVAO);
17695
- }
17696
- var STENCIL_HANDLE = Symbol("stencilClipHandle");
17697
- /**
17698
- * Applies a WebGL stencil-buffer clip to a Phaser 4 Container.
17699
- *
17700
- * The clip rectangle is expressed in the container's **local coordinate
17701
- * space**. `offsetX`/`offsetY` mark the top-left corner (default 0/0), so
17702
- * a container whose visual area starts at its local origin can be clipped with
17703
- * `applyStencilClip(container, { width, height })`.
17704
- *
17705
- * **Nesting** is handled transparently: each clip level occupies one stencil
17706
- * value (0 = no clip, 1 = depth 0, 2 = depth 1, …). Child clips are always
17707
- * the intersection of their own shape and all ancestor shapes.
17708
- *
17709
- * **Transforms** (translate, scale, rotation) are re-evaluated from
17710
- * `container.getWorldTransformMatrix()` on every frame, so animated or
17711
- * scroll-driven containers clip correctly without any manual update call.
17712
- *
17713
- * If a stencil clip is already attached to the container, calling this
17714
- * function again calls `handle.update(shape)` on the existing handle and
17715
- * returns it.
17716
- *
17717
- * @param container - The container to clip.
17718
- * @param shape - Clip rect geometry in local units.
17719
- * @returns A handle to modify dimensions / corner radii or remove the clip.
17720
- */
17721
- function applyStencilClip(container, shape) {
17722
- const obj = container;
17723
- if (obj[STENCIL_HANDLE]) {
17724
- obj[STENCIL_HANDLE].update(shape);
17725
- return obj[STENCIL_HANDLE];
17726
- }
17727
- if (container.scene.renderer.type !== Phaser.WEBGL) return {
17728
- update() {},
17729
- destroy() {}
17730
- };
17731
- const gl = container.scene.renderer.gl;
17732
- ensurePrerenderReset(gl, container.scene.game);
17733
- ensureFboPatch(gl);
17734
- const vertBuf = gl.createBuffer();
17735
- gl.bindBuffer(gl.ARRAY_BUFFER, vertBuf);
17736
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(16), gl.DYNAMIC_DRAW);
17737
- gl.bindBuffer(gl.ARRAY_BUFFER, null);
17738
- const verts = new Float32Array(16);
17739
- let clipW = shape.width;
17740
- let clipH = shape.height;
17741
- let clipOffsetX = shape.offsetX ?? 0;
17742
- let clipOffsetY = shape.offsetY ?? 0;
17743
- let radii = resolveRadii(shape.cornerRadius);
17744
- let destroyed = false;
17745
- const wrapper = (webglRenderer, go, drawingContext, parentMatrix, renderStep = 0, displayList, displayListIndex) => {
17746
- const renderNext = () => {
17747
- go.renderWebGLStep(webglRenderer, go, drawingContext, parentMatrix, renderStep + 1, displayList, displayListIndex);
17748
- };
17749
- if (destroyed) {
17750
- renderNext();
17751
- return;
17752
- }
17753
- const matrix = container.getWorldTransformMatrix();
17754
- const rn = webglRenderer.renderNodes;
17755
- const depth = getDepth(gl);
17756
- const myDepth = depth.value++;
17757
- const logW = webglRenderer.width;
17758
- const logH = webglRenderer.height;
17759
- rn.finishBatch();
17760
- if (myDepth === 0) gl.enable(gl.STENCIL_TEST);
17761
- gl.colorMask(false, false, false, false);
17762
- gl.stencilMask(255);
17763
- gl.stencilFunc(gl.EQUAL, myDepth, 255);
17764
- gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR);
17765
- drawMaskShape(gl, matrix, clipOffsetX, clipOffsetY, clipW, clipH, logW, logH, radii, vertBuf, verts);
17766
- gl.colorMask(true, true, true, true);
17767
- gl.stencilFunc(gl.EQUAL, myDepth + 1, 255);
17768
- gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
17769
- gl.stencilMask(0);
17770
- renderNext();
17771
- rn.finishBatch();
17772
- gl.colorMask(false, false, false, false);
17773
- gl.stencilMask(255);
17774
- gl.stencilFunc(gl.EQUAL, myDepth + 1, 255);
17775
- gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR);
17776
- drawMaskShape(gl, matrix, clipOffsetX, clipOffsetY, clipW, clipH, logW, logH, radii, vertBuf, verts);
17777
- gl.colorMask(true, true, true, true);
17778
- depth.value--;
17779
- if (myDepth === 0) {
17780
- gl.disable(gl.STENCIL_TEST);
17781
- gl.stencilMask(255);
17782
- } else {
17783
- gl.stencilFunc(gl.EQUAL, myDepth, 255);
17784
- gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
17785
- gl.stencilMask(0);
17786
- }
17787
- };
17788
- const handle = {
17789
- update(s) {
17790
- if (s.width !== void 0) clipW = s.width;
17791
- if (s.height !== void 0) clipH = s.height;
17792
- if (s.offsetX !== void 0) clipOffsetX = s.offsetX;
17793
- if (s.offsetY !== void 0) clipOffsetY = s.offsetY;
17794
- if ("cornerRadius" in s) radii = resolveRadii(s.cornerRadius);
17795
- },
17796
- destroy() {
17797
- if (destroyed) return;
17798
- destroyed = true;
17799
- gl.deleteBuffer(vertBuf);
17800
- const index = obj._renderSteps.indexOf(wrapper);
17801
- if (index !== -1) obj._renderSteps.splice(index, 1);
17802
- delete obj[STENCIL_HANDLE];
17803
- }
17804
- };
17805
- obj.addRenderStep(wrapper, 0);
17806
- obj[STENCIL_HANDLE] = handle;
17807
- return handle;
17808
- }
17809
- //#endregion
17810
17505
  //#region src/core-props.ts
17811
17506
  /**
17812
17507
  * Normalize edge insets - converts number to all-sides object
@@ -17865,23 +17560,13 @@ function updateBackground(container, width, height) {
17865
17560
  if (background) {
17866
17561
  const layoutProps = container.__layoutProps;
17867
17562
  if (layoutProps) {
17868
- const bgColor = layoutProps.backgroundColor;
17869
- const bgAlpha = layoutProps.backgroundAlpha ?? 1;
17870
- const cornerRadius = layoutProps.cornerRadius ?? 0;
17871
- const borderWidth = layoutProps.borderWidth ?? 0;
17872
- const borderColor = layoutProps.borderColor;
17873
- const borderAlpha = layoutProps.borderAlpha ?? 1;
17874
- const hasBorder = borderWidth > 0 && borderColor !== void 0;
17875
- background.clear();
17876
- if (bgColor !== void 0) background.fillStyle(bgColor, bgAlpha);
17877
- if (hasBorder) background.lineStyle(borderWidth, borderColor, borderAlpha);
17878
- if (cornerRadius !== 0) {
17879
- if (bgColor !== void 0) background.fillRoundedRect(0, 0, width, height, cornerRadius);
17880
- if (hasBorder) background.strokeRoundedRect(0, 0, width, height, cornerRadius);
17881
- } else {
17882
- if (bgColor !== void 0) background.fillRect(0, 0, width, height);
17883
- if (hasBorder) background.strokeRect(0, 0, width, height);
17884
- }
17563
+ const cacheKey = getBackgroundImageCacheKey(layoutProps, width, height);
17564
+ if (background.__backgroundCacheKey === cacheKey) return;
17565
+ const nextBackground = createBackgroundImage(container.scene, layoutProps, width, height);
17566
+ if (!nextBackground) return;
17567
+ container.addAt(nextBackground, 0);
17568
+ container.__background = nextBackground;
17569
+ destroyBackgroundImage(background);
17885
17570
  DebugLogger.log("layout", "Background redrawn to:", {
17886
17571
  width,
17887
17572
  height
@@ -18838,12 +18523,10 @@ function applyOverflowMask(container, containerProps, width, height) {
18838
18523
  height
18839
18524
  };
18840
18525
  if (containerProps.cornerRadius !== void 0) shapeBase.cornerRadius = containerProps.cornerRadius;
18841
- const handle = applyStencilClip(container, shapeBase);
18526
+ container.setStencilClip(shapeBase);
18527
+ const handle = container.getStencilClipHandle();
18528
+ if (!handle) return;
18842
18529
  ext.__overflowClip = handle;
18843
- container.once("destroy", () => {
18844
- handle.destroy();
18845
- delete ext.__overflowClip;
18846
- });
18847
18530
  DebugLogger.log("overflowMask", "Created stencil clip");
18848
18531
  } else {
18849
18532
  const update = {
@@ -18851,10 +18534,10 @@ function applyOverflowMask(container, containerProps, width, height) {
18851
18534
  height,
18852
18535
  cornerRadius: containerProps.cornerRadius ?? 0
18853
18536
  };
18854
- ext.__overflowClip.update(update);
18537
+ container.updateStencilClip(update);
18855
18538
  }
18856
18539
  else if (ext.__overflowClip) {
18857
- ext.__overflowClip.destroy();
18540
+ container.clearStencilClip();
18858
18541
  delete ext.__overflowClip;
18859
18542
  DebugLogger.log("overflowMask", "Removed stencil clip");
18860
18543
  }
@@ -18872,6 +18555,7 @@ var LAYOUT_CYCLE_TIME_MS = 20;
18872
18555
  var LAYOUT_CYCLE_MAX = 5;
18873
18556
  var LAYOUT_MAX_SIZE = 2e5;
18874
18557
  var layoutCycleGuard = /* @__PURE__ */ new WeakMap();
18558
+ var deferredParentInvalidations = /* @__PURE__ */ new WeakSet();
18875
18559
  function isCloseSize(a, b, epsilon) {
18876
18560
  return Math.abs(a.width - b.width) < epsilon && Math.abs(a.height - b.height) < epsilon;
18877
18561
  }
@@ -18895,6 +18579,8 @@ function invalidateParentLayoutIfNeeded(container, oldSize, newWidth, newHeight)
18895
18579
  height: newHeight
18896
18580
  };
18897
18581
  const now = Date.now();
18582
+ const parent = container.parentContainer;
18583
+ if (!parent || !parent.__layoutProps) return;
18898
18584
  const guard = layoutCycleGuard.get(container);
18899
18585
  if (guard) {
18900
18586
  if ((guard.prev ? isCloseSize(guard.prev, newSize, LAYOUT_CYCLE_EPSILON) : false) && now - guard.lastTime < LAYOUT_CYCLE_TIME_MS) guard.count += 1;
@@ -18904,11 +18590,15 @@ function invalidateParentLayoutIfNeeded(container, oldSize, newWidth, newHeight)
18904
18590
  guard.lastTime = now;
18905
18591
  layoutCycleGuard.set(container, guard);
18906
18592
  if (guard.count >= LAYOUT_CYCLE_MAX) {
18907
- DebugLogger.warn("layout", "Layout cycle detected, skipping parent invalidation", {
18593
+ DebugLogger.log("layout", "Layout cycle detected, deferring parent invalidation", {
18908
18594
  container,
18909
18595
  oldSize,
18910
18596
  newSize
18911
18597
  });
18598
+ guard.count = 0;
18599
+ guard.lastTime = 0;
18600
+ layoutCycleGuard.set(container, guard);
18601
+ deferParentLayoutInvalidation(container);
18912
18602
  return;
18913
18603
  }
18914
18604
  } else layoutCycleGuard.set(container, {
@@ -18916,9 +18606,20 @@ function invalidateParentLayoutIfNeeded(container, oldSize, newWidth, newHeight)
18916
18606
  count: 0,
18917
18607
  lastTime: now
18918
18608
  });
18609
+ DebugLogger.log("layout", "Container size changed, invalidating parent:", `${oldSize.width}x${oldSize.height} -> ${newWidth}x${newHeight}`);
18610
+ invalidateParentLayout(container);
18611
+ }
18612
+ function deferParentLayoutInvalidation(container) {
18613
+ if (deferredParentInvalidations.has(container)) return;
18614
+ deferredParentInvalidations.add(container);
18615
+ DeferredLayoutQueue.defer(() => {
18616
+ deferredParentInvalidations.delete(container);
18617
+ invalidateParentLayout(container);
18618
+ });
18619
+ }
18620
+ function invalidateParentLayout(container) {
18919
18621
  const parent = container.parentContainer;
18920
18622
  if (!parent || !parent.__layoutProps) return;
18921
- DebugLogger.log("layout", "Container size changed, invalidating parent:", `${oldSize.width}x${oldSize.height} -> ${newWidth}x${newHeight}`);
18922
18623
  const grandParent = parent.parentContainer;
18923
18624
  let grandParentSize;
18924
18625
  if (grandParent && grandParent.__getLayoutSize) {
@@ -20948,22 +20649,22 @@ function getLayoutProps(container) {
20948
20649
  return container.__layoutProps;
20949
20650
  }
20950
20651
  /**
20951
- * Utility function to get background graphics from a container
20952
- * Returns the Graphics object used for backgroundColor, border, cornerRadius
20652
+ * Utility function to get background object from a container
20653
+ * Returns the Image object used for backgroundColor, border, cornerRadius
20953
20654
  * Useful for custom animations or modifications
20954
20655
  * @param container - Phaser container with background
20955
- * @returns Graphics object or undefined if container has no background
20656
+ * @returns Background image or undefined if container has no background
20956
20657
  */
20957
20658
  function getBackgroundGraphics(container) {
20958
20659
  if (!container) return void 0;
20959
20660
  return container.__background;
20960
20661
  }
20961
20662
  /**
20962
- * Hook to get background graphics from a ref
20963
- * Returns the Graphics object used for rendering the background
20663
+ * Hook to get background image from a ref
20664
+ * Returns the Image object used for rendering the background
20964
20665
  * Useful for animating background properties (tint, alpha, etc.)
20965
20666
  * @param ref - Ref to a Phaser container
20966
- * @returns Graphics object or undefined
20667
+ * @returns Background image or undefined
20967
20668
  */
20968
20669
  function useBackgroundGraphics(ref) {
20969
20670
  return getBackgroundGraphics(ref.current);
@@ -22713,8 +22414,6 @@ function showTooltip(scene, container, config) {
22713
22414
  const textHeight = text.height;
22714
22415
  const bgWidth = textWidth + paddingX * 2;
22715
22416
  const bgHeight = textHeight + paddingY * 2;
22716
- const cornerRadius = tooltipTheme.cornerRadius ?? 6;
22717
- const graphics = scene.add.graphics();
22718
22417
  const bg = bgColor ?? "#000000dd";
22719
22418
  let fillColor = 0;
22720
22419
  let fillAlpha = .87;
@@ -22730,9 +22429,17 @@ function showTooltip(scene, container, config) {
22730
22429
  }
22731
22430
  }
22732
22431
  }
22733
- graphics.fillStyle(fillColor, fillAlpha);
22734
- graphics.fillRoundedRect(-bgWidth / 2, -bgHeight / 2, bgWidth, bgHeight, cornerRadius);
22735
- const tooltipContainer = scene.add.container(0, 0, [graphics, text]);
22432
+ const background = createBackgroundImage(scene, {
22433
+ backgroundColor: fillColor,
22434
+ backgroundAlpha: fillAlpha,
22435
+ cornerRadius: tooltipTheme.cornerRadius ?? 6
22436
+ }, bgWidth, bgHeight);
22437
+ if (!background) {
22438
+ text.destroy();
22439
+ return;
22440
+ }
22441
+ background.setOrigin(.5, .5);
22442
+ const tooltipContainer = scene.add.container(0, 0, [background, text]);
22736
22443
  tooltipContainer.setDepth(1e4);
22737
22444
  const textBounds = tooltipContainer.getBounds();
22738
22445
  const pos = calculateTooltipPosition(targetBounds, position, offset, textBounds.width, textBounds.height);
@@ -22883,29 +22590,12 @@ function applyTooltip(scene, container, nextCallback, existingOnHoverStart, exis
22883
22590
  */
22884
22591
  function createBackground(scene, container, props) {
22885
22592
  const hasBackground = props.backgroundColor !== void 0;
22886
- const hasBorder = props.borderColor !== void 0;
22593
+ const hasBorder = (props.borderWidth ?? 0) > 0 && props.borderColor !== void 0;
22887
22594
  if (hasBackground || hasBorder) {
22888
- const width = typeof props.width === "number" ? props.width : 100;
22889
- const height = typeof props.height === "number" ? props.height : 100;
22890
- const bgColor = props.backgroundColor;
22891
- const bgAlpha = props.backgroundAlpha ?? 1;
22892
- const cornerRadius = props.cornerRadius ?? 0;
22893
- const borderColor = props.borderColor;
22894
- const borderWidth = props.borderWidth ?? 0;
22895
- const borderAlpha = props.borderAlpha ?? 1;
22896
- const background = scene.add.graphics();
22897
- if (bgColor !== void 0) background.fillStyle(bgColor, bgAlpha);
22898
- if (borderWidth > 0 && borderColor !== void 0) background.lineStyle(borderWidth, borderColor, borderAlpha);
22899
- if (cornerRadius !== 0) {
22900
- if (bgColor !== void 0) background.fillRoundedRect(0, 0, width, height, cornerRadius);
22901
- if (borderWidth > 0 && borderColor !== void 0) background.strokeRoundedRect(0, 0, width, height, cornerRadius);
22902
- } else {
22903
- if (bgColor !== void 0) background.fillRect(0, 0, width, height);
22904
- if (borderWidth > 0 && borderColor !== void 0) background.strokeRect(0, 0, width, height);
22905
- }
22595
+ const background = createBackgroundImage(scene, props, typeof props.width === "number" ? props.width : 100, typeof props.height === "number" ? props.height : 100);
22596
+ if (!background) return;
22906
22597
  container.addAt(background, 0);
22907
22598
  container.__background = background;
22908
- background.__isBackground = true;
22909
22599
  }
22910
22600
  }
22911
22601
  //#endregion
@@ -24361,7 +24051,7 @@ function registerBuiltins() {
24361
24051
  /**
24362
24052
  * Preprocesses SVG string to ensure tinting works correctly
24363
24053
  * Replaces fill/stroke="currentColor" and other colors with white (#FFFFFF)
24364
- * This allows Phaser's tint to work multiplicatively (white × tint = tint color)
24054
+ * This allows Phaser's tint to work by multiplication (white × tint = tint color)
24365
24055
  * @param svg - Raw SVG string
24366
24056
  * @returns Preprocessed SVG string with white fills/strokes
24367
24057
  */
@@ -25366,6 +25056,9 @@ function Accordion(props) {
25366
25056
  }, [isOpen, autoHeight]);
25367
25057
  const [contentHeight, setContentHeight] = useSpring(animated ? isOpen ? autoHeight ? measuredHeight : maxHeight : 0 : isOpen ? autoHeight ? measuredHeight : maxHeight : 0, animationConfig, props.onAnimationEnd);
25368
25058
  if (animated) useForceRedraw(20, contentHeight);
25059
+ const animatedContentHeight = Math.max(0, contentHeight.value);
25060
+ const resolvedContentHeight = animated ? animatedContentHeight : isOpen ? void 0 : 0;
25061
+ const resolvedContentVisible = animated ? animatedContentHeight > .5 : isOpen;
25369
25062
  const handleToggle = () => {
25370
25063
  const newState = !isOpen;
25371
25064
  setInternalOpen(newState);
@@ -25399,6 +25092,9 @@ function Accordion(props) {
25399
25092
  autoHeight && /* @__PURE__ */ jsx(View, {
25400
25093
  visible: false,
25401
25094
  direction: "stack",
25095
+ headless: true,
25096
+ width: 0,
25097
+ height: 0,
25402
25098
  children: /* @__PURE__ */ jsx(View, {
25403
25099
  ref: measurementRef,
25404
25100
  ...contentTheme,
@@ -25407,9 +25103,9 @@ function Accordion(props) {
25407
25103
  }),
25408
25104
  /* @__PURE__ */ jsx(View, {
25409
25105
  direction: "column",
25410
- height: animated ? contentHeight.value : isOpen ? void 0 : 0,
25106
+ height: resolvedContentHeight,
25411
25107
  overflow: "hidden",
25412
- visible: animated ? contentHeight.value > .5 : isOpen,
25108
+ visible: resolvedContentVisible,
25413
25109
  ...contentTheme,
25414
25110
  children: props.children
25415
25111
  })
@@ -26947,6 +26643,264 @@ function CharTextInput(props) {
26947
26643
  });
26948
26644
  }
26949
26645
  //#endregion
26646
+ //#region src/design-tokens/use-theme-tokens.ts
26647
+ /**
26648
+ * Hook to access complete design token system
26649
+ * Combines colors with text styles, spacing, sizes, and radius tokens
26650
+ */
26651
+ /**
26652
+ * Hook to access complete design token system from theme context
26653
+ * Provides colors, text styles, spacing, sizes, and radius tokens
26654
+ * Automatically updates when color mode or preset changes
26655
+ * @returns Current DesignTokens or undefined
26656
+ * @example
26657
+ * ```typescript
26658
+ * function MyComponent() {
26659
+ * const tokens = useThemeTokens()
26660
+ *
26661
+ * if (!tokens) return null
26662
+ *
26663
+ * return (
26664
+ * <View
26665
+ * backgroundColor={tokens.colors.surface.DEFAULT}
26666
+ * padding={tokens.spacing.lg}
26667
+ * cornerRadius={tokens.radius.md}
26668
+ * >
26669
+ * <Text text="Title" style={tokens.textStyles.title} />
26670
+ * <Text text="Body text" style={tokens.textStyles.DEFAULT} />
26671
+ * </View>
26672
+ * )
26673
+ * }
26674
+ * ```
26675
+ */
26676
+ function useThemeTokens() {
26677
+ const localTheme = useTheme();
26678
+ const getInitialTokens = () => {
26679
+ if (localTheme?.__colorPreset) {
26680
+ const preset = getPresetWithMode(localTheme.__colorPreset.name, localTheme.__colorPreset.mode ?? "light");
26681
+ return {
26682
+ colors: preset.colors,
26683
+ textStyles: createTextStyleTokens(preset.colors.text.DEFAULT.toString()),
26684
+ spacing: defaultSpacingTokens,
26685
+ sizes: defaultSizeTokens,
26686
+ radius: defaultRadiusTokens
26687
+ };
26688
+ }
26689
+ const colors = themeRegistry.getColorTokens();
26690
+ if (!colors) return void 0;
26691
+ return {
26692
+ colors,
26693
+ textStyles: createTextStyleTokens(colors.text.DEFAULT.toString()),
26694
+ spacing: defaultSpacingTokens,
26695
+ sizes: defaultSizeTokens,
26696
+ radius: defaultRadiusTokens
26697
+ };
26698
+ };
26699
+ const [tokens, setTokens] = useState(getInitialTokens());
26700
+ const [, forceUpdate] = useState(0);
26701
+ useEffect(() => {
26702
+ return themeRegistry.subscribe(() => {
26703
+ if (localTheme?.__colorPreset) {
26704
+ const currentMode = themeRegistry.getColorMode();
26705
+ const preset = getPresetWithMode(localTheme.__colorPreset.name, currentMode);
26706
+ setTokens({
26707
+ colors: preset.colors,
26708
+ textStyles: createTextStyleTokens(preset.colors.text.DEFAULT.toString()),
26709
+ spacing: defaultSpacingTokens,
26710
+ sizes: defaultSizeTokens,
26711
+ radius: defaultRadiusTokens
26712
+ });
26713
+ } else {
26714
+ const colors = themeRegistry.getColorTokens();
26715
+ if (colors) setTokens({
26716
+ colors,
26717
+ textStyles: createTextStyleTokens(colors.text.DEFAULT.toString()),
26718
+ spacing: defaultSpacingTokens,
26719
+ sizes: defaultSizeTokens,
26720
+ radius: defaultRadiusTokens
26721
+ });
26722
+ }
26723
+ forceUpdate((n) => n + 1);
26724
+ });
26725
+ }, [localTheme]);
26726
+ return tokens;
26727
+ }
26728
+ //#endregion
26729
+ //#region src/components/custom/DebugPanel.tsx
26730
+ /** @jsxImportSource ../.. */
26731
+ /**
26732
+ * DebugPanel component - lightweight runtime diagnostics for Phaser + PhaserJSX
26733
+ * Renders selected metrics as text rows or a compact single-line summary.
26734
+ */
26735
+ var PRESET_METRICS = {
26736
+ fps: ["fps", "frameMs"],
26737
+ perf: [
26738
+ "fps",
26739
+ "frameMs",
26740
+ "renderer",
26741
+ "viewport"
26742
+ ],
26743
+ vdom: [
26744
+ "mountsTotal",
26745
+ "mountsByType",
26746
+ "mountsByParent",
26747
+ "mountsByKey"
26748
+ ],
26749
+ textures: ["textureCount"],
26750
+ full: [
26751
+ "fps",
26752
+ "frameMs",
26753
+ "phaserVersion",
26754
+ "renderer",
26755
+ "viewport",
26756
+ "textureCount",
26757
+ "mountsTotal",
26758
+ "mountsByType",
26759
+ "mountsByParent",
26760
+ "mountsByKey",
26761
+ "debugFlags"
26762
+ ]
26763
+ };
26764
+ var DEFAULT_LABELS = {
26765
+ fps: "FPS",
26766
+ frameMs: "Frame ms",
26767
+ phaserVersion: "Phaser",
26768
+ renderer: "Renderer",
26769
+ viewport: "Viewport",
26770
+ textureCount: "Textures",
26771
+ mountsTotal: "Mounts",
26772
+ mountsByType: "Mounts/type",
26773
+ mountsByParent: "Mounts/parent",
26774
+ mountsByKey: "Mounts/key",
26775
+ debugFlags: "Debug flags"
26776
+ };
26777
+ function resolveRendererName(scene) {
26778
+ const renderer = scene.renderer;
26779
+ const canvasType = Phaser.CANVAS;
26780
+ if (renderer.type === Phaser.WEBGL) return "WebGL";
26781
+ if (canvasType !== void 0 && renderer.type === canvasType) return "Canvas";
26782
+ if (renderer.constructor?.name) return renderer.constructor.name;
26783
+ if (typeof renderer.type === "number") return `Type ${renderer.type}`;
26784
+ return "Unknown";
26785
+ }
26786
+ function resolveTextureCount(scene) {
26787
+ const manager = scene.textures;
26788
+ const keys = typeof manager.getTextureKeys === "function" ? manager.getTextureKeys() : Object.keys(manager.list ?? {});
26789
+ const internalKeys = new Set([
26790
+ "__DEFAULT",
26791
+ "__MISSING",
26792
+ "__NORMAL",
26793
+ "__WHITE"
26794
+ ]);
26795
+ return keys.filter((key) => !internalKeys.has(key.toUpperCase())).length;
26796
+ }
26797
+ function resolveFrameStats(scene) {
26798
+ const loop = scene.game.loop;
26799
+ const rawFps = loop?.actualFps ?? loop?.fps ?? 0;
26800
+ const rawFrameMs = loop?.delta ?? loop?.frameDelta ?? 0;
26801
+ return {
26802
+ fps: Number.isFinite(rawFps) ? Number(rawFps.toFixed(1)) : 0,
26803
+ frameMs: Number.isFinite(rawFrameMs) ? Number(rawFrameMs.toFixed(2)) : 0
26804
+ };
26805
+ }
26806
+ function summarizeMap(map, maxEntries = 3) {
26807
+ return Array.from(map.entries()).sort((a, b) => b[1] - a[1]).slice(0, maxEntries).map(([key, value]) => `${String(key)}:${value}`).join(" | ") || "-";
26808
+ }
26809
+ function resolveDebugFlags() {
26810
+ const debugConfig = DevConfig.debug;
26811
+ if (!debugConfig.enabled) return "off";
26812
+ const enabledFlags = Object.entries(debugConfig).filter(([key, value]) => key !== "enabled" && value).map(([key]) => key);
26813
+ return enabledFlags.length ? enabledFlags.join(",") : "enabled";
26814
+ }
26815
+ function collectSnapshot(scene) {
26816
+ const { fps, frameMs } = resolveFrameStats(scene);
26817
+ const mountStats = getMountStats();
26818
+ return {
26819
+ fps,
26820
+ frameMs,
26821
+ phaserVersion: Phaser.VERSION,
26822
+ renderer: resolveRendererName(scene),
26823
+ viewport: `${scene.scale.width}x${scene.scale.height}`,
26824
+ textureCount: resolveTextureCount(scene),
26825
+ mountsTotal: mountStats.totalMounts,
26826
+ mountsByType: summarizeMap(mountStats.byType),
26827
+ mountsByParent: summarizeMap(mountStats.byParent),
26828
+ mountsByKey: summarizeMap(mountStats.byKey),
26829
+ debugFlags: resolveDebugFlags()
26830
+ };
26831
+ }
26832
+ function formatDefault(value) {
26833
+ return typeof value === "number" ? String(value) : value;
26834
+ }
26835
+ /**
26836
+ * DebugPanel component
26837
+ * Shows selected diagnostics from Phaser + PhaserJSX as overlay-ready content.
26838
+ */
26839
+ function DebugPanel(props) {
26840
+ const scene = useScene();
26841
+ const { preset = "fps", metrics, intervalMs = 250, compact = false, maxRows, labels, formatters, innerProps, ...viewProps } = props;
26842
+ const selectedMetrics = useMemo(() => {
26843
+ const source = metrics && metrics.length > 0 ? metrics : PRESET_METRICS[preset];
26844
+ return Array.from(new Set(source));
26845
+ }, [metrics, preset]);
26846
+ const [snapshot, setSnapshot] = useState(collectSnapshot(scene));
26847
+ useEffect(() => {
26848
+ const delay = Math.max(50, intervalMs);
26849
+ setSnapshot(collectSnapshot(scene));
26850
+ const timer = window.setInterval(() => {
26851
+ setSnapshot(collectSnapshot(scene));
26852
+ }, delay);
26853
+ return () => {
26854
+ window.clearInterval(timer);
26855
+ };
26856
+ }, [
26857
+ scene,
26858
+ intervalMs,
26859
+ selectedMetrics
26860
+ ]);
26861
+ const tokens = useThemeTokens();
26862
+ const rowStyle = tokens?.textStyles.small;
26863
+ const valueStyle = tokens?.textStyles.small;
26864
+ const rows = selectedMetrics.map((metric) => {
26865
+ const rawValue = snapshot[metric];
26866
+ const label = labels?.[metric] ?? DEFAULT_LABELS[metric];
26867
+ const formatter = formatters?.[metric];
26868
+ return {
26869
+ metric,
26870
+ label,
26871
+ value: formatter ? formatter(rawValue) : formatDefault(rawValue)
26872
+ };
26873
+ });
26874
+ const limitedRows = typeof maxRows === "number" ? rows.slice(0, Math.max(1, maxRows)) : rows;
26875
+ if (compact) {
26876
+ const compactText = limitedRows.map((row) => `${row.label} ${row.value}`).join(" | ");
26877
+ return /* @__PURE__ */ jsx(View, {
26878
+ ...viewProps,
26879
+ children: /* @__PURE__ */ jsx(Text, {
26880
+ text: compactText,
26881
+ style: valueStyle
26882
+ })
26883
+ });
26884
+ }
26885
+ return /* @__PURE__ */ jsx(View, {
26886
+ direction: "column",
26887
+ gap: 4,
26888
+ ...viewProps,
26889
+ children: limitedRows.map((row) => /* @__PURE__ */ jsxs(View, {
26890
+ direction: "row",
26891
+ gap: 8,
26892
+ ...innerProps,
26893
+ children: [/* @__PURE__ */ jsx(Text, {
26894
+ text: `${row.label}:`,
26895
+ style: rowStyle
26896
+ }), /* @__PURE__ */ jsx(Text, {
26897
+ text: row.value,
26898
+ style: valueStyle
26899
+ })]
26900
+ }, `debug-${row.metric}`))
26901
+ });
26902
+ }
26903
+ //#endregion
26950
26904
  //#region src/components/custom/Divider.tsx
26951
26905
  /**
26952
26906
  * Divider component - renders a simple line separator
@@ -27719,6 +27673,7 @@ function Dropdown(props) {
27719
27673
  useForceRedraw(20, overlayHeight);
27720
27674
  const [arrowRotation, setArrowRotation] = useSpring(isOpen ? Math.PI : 0, animationConfig);
27721
27675
  useForceRedraw(20, arrowRotation);
27676
+ const resolvedOverlayHeight = Math.max(0, overlayHeight.value);
27722
27677
  const handleToggle = (event) => {
27723
27678
  if (props.disabled) return;
27724
27679
  event?.stopPropagation();
@@ -27890,7 +27845,7 @@ function Dropdown(props) {
27890
27845
  })
27891
27846
  });
27892
27847
  const overlay = /* @__PURE__ */ jsx(View, {
27893
- height: overlayHeight.value,
27848
+ height: resolvedOverlayHeight,
27894
27849
  width: overlayPosition.width,
27895
27850
  overflow: "hidden",
27896
27851
  children: /* @__PURE__ */ jsx(View, {
@@ -27898,7 +27853,7 @@ function Dropdown(props) {
27898
27853
  direction: "column",
27899
27854
  width: "fill",
27900
27855
  height: "fill",
27901
- visible: isOpen || Math.abs(overlayHeight.value) > .1,
27856
+ visible: isOpen || resolvedOverlayHeight > .1,
27902
27857
  ...overlayTheme,
27903
27858
  children: placement === "top" ? /* @__PURE__ */ jsxs(Fragment, { children: [optionsList, filterInput] }) : /* @__PURE__ */ jsxs(Fragment, { children: [filterInput, optionsList] })
27904
27859
  })
@@ -29390,7 +29345,6 @@ function Toggle(props) {
29390
29345
  setIsAnimating(true);
29391
29346
  const endX = newChecked ? thumbOffsetOn : thumbOffsetOff;
29392
29347
  const startX = thumbX;
29393
- console.log("Duration:", duration.current);
29394
29348
  scene.tweens.addCounter({
29395
29349
  from: 0,
29396
29350
  to: 1,
@@ -29557,6 +29511,6 @@ function TransformOriginView({ originX = .5, originY = .5, width, height, x = 0,
29557
29511
  });
29558
29512
  }
29559
29513
  //#endregion
29560
- export { RadioButton as $, textCreator as $n, shouldComponentUpdate as $t, computed as A, midnightPreset as An, createTheme as At, DOMInputElement as B, hexToNumber as Bn, remountAll as Bt, CharText as C, defaultTextStyleTokens as Cn, graphicsCreator as Cr, createWiggleEffect as Ct, Modal as D, generateColorScale as Dn, register as Dr, useGameObjectEffect as Dt, Dialog as E, forestGreenPreset as En, nodeRegistry as Er, createZoomOutEffect as Et, SpringPhysics as F, darken as Fn, getMountStats as Ft, useSVGTextures as G, rgbToHsl as Gn, disposeCtx as Gt, releaseSVGTexture as H, lightenHex as Hn, unmountJSX as Ht, animatedSignal as I, darkenHex as In, mount as It, Particles as J, normalizeCornerRadius as Jn, getLayoutProps as Jt, svgToTexture as K, rgbToNumber as Kn, getBackgroundGraphics as Kt, isAnimatedSignal as L, ensureContrast as Ln, mountJSX as Lt, useSprings as M, presets as Mn, mergeThemes as Mt, DEFAULT_SPRING_CONFIG as N, alpha as Nn, themeRegistry as Nt, Portal as O, getPreset as On, viewCreator as Ot, SPRING_PRESETS as P, createTextStyle as Pn, createElement as Pt, RadioGroup as Q, tileSpritePatcher as Qn, shallowEqual as Qt, unwrapSignal as R, getContrastRatio as Rn, normalizeVNodeLike as Rt, CharTextInput as S, defaultSpacingTokens as Sn, imagePatcher as Sr, createTadaEffect as St, WrapText as T, applyLightMode as Tn, host as Tr, createZoomInEffect as Tt, releaseSVGTextures as U, numberToHex as Un, portalRegistry as Ut, releaseAllSVGTextures as V, lighten as Vn, unmount as Vt, useSVGTexture as W, numberToRgb as Wn, View as Wt, Text as X, normalizeGap as Xn, getLayoutSize as Xt, TileSprite as Y, normalizeEdgeInsets as Yn, getLayoutRect as Yt, Sprite as Z, tileSpriteCreator as Zn, getWorldLayoutRect as Zt, Dropdown as _, createDefaultTheme as _n, buildEmitZone as _r, createShakeEffect as _t, Tabs as a, useLayoutRect as an, spriteCreator as ar, resolveEffect as at, calculateSliderSize as b, defaultRadiusTokens as bn, nineSlicePatcher as br, createSpinEffect as bt, Sidebar as c, useRedraw as cn, particlesPatcher as cr, createFadeEffect as ct, NineSlice as d, useState as dn, applyEmitterConfig as dr, createFlipOutEffect as dt, useBackgroundGraphics as en, textPatcher as er, Graphics as et, Joystick as f, useTheme as fn, getFirstEmitter as fr, createFloatEffect as ft, Image$1 as g, getRenderContext as gn, buildDeathZonesFromLayout as gr, createPulseEffect as gt, useIconPreload as h, withHooks as hn, resolveParticlePreset as hr, createPressEffect as ht, TabPanel as i, useLayoutEffect as in, DevPresets as ir, applyEffectByName as it, useSpring as j, oceanBluePreset as jn, getThemedProps as jt, Accordion as k, getPresetWithMode as kn, viewPatcher as kt, RefOriginView as l, useRef as ln, applyDeathZone as lr, createFlashEffect as lt, createIconComponent as m, useWorldLayoutRect as mn, PARTICLE_PRESET_REGISTRY as mr, createNoneEffect as mt, Toggle as n, useEffect as nn, DebugLogger as nr, DEFAULT_EFFECT as nt, RangeSlider as o, useLayoutSize as on, spritePatcher as or, createBounceEffect as ot, Icon as p, useViewportSize as pn, isParticleEmitter as pr, createJelloEffect as pt, registerBuiltins as q, HexColor as qn, getCurrent as qt, Tab as r, useForceRedraw as rn, DevConfig as rr, EFFECT_REGISTRY as rt, Slider as s, useMemo as sn, particlesCreator as sr, createBreatheEffect as st, TransformOriginView as t, useCallback as tn, viewportRegistry as tr, Button as tt, NineSliceButton as u, useScene as un, applyEmitZone as ur, createFlipInEffect as ut, ScrollView as v, defaultTheme as vn, buildEmitZoneFromLayout as vr, createSlideInEffect as vt, AlertDialog as w, applyDarkMode as wn, graphicsPatcher as wr, createWobbleEffect as wt, Divider as x, defaultSizeTokens as xn, imageCreator as xr, createSwingEffect as xt, ScrollSlider as y, createTextStyleTokens as yn, nineSliceCreator as yr, createSlideOutEffect as yt, KeyboardInputManager as z, hex as zn, patchVNode as zt };
29514
+ export { Sprite as $, tileSpriteCreator as $n, getWorldLayoutRect as $t, Portal as A, getPreset as An, viewCreator as At, unwrapSignal as B, getContrastRatio as Bn, normalizeVNodeLike as Bt, useThemeTokens as C, defaultSizeTokens as Cn, imageCreator as Cr, createSwingEffect as Ct, WrapText as D, applyLightMode as Dn, host as Dr, createZoomInEffect as Dt, AlertDialog as E, applyDarkMode as En, graphicsPatcher as Er, createWobbleEffect as Et, DEFAULT_SPRING_CONFIG as F, alpha as Fn, themeRegistry as Ft, releaseSVGTextures as G, numberToHex as Gn, portalRegistry as Gt, DOMInputElement as H, hexToNumber as Hn, remountAll as Ht, SPRING_PRESETS as I, createTextStyle as In, createElement as It, svgToTexture as J, rgbToNumber as Jn, getBackgroundGraphics as Jt, useSVGTexture as K, numberToRgb as Kn, View as Kt, SpringPhysics as L, darken as Ln, getMountStats as Lt, computed as M, midnightPreset as Mn, createTheme as Mt, useSpring as N, oceanBluePreset as Nn, getThemedProps as Nt, Dialog as O, forestGreenPreset as On, nodeRegistry as Or, createZoomOutEffect as Ot, useSprings as P, presets as Pn, mergeThemes as Pt, Text as Q, normalizeGap as Qn, getLayoutSize as Qt, animatedSignal as R, darkenHex as Rn, mount as Rt, DebugPanel as S, defaultRadiusTokens as Sn, nineSlicePatcher as Sr, createSpinEffect as St, CharText as T, defaultTextStyleTokens as Tn, graphicsCreator as Tr, createWiggleEffect as Tt, releaseAllSVGTextures as U, lighten as Un, unmount as Ut, KeyboardInputManager as V, hex as Vn, patchVNode as Vt, releaseSVGTexture as W, lightenHex as Wn, unmountJSX as Wt, Particles as X, normalizeCornerRadius as Xn, getLayoutProps as Xt, registerBuiltins as Y, HexColor as Yn, getCurrent as Yt, TileSprite as Z, normalizeEdgeInsets as Zn, getLayoutRect as Zt, Dropdown as _, withHooks as _n, resolveParticlePreset as _r, createPressEffect as _t, Tabs as a, useForceRedraw as an, DevConfig as ar, EFFECT_REGISTRY as at, calculateSliderSize as b, defaultTheme as bn, buildEmitZoneFromLayout as br, createSlideInEffect as bt, Sidebar as c, useLayoutSize as cn, spritePatcher as cr, createBounceEffect as ct, NineSlice as d, useRef as dn, applyDeathZone as dr, createFlashEffect as dt, shallowEqual as en, tileSpritePatcher as er, RadioGroup as et, Joystick as f, useScene as fn, applyEmitZone as fr, createFlipInEffect as ft, Image$1 as g, useWorldLayoutRect as gn, PARTICLE_PRESET_REGISTRY as gr, createNoneEffect as gt, useIconPreload as h, useViewportSize as hn, isParticleEmitter as hr, createJelloEffect as ht, TabPanel as i, useEffect as in, DebugLogger as ir, DEFAULT_EFFECT as it, Accordion as j, getPresetWithMode as jn, viewPatcher as jt, Modal as k, generateColorScale as kn, register as kr, useGameObjectEffect as kt, RefOriginView as l, useMemo as ln, particlesCreator as lr, createBreatheEffect as lt, createIconComponent as m, useTheme as mn, getFirstEmitter as mr, createFloatEffect as mt, Toggle as n, useBackgroundGraphics as nn, textPatcher as nr, Graphics as nt, RangeSlider as o, useLayoutEffect as on, DevPresets as or, applyEffectByName as ot, Icon as p, useState as pn, applyEmitterConfig as pr, createFlipOutEffect as pt, useSVGTextures as q, rgbToHsl as qn, disposeCtx as qt, Tab as r, useCallback as rn, viewportRegistry as rr, Button as rt, Slider as s, useLayoutRect as sn, spriteCreator as sr, resolveEffect as st, TransformOriginView as t, shouldComponentUpdate as tn, textCreator as tr, RadioButton as tt, NineSliceButton as u, useRedraw as un, particlesPatcher as ur, createFadeEffect as ut, ScrollView as v, getRenderContext as vn, buildDeathZonesFromLayout as vr, createPulseEffect as vt, CharTextInput as w, defaultSpacingTokens as wn, imagePatcher as wr, createTadaEffect as wt, Divider as x, createTextStyleTokens as xn, nineSliceCreator as xr, createSlideOutEffect as xt, ScrollSlider as y, createDefaultTheme as yn, buildEmitZone as yr, createShakeEffect as yt, isAnimatedSignal as z, ensureContrast as zn, mountJSX as zt };
29561
29515
 
29562
- //# sourceMappingURL=custom-oy3mBnrW.js.map
29516
+ //# sourceMappingURL=custom-BXDJDGOl.js.map