@number10/phaserjsx 4.2.0 → 4.3.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 (42) hide show
  1. package/README.md +14 -1
  2. package/dist/components/appliers/applyParticles.d.ts.map +1 -1
  3. package/dist/components/custom/Badge.d.ts +73 -0
  4. package/dist/components/custom/Badge.d.ts.map +1 -0
  5. package/dist/components/custom/Checkbox.d.ts +41 -0
  6. package/dist/components/custom/Checkbox.d.ts.map +1 -0
  7. package/dist/components/custom/Particles.d.ts +14 -3
  8. package/dist/components/custom/Particles.d.ts.map +1 -1
  9. package/dist/components/custom/Popover.d.ts +89 -0
  10. package/dist/components/custom/Popover.d.ts.map +1 -0
  11. package/dist/components/custom/ProgressBar.d.ts +52 -0
  12. package/dist/components/custom/ProgressBar.d.ts.map +1 -0
  13. package/dist/components/custom/index.cjs +14 -1
  14. package/dist/components/custom/index.d.ts +4 -0
  15. package/dist/components/custom/index.d.ts.map +1 -1
  16. package/dist/components/custom/index.js +2 -2
  17. package/dist/components/index.d.ts +5 -1
  18. package/dist/components/index.d.ts.map +1 -1
  19. package/dist/components/primitives/particles.d.ts +14 -4
  20. package/dist/components/primitives/particles.d.ts.map +1 -1
  21. package/dist/components/primitives/tilesprite.d.ts +15 -19
  22. package/dist/components/primitives/tilesprite.d.ts.map +1 -1
  23. package/dist/{custom-DTd4LxDn.cjs → custom-37gL0VZG.cjs} +1701 -656
  24. package/dist/custom-37gL0VZG.cjs.map +1 -0
  25. package/dist/{custom-BXDJDGOl.js → custom-DMZASXll.js} +3910 -2943
  26. package/dist/custom-DMZASXll.js.map +1 -0
  27. package/dist/index.cjs +40 -20
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.js +28 -21
  30. package/dist/index.js.map +1 -1
  31. package/dist/particles/emit-zone.d.ts +34 -12
  32. package/dist/particles/emit-zone.d.ts.map +1 -1
  33. package/dist/particles/index.d.ts +1 -1
  34. package/dist/particles/index.d.ts.map +1 -1
  35. package/dist/particles/use-particles.d.ts +6 -2
  36. package/dist/particles/use-particles.d.ts.map +1 -1
  37. package/dist/theme-custom.d.ts +68 -0
  38. package/dist/theme-custom.d.ts.map +1 -1
  39. package/dist/theme-defaults.d.ts.map +1 -1
  40. package/package.json +1 -1
  41. package/dist/custom-BXDJDGOl.js.map +0 -1
  42. package/dist/custom-DTd4LxDn.cjs.map +0 -1
@@ -549,9 +549,6 @@ var nineSlicePatcher = (node, prev, next) => {
549
549
  };
550
550
  //#endregion
551
551
  //#region src/particles/emit-zone.ts
552
- /**
553
- * Emit zone helpers for particle emitters
554
- */
555
552
  function resolveNumericSize(value) {
556
553
  return typeof value === "number" ? value : void 0;
557
554
  }
@@ -563,18 +560,164 @@ function resolveZoneSize(zone, fallback) {
563
560
  if (height !== void 0) size.height = height;
564
561
  return size;
565
562
  }
563
+ function withPoint(point, x, y) {
564
+ if (point) {
565
+ point.x = x;
566
+ point.y = y;
567
+ return point;
568
+ }
569
+ return {
570
+ x,
571
+ y
572
+ };
573
+ }
574
+ function createRectSource(x, y, width, height) {
575
+ return {
576
+ x,
577
+ y,
578
+ width,
579
+ height,
580
+ contains: (px, py) => px >= x && px <= x + width && py >= y && py <= y + height,
581
+ getRandomPoint: (point) => withPoint(point, x + Math.random() * width, y + Math.random() * height),
582
+ getPoints: (quantity = 1) => {
583
+ const count = Math.max(1, quantity);
584
+ return Array.from({ length: count }, (_, index) => {
585
+ const distance = (count === 1 ? 0 : index / (count - 1)) * (2 * (width + height));
586
+ if (distance <= width) return {
587
+ x: x + distance,
588
+ y
589
+ };
590
+ if (distance <= width + height) return {
591
+ x: x + width,
592
+ y: y + distance - width
593
+ };
594
+ if (distance <= width * 2 + height) return {
595
+ x: x + width - (distance - width - height),
596
+ y: y + height
597
+ };
598
+ return {
599
+ x,
600
+ y: y + height - (distance - width * 2 - height)
601
+ };
602
+ });
603
+ }
604
+ };
605
+ }
606
+ function createCircleSource(x, y, radius) {
607
+ return {
608
+ x,
609
+ y,
610
+ radius,
611
+ contains: (px, py) => {
612
+ const dx = px - x;
613
+ const dy = py - y;
614
+ return dx * dx + dy * dy <= radius * radius;
615
+ },
616
+ getRandomPoint: (point) => {
617
+ const angle = Math.random() * Math.PI * 2;
618
+ const distance = Math.sqrt(Math.random()) * radius;
619
+ return withPoint(point, x + Math.cos(angle) * distance, y + Math.sin(angle) * distance);
620
+ },
621
+ getPoints: (quantity = 1) => {
622
+ const count = Math.max(1, quantity);
623
+ return Array.from({ length: count }, (_, index) => {
624
+ const angle = index / count * Math.PI * 2;
625
+ return {
626
+ x: x + Math.cos(angle) * radius,
627
+ y: y + Math.sin(angle) * radius
628
+ };
629
+ });
630
+ }
631
+ };
632
+ }
633
+ function createEllipseSource(x, y, width, height) {
634
+ const radiusX = width / 2;
635
+ const radiusY = height / 2;
636
+ return {
637
+ x,
638
+ y,
639
+ width,
640
+ height,
641
+ contains: (px, py) => {
642
+ const dx = (px - x) / radiusX;
643
+ const dy = (py - y) / radiusY;
644
+ return dx * dx + dy * dy <= 1;
645
+ },
646
+ getRandomPoint: (point) => {
647
+ const angle = Math.random() * Math.PI * 2;
648
+ const distance = Math.sqrt(Math.random());
649
+ return withPoint(point, x + Math.cos(angle) * radiusX * distance, y + Math.sin(angle) * radiusY * distance);
650
+ },
651
+ getPoints: (quantity = 1) => {
652
+ const count = Math.max(1, quantity);
653
+ return Array.from({ length: count }, (_, index) => {
654
+ const angle = index / count * Math.PI * 2;
655
+ return {
656
+ x: x + Math.cos(angle) * radiusX,
657
+ y: y + Math.sin(angle) * radiusY
658
+ };
659
+ });
660
+ }
661
+ };
662
+ }
663
+ function createLineSource(x, y, endX, endY) {
664
+ return {
665
+ x,
666
+ y,
667
+ contains: (px, py) => {
668
+ const lengthSquared = (endX - x) ** 2 + (endY - y) ** 2;
669
+ if (lengthSquared === 0) return px === x && py === y;
670
+ const progress = Math.max(0, Math.min(1, ((px - x) * (endX - x) + (py - y) * (endY - y)) / lengthSquared));
671
+ const closestX = x + progress * (endX - x);
672
+ const closestY = y + progress * (endY - y);
673
+ return Math.hypot(px - closestX, py - closestY) <= .5;
674
+ },
675
+ getRandomPoint: (point) => {
676
+ const progress = Math.random();
677
+ return withPoint(point, x + (endX - x) * progress, y + (endY - y) * progress);
678
+ },
679
+ getPoints: (quantity = 1) => {
680
+ const count = Math.max(1, quantity);
681
+ return Array.from({ length: count }, (_, index) => {
682
+ const progress = count === 1 ? 0 : index / (count - 1);
683
+ return {
684
+ x: x + (endX - x) * progress,
685
+ y: y + (endY - y) * progress
686
+ };
687
+ });
688
+ }
689
+ };
690
+ }
566
691
  function buildZoneSource(zone, fallbackSize) {
567
692
  const baseX = zone.x ?? 0;
568
693
  const baseY = zone.y ?? 0;
569
694
  const { width, height } = resolveZoneSize(zone, fallbackSize);
570
695
  switch (zone.shape) {
571
- case "rect": return new phaser.Geom.Rectangle(baseX, baseY, width ?? 1, height ?? 1);
572
- case "circle": return new phaser.Geom.Circle(baseX, baseY, zone.radius ?? 1);
573
- case "ellipse": return new phaser.Geom.Ellipse(baseX, baseY, width ?? 1, height ?? 1);
574
- case "line": return new phaser.Geom.Line(baseX, baseY, zone.endX ?? baseX + (width ?? 1), zone.endY ?? baseY + (height ?? 1));
696
+ case "rect": return createRectSource(baseX, baseY, width ?? 1, height ?? 1);
697
+ case "circle": return createCircleSource(baseX, baseY, zone.radius ?? 1);
698
+ case "ellipse": return createEllipseSource(baseX, baseY, width ?? 1, height ?? 1);
699
+ case "line": return createLineSource(baseX, baseY, zone.endX ?? baseX + (width ?? 1), zone.endY ?? baseY + (height ?? 1));
575
700
  default: return;
576
701
  }
577
702
  }
703
+ function getTransformOwner(value) {
704
+ if (!value || typeof value !== "object") return void 0;
705
+ if (typeof value.getWorldTransformMatrix !== "function") return;
706
+ return value;
707
+ }
708
+ function createLocalDeathZoneSource(source, owner) {
709
+ const transformOwner = getTransformOwner(owner);
710
+ if (!transformOwner) return source;
711
+ return {
712
+ ...source,
713
+ contains: (worldX, worldY) => {
714
+ const matrix = transformOwner.getWorldTransformMatrix?.();
715
+ if (!matrix) return source.contains(worldX, worldY);
716
+ const localPoint = matrix.applyInverse(worldX, worldY);
717
+ return source.contains(localPoint.x, localPoint.y);
718
+ }
719
+ };
720
+ }
578
721
  /**
579
722
  * Build a Phaser emitZone config from a lightweight zone definition
580
723
  */
@@ -599,21 +742,21 @@ function buildEmitZoneFromLayout(zone, width, height) {
599
742
  return buildEmitZone(zone, fallback);
600
743
  }
601
744
  /**
602
- * Build a Phaser deathZone config from a lightweight exclusion definition
745
+ * Build a Phaser deathZone config from a lightweight death zone definition
603
746
  */
604
- function buildDeathZone(zone, fallbackSize = {}) {
747
+ function buildDeathZone(zone, fallbackSize = {}, owner) {
605
748
  const type = zone.mode ?? "onEnter";
606
749
  const source = buildZoneSource(zone, fallbackSize);
607
750
  if (!source) return void 0;
608
751
  return {
609
752
  type,
610
- source
753
+ source: createLocalDeathZoneSource(source, owner)
611
754
  };
612
755
  }
613
756
  /**
614
757
  * Create death zones using layout size as a fallback
615
758
  */
616
- function buildDeathZonesFromLayout(zones, width, height) {
759
+ function buildDeathZonesFromLayout(zones, width, height, owner) {
617
760
  if (!zones) return void 0;
618
761
  const list = Array.isArray(zones) ? zones : [zones];
619
762
  const fallback = {};
@@ -621,7 +764,7 @@ function buildDeathZonesFromLayout(zones, width, height) {
621
764
  const resolvedHeight = resolveNumericSize(height);
622
765
  if (resolvedWidth !== void 0) fallback.width = resolvedWidth;
623
766
  if (resolvedHeight !== void 0) fallback.height = resolvedHeight;
624
- const deathZones = list.map((zone) => buildDeathZone(zone, fallback)).filter((zone) => Boolean(zone));
767
+ const deathZones = list.map((zone) => buildDeathZone(zone, fallback, owner)).filter((zone) => Boolean(zone));
625
768
  return deathZones.length > 0 ? deathZones : void 0;
626
769
  }
627
770
  //#endregion
@@ -812,7 +955,7 @@ function mergeDeathZones(base, extra) {
812
955
  function applyParticlesProps(manager, prev, next) {
813
956
  if (!manager) return;
814
957
  if ((prev.texture !== next.texture || prev.frame !== next.frame) && next.texture && "setTexture" in manager && manager.setTexture) manager.setTexture(next.texture, next.frame);
815
- if (prev.preset !== next.preset || prev.config !== next.config || prev.zone !== next.zone || prev.excludeZones !== next.excludeZones || prev.width !== next.width || prev.height !== next.height) {
958
+ if (prev.preset !== next.preset || prev.config !== next.config || prev.emitZone !== next.emitZone || prev.zone !== next.zone || prev.deathZones !== next.deathZones || prev.excludeZones !== next.excludeZones || prev.width !== next.width || prev.height !== next.height) {
816
959
  const resolvedConfig = resolveParticlePreset(next.preset, next.config);
817
960
  let emitter;
818
961
  if (isParticleEmitter(manager)) emitter = manager;
@@ -824,11 +967,13 @@ function applyParticlesProps(manager, prev, next) {
824
967
  }
825
968
  if (!emitter) return;
826
969
  applyEmitterConfig(emitter, resolvedConfig);
827
- if (next.zone) {
828
- const emitZone = buildEmitZoneFromLayout(next.zone, next.width, next.height);
970
+ const emitZoneProps = next.emitZone ?? next.zone;
971
+ const deathZoneProps = next.deathZones ?? next.excludeZones;
972
+ if (emitZoneProps) {
973
+ const emitZone = buildEmitZoneFromLayout(emitZoneProps, next.width, next.height);
829
974
  if (emitZone) applyEmitZone(emitter, emitZone);
830
975
  }
831
- const deathZones = buildDeathZonesFromLayout(next.excludeZones, next.width, next.height);
976
+ const deathZones = buildDeathZonesFromLayout(deathZoneProps, next.width, next.height, manager);
832
977
  const combined = mergeDeathZones(resolvedConfig.deathZone, deathZones);
833
978
  applyDeathZone(emitter, combined);
834
979
  }
@@ -864,8 +1009,10 @@ function createParticlesLayout(particles, props) {
864
1009
  */
865
1010
  var particlesCreator = (scene, props) => {
866
1011
  const resolvedConfig = { ...resolveParticlePreset(props.preset, props.config) };
867
- if (props.zone) {
868
- const emitZone = buildEmitZoneFromLayout(props.zone, props.width, props.height);
1012
+ const emitZoneProps = props.emitZone ?? props.zone;
1013
+ const deathZoneProps = props.deathZones ?? props.excludeZones;
1014
+ if (emitZoneProps) {
1015
+ const emitZone = buildEmitZoneFromLayout(emitZoneProps, props.width, props.height);
869
1016
  if (emitZone) {
870
1017
  const configWithZone = resolvedConfig;
871
1018
  configWithZone.emitZone = emitZone;
@@ -883,8 +1030,8 @@ var particlesCreator = (scene, props) => {
883
1030
  createTransform(particles, props);
884
1031
  createPhaser(particles, props);
885
1032
  createParticlesLayout(particles, props);
886
- if (props.excludeZones && emitter) {
887
- const deathZones = buildDeathZonesFromLayout(props.excludeZones, props.width, props.height);
1033
+ if (deathZoneProps && emitter) {
1034
+ const deathZones = buildDeathZonesFromLayout(deathZoneProps, props.width, props.height, particles);
888
1035
  const combinedDeathZones = mergeDeathZones(resolvedConfig.deathZone, deathZones);
889
1036
  if (combinedDeathZones) applyDeathZone(emitter, combinedDeathZones);
890
1037
  }
@@ -16539,18 +16686,54 @@ var textPatcher = (node, prev, next) => {
16539
16686
  //#endregion
16540
16687
  //#region src/components/primitives/tilesprite.ts
16541
16688
  /**
16542
- * TileSprite creator - NOT IMPLEMENTED YET
16543
- * @throws Error indicating component is not implemented
16689
+ * Creates layout infrastructure for TileSprite.
16544
16690
  */
16545
- var tileSpriteCreator = (_scene, _props) => {
16546
- throw new Error("TileSprite component not implemented yet. This is a placeholder for architecture planning.");
16691
+ function createTileSpriteLayout(tileSprite, props) {
16692
+ tileSprite.__layoutProps = props;
16693
+ tileSprite.__getLayoutSize = () => {
16694
+ if (tileSprite.__layoutProps?.headless) return {
16695
+ width: .01,
16696
+ height: .01
16697
+ };
16698
+ return {
16699
+ width: tileSprite.width,
16700
+ height: tileSprite.height
16701
+ };
16702
+ };
16703
+ }
16704
+ function applyTileSpriteProps(tileSprite, prev, next) {
16705
+ if ((prev.texture !== next.texture || prev.frame !== next.frame) && next.texture) tileSprite.setTexture(next.texture, next.frame);
16706
+ if ((prev.width !== next.width || prev.height !== next.height) && typeof next.width === "number" && typeof next.height === "number") tileSprite.setSize(next.width, next.height);
16707
+ if (prev.tilePositionX !== next.tilePositionX && typeof next.tilePositionX === "number") tileSprite.tilePositionX = next.tilePositionX;
16708
+ if (prev.tilePositionY !== next.tilePositionY && typeof next.tilePositionY === "number") tileSprite.tilePositionY = next.tilePositionY;
16709
+ if (prev.tileScaleX !== next.tileScaleX && typeof next.tileScaleX === "number") tileSprite.tileScaleX = next.tileScaleX;
16710
+ if (prev.tileScaleY !== next.tileScaleY && typeof next.tileScaleY === "number") tileSprite.tileScaleY = next.tileScaleY;
16711
+ if (prev.tint !== next.tint) if (typeof next.tint === "number") tileSprite.setTint(next.tint);
16712
+ else tileSprite.clearTint();
16713
+ if (prev.originX !== next.originX || prev.originY !== next.originY) tileSprite.setOrigin(next.originX ?? tileSprite.originX, next.originY ?? tileSprite.originY);
16714
+ }
16715
+ /**
16716
+ * TileSprite creator - creates a Phaser TileSprite object.
16717
+ */
16718
+ var tileSpriteCreator = (scene, props) => {
16719
+ const tileSprite = scene.add.tileSprite(props.x ?? 0, props.y ?? 0, props.width, props.height, props.texture, props.frame);
16720
+ tileSprite.setOrigin(props.originX ?? 0, props.originY ?? 0);
16721
+ createTransform(tileSprite, props);
16722
+ createPhaser(tileSprite, props);
16723
+ applyTileSpriteProps(tileSprite, {}, props);
16724
+ createTileSpriteLayout(tileSprite, props);
16725
+ props.onReady?.(tileSprite);
16726
+ return tileSprite;
16547
16727
  };
16548
16728
  /**
16549
- * TileSprite patcher - NOT IMPLEMENTED YET
16550
- * @throws Error indicating component is not implemented
16729
+ * TileSprite patcher - updates texture, size, tiling and shared display props.
16551
16730
  */
16552
- var tileSpritePatcher = (_node, _prev, _next) => {
16553
- throw new Error("TileSprite component not implemented yet. This is a placeholder for architecture planning.");
16731
+ var tileSpritePatcher = (node, prev, next) => {
16732
+ applyTransformProps(node, prev, next);
16733
+ applyPhaserProps(node, prev, next);
16734
+ applyTileSpriteProps(node, prev, next);
16735
+ const tileSprite = node;
16736
+ if (tileSprite.__layoutProps) tileSprite.__layoutProps = next;
16554
16737
  };
16555
16738
  //#endregion
16556
16739
  //#region src/components/backgroundImage.ts
@@ -19806,6 +19989,91 @@ function buildDefaultTheme(colors) {
19806
19989
  fontSize: "14px"
19807
19990
  }
19808
19991
  },
19992
+ Checkbox: {
19993
+ checkedColor: colors.primary.DEFAULT.toNumber(),
19994
+ indeterminateColor: colors.primary.medium.toNumber(),
19995
+ color: colors.border.medium.toNumber(),
19996
+ gap: 10,
19997
+ size: 20,
19998
+ disabledAlpha: .5,
19999
+ labelPosition: "right",
20000
+ labelStyle: {
20001
+ color: colors.text.DEFAULT.toString(),
20002
+ fontSize: "14px"
20003
+ }
20004
+ },
20005
+ Badge: {
20006
+ tone: "neutral",
20007
+ variant: "solid",
20008
+ size: "medium",
20009
+ maxCount: 99,
20010
+ disabledAlpha: .5,
20011
+ textStyle: { fontStyle: "bold" }
20012
+ },
20013
+ Tag: {
20014
+ tone: "neutral",
20015
+ variant: "soft",
20016
+ size: "medium",
20017
+ closeSize: 16,
20018
+ disabledAlpha: .5,
20019
+ textStyle: { fontStyle: "bold" }
20020
+ },
20021
+ Popover: {
20022
+ placement: "bottom",
20023
+ offset: 8,
20024
+ depth: 1100,
20025
+ closeOnOutside: true,
20026
+ closeOnEscape: true,
20027
+ viewportPadding: 8,
20028
+ backgroundColor: colors.surface.dark.toNumber(),
20029
+ borderColor: colors.border.medium.toNumber(),
20030
+ borderWidth: 1,
20031
+ cornerRadius: 8,
20032
+ padding: 10,
20033
+ gap: 8
20034
+ },
20035
+ ContextMenu: {
20036
+ width: 220,
20037
+ itemHeight: 34,
20038
+ itemGap: 8,
20039
+ itemPadding: {
20040
+ left: 10,
20041
+ right: 10,
20042
+ top: 6,
20043
+ bottom: 6
20044
+ },
20045
+ itemCornerRadius: 5,
20046
+ backgroundColor: colors.surface.dark.toNumber(),
20047
+ borderColor: colors.border.medium.toNumber(),
20048
+ borderWidth: 1,
20049
+ cornerRadius: 8,
20050
+ padding: 6,
20051
+ gap: 2,
20052
+ textStyle: {
20053
+ ...textStyles.small,
20054
+ color: colors.text.medium.toString()
20055
+ },
20056
+ disabledTextColor: colors.text.lightest.toString(),
20057
+ dangerTextColor: colors.error.darkest.toString(),
20058
+ dangerBackgroundColor: colors.error.dark.toNumber()
20059
+ },
20060
+ ProgressBar: {
20061
+ width: 240,
20062
+ height: 22,
20063
+ orientation: "horizontal",
20064
+ labelPosition: "right",
20065
+ trackColor: colors.surface.dark.toNumber(),
20066
+ fillColor: colors.success.DEFAULT.toNumber(),
20067
+ borderColor: colors.border.medium.toNumber(),
20068
+ borderWidth: 1,
20069
+ cornerRadius: 11,
20070
+ gap: 8,
20071
+ disabledAlpha: .5,
20072
+ labelStyle: {
20073
+ color: colors.text.DEFAULT.toString(),
20074
+ fontSize: "13px"
20075
+ }
20076
+ },
19809
20077
  ScrollSlider: {
19810
20078
  borderColor: colors.border.dark.toNumber(),
19811
20079
  trackColor: colors.surface.dark.toNumber(),
@@ -20618,7 +20886,7 @@ function useViewportSize() {
20618
20886
  * @param container - Phaser container with layout
20619
20887
  * @returns Layout size or undefined if container has no layout
20620
20888
  */
20621
- function getLayoutSize(container) {
20889
+ function getLayoutSize$1(container) {
20622
20890
  if (!container) return void 0;
20623
20891
  return container.__getLayoutSize?.();
20624
20892
  }
@@ -20630,7 +20898,7 @@ function getLayoutSize(container) {
20630
20898
  * @returns Current layout size or undefined
20631
20899
  */
20632
20900
  function useLayoutSize(ref) {
20633
- return getLayoutSize(ref.current);
20901
+ return getLayoutSize$1(ref.current);
20634
20902
  }
20635
20903
  /**
20636
20904
  * Utility function to get layout props from a container
@@ -20671,7 +20939,7 @@ function useBackgroundGraphics(ref) {
20671
20939
  */
20672
20940
  function getLayoutRect(container) {
20673
20941
  if (!container) return void 0;
20674
- const size = getLayoutSize(container);
20942
+ const size = getLayoutSize$1(container);
20675
20943
  if (!size) return void 0;
20676
20944
  return {
20677
20945
  x: container.x,
@@ -20704,7 +20972,7 @@ function useLayoutRect(ref) {
20704
20972
  */
20705
20973
  function getWorldLayoutRect(container) {
20706
20974
  if (!container) return void 0;
20707
- const size = getLayoutSize(container);
20975
+ const size = getLayoutSize$1(container);
20708
20976
  if (!size) return void 0;
20709
20977
  const matrix = container.getWorldTransformMatrix();
20710
20978
  return {
@@ -22870,6 +23138,241 @@ var viewPatcher = (node, prev, next) => {
22870
23138
  applyLayoutProps(container, normalizedPrev, normalizedNext);
22871
23139
  };
22872
23140
  //#endregion
23141
+ //#region src/components/custom/Badge.tsx
23142
+ /** @jsxImportSource ../.. */
23143
+ /**
23144
+ * Badge and Tag components - compact labels for counts, status, filters, and inventory metadata.
23145
+ */
23146
+ var DEFAULT_TONES = {
23147
+ neutral: {
23148
+ backgroundColor: 4674921,
23149
+ softBackgroundColor: 1976635,
23150
+ borderColor: 6583435,
23151
+ textColor: "#ffffff",
23152
+ outlineTextColor: "#cbd5e1"
23153
+ },
23154
+ primary: {
23155
+ backgroundColor: 2450411,
23156
+ softBackgroundColor: 1981066,
23157
+ borderColor: 6333946,
23158
+ textColor: "#ffffff",
23159
+ outlineTextColor: "#93c5fd"
23160
+ },
23161
+ success: {
23162
+ backgroundColor: 1483594,
23163
+ softBackgroundColor: 1332013,
23164
+ borderColor: 4906624,
23165
+ textColor: "#ffffff",
23166
+ outlineTextColor: "#86efac"
23167
+ },
23168
+ warning: {
23169
+ backgroundColor: 16096779,
23170
+ softBackgroundColor: 7877903,
23171
+ borderColor: 16498468,
23172
+ textColor: "#111827",
23173
+ outlineTextColor: "#fde68a"
23174
+ },
23175
+ danger: {
23176
+ backgroundColor: 14427686,
23177
+ softBackgroundColor: 8330525,
23178
+ borderColor: 16281969,
23179
+ textColor: "#ffffff",
23180
+ outlineTextColor: "#fecaca"
23181
+ },
23182
+ info: {
23183
+ backgroundColor: 561586,
23184
+ softBackgroundColor: 1461859,
23185
+ borderColor: 2282478,
23186
+ textColor: "#ffffff",
23187
+ outlineTextColor: "#a5f3fc"
23188
+ }
23189
+ };
23190
+ var DEFAULT_SIZES = {
23191
+ small: {
23192
+ height: 20,
23193
+ padding: {
23194
+ left: 7,
23195
+ right: 7,
23196
+ top: 2,
23197
+ bottom: 2
23198
+ },
23199
+ fontSize: 11,
23200
+ cornerRadius: 10,
23201
+ gap: 5,
23202
+ dotSize: 8
23203
+ },
23204
+ medium: {
23205
+ height: 24,
23206
+ padding: {
23207
+ left: 9,
23208
+ right: 9,
23209
+ top: 3,
23210
+ bottom: 3
23211
+ },
23212
+ fontSize: 13,
23213
+ cornerRadius: 12,
23214
+ gap: 6,
23215
+ dotSize: 10
23216
+ },
23217
+ large: {
23218
+ height: 30,
23219
+ padding: {
23220
+ left: 12,
23221
+ right: 12,
23222
+ top: 4,
23223
+ bottom: 4
23224
+ },
23225
+ fontSize: 18,
23226
+ cornerRadius: 15,
23227
+ gap: 8,
23228
+ dotSize: 12
23229
+ }
23230
+ };
23231
+ function formatBadgeCount({ count, maxCount = 99 }) {
23232
+ if (!Number.isFinite(count)) return "0";
23233
+ const normalizedCount = Math.max(0, Math.floor(count ?? 0));
23234
+ const normalizedMax = Math.max(0, Math.floor(maxCount));
23235
+ return normalizedCount > normalizedMax ? `${normalizedMax}+` : `${normalizedCount}`;
23236
+ }
23237
+ function getBadgeText(props) {
23238
+ if (props.count !== void 0) return formatBadgeCount(props);
23239
+ if (props.label !== void 0) return `${props.label}`;
23240
+ return "";
23241
+ }
23242
+ function resolveBadgeColors(tone, variant) {
23243
+ const toneColors = DEFAULT_TONES[tone];
23244
+ if (variant === "outline") return {
23245
+ backgroundColor: 0,
23246
+ backgroundAlpha: 0,
23247
+ borderColor: toneColors.borderColor,
23248
+ borderWidth: 1,
23249
+ textColor: toneColors.outlineTextColor
23250
+ };
23251
+ if (variant === "soft") return {
23252
+ backgroundColor: toneColors.softBackgroundColor,
23253
+ backgroundAlpha: 1,
23254
+ borderColor: toneColors.borderColor,
23255
+ borderWidth: 1,
23256
+ textColor: toneColors.outlineTextColor
23257
+ };
23258
+ return {
23259
+ backgroundColor: toneColors.backgroundColor,
23260
+ backgroundAlpha: 1,
23261
+ borderColor: toneColors.borderColor,
23262
+ borderWidth: 0,
23263
+ textColor: toneColors.textColor
23264
+ };
23265
+ }
23266
+ function getBadgeSizeConfig(size) {
23267
+ return DEFAULT_SIZES[size];
23268
+ }
23269
+ function resolveBadgeTextStyle(options) {
23270
+ const sizeConfig = getBadgeSizeConfig(options.size);
23271
+ return {
23272
+ color: options.textColor,
23273
+ fontSize: `${sizeConfig.fontSize}px`,
23274
+ ...options.themedTextStyle ?? {},
23275
+ ...options.explicitTextStyle ?? {}
23276
+ };
23277
+ }
23278
+ function Badge(props) {
23279
+ const { children, label, count, maxCount, dot = false, tone: explicitTone, variant: explicitVariant, size: explicitSize, textStyle: explicitTextStyle, disabled = false, disabledAlpha: explicitDisabledAlpha, theme, width, height, padding, gap, cornerRadius, backgroundColor, backgroundAlpha, borderColor, borderWidth, alpha, ...viewProps } = props;
23280
+ const { props: themed, nestedTheme } = getThemedProps("Badge", useTheme(), theme ?? {});
23281
+ const tone = explicitTone ?? themed.tone ?? "neutral";
23282
+ const variant = explicitVariant ?? themed.variant ?? "solid";
23283
+ const size = explicitSize ?? themed.size ?? "medium";
23284
+ const sizeConfig = getBadgeSizeConfig(size);
23285
+ const colors = resolveBadgeColors(tone, variant);
23286
+ const disabledAlpha = explicitDisabledAlpha ?? themed.disabledAlpha ?? .5;
23287
+ const effectiveAlpha = disabled ? disabledAlpha : alpha;
23288
+ const resolvedMaxCount = maxCount ?? themed.maxCount ?? 99;
23289
+ const text = count !== void 0 ? formatBadgeCount({
23290
+ count,
23291
+ maxCount: resolvedMaxCount
23292
+ }) : label !== void 0 ? `${label}` : "";
23293
+ const resolvedTextStyle = resolveBadgeTextStyle({
23294
+ size,
23295
+ textColor: colors.textColor,
23296
+ themedTextStyle: themed.textStyle,
23297
+ explicitTextStyle
23298
+ });
23299
+ const dotSize = themed.dotSize ?? sizeConfig.dotSize;
23300
+ if (dot) return /* @__PURE__ */ require_jsx_runtime.jsx(View, {
23301
+ ...viewProps,
23302
+ width: width ?? dotSize,
23303
+ height: height ?? dotSize,
23304
+ backgroundColor: backgroundColor ?? themed.backgroundColor ?? colors.backgroundColor,
23305
+ backgroundAlpha: backgroundAlpha ?? themed.backgroundAlpha ?? colors.backgroundAlpha,
23306
+ borderColor: borderColor ?? themed.borderColor ?? colors.borderColor,
23307
+ borderWidth: borderWidth ?? themed.borderWidth ?? colors.borderWidth,
23308
+ cornerRadius: cornerRadius ?? themed.cornerRadius ?? dotSize / 2,
23309
+ ...effectiveAlpha !== void 0 ? { alpha: effectiveAlpha } : {},
23310
+ theme: nestedTheme
23311
+ });
23312
+ return /* @__PURE__ */ require_jsx_runtime.jsx(View, {
23313
+ ...viewProps,
23314
+ width: width ?? themed.width ?? "auto",
23315
+ height: height ?? themed.height ?? sizeConfig.height,
23316
+ direction: "row",
23317
+ alignItems: "center",
23318
+ justifyContent: "center",
23319
+ gap: gap ?? themed.gap ?? sizeConfig.gap,
23320
+ padding: padding ?? themed.padding ?? sizeConfig.padding,
23321
+ backgroundColor: backgroundColor ?? themed.backgroundColor ?? colors.backgroundColor,
23322
+ backgroundAlpha: backgroundAlpha ?? themed.backgroundAlpha ?? colors.backgroundAlpha,
23323
+ borderColor: borderColor ?? themed.borderColor ?? colors.borderColor,
23324
+ borderWidth: borderWidth ?? themed.borderWidth ?? colors.borderWidth,
23325
+ cornerRadius: cornerRadius ?? themed.cornerRadius ?? sizeConfig.cornerRadius,
23326
+ ...effectiveAlpha !== void 0 ? { alpha: effectiveAlpha } : {},
23327
+ theme: nestedTheme,
23328
+ children: children ?? /* @__PURE__ */ require_jsx_runtime.jsx(Text, {
23329
+ text,
23330
+ style: resolvedTextStyle
23331
+ })
23332
+ });
23333
+ }
23334
+ function Tag(props) {
23335
+ const { selected = false, onRemove, closeLabel = "x", tone, variant, size, theme, children, label, textStyle, ...badgeProps } = props;
23336
+ const { props: themed, nestedTheme } = getThemedProps("Tag", useTheme(), theme ?? {});
23337
+ const resolvedTone = tone ?? themed.tone ?? (selected ? "primary" : "neutral");
23338
+ const resolvedVariant = variant ?? themed.variant ?? (selected ? "solid" : "soft");
23339
+ const resolvedSize = size ?? themed.size ?? "medium";
23340
+ const resolvedTextStyle = resolveBadgeTextStyle({
23341
+ size: resolvedSize,
23342
+ textColor: resolveBadgeColors(resolvedTone, resolvedVariant).textColor,
23343
+ themedTextStyle: themed.textStyle,
23344
+ explicitTextStyle: textStyle
23345
+ });
23346
+ const text = label !== void 0 ? `${label}` : "";
23347
+ return /* @__PURE__ */ require_jsx_runtime.jsxs(Badge, {
23348
+ ...badgeProps,
23349
+ tone: resolvedTone,
23350
+ variant: resolvedVariant,
23351
+ size: resolvedSize,
23352
+ textStyle: resolvedTextStyle,
23353
+ theme: nestedTheme,
23354
+ children: [
23355
+ label !== void 0 && /* @__PURE__ */ require_jsx_runtime.jsx(Text, {
23356
+ text,
23357
+ style: resolvedTextStyle
23358
+ }),
23359
+ children,
23360
+ onRemove && /* @__PURE__ */ require_jsx_runtime.jsx(Button, {
23361
+ width: themed.closeSize ?? 16,
23362
+ height: themed.closeSize ?? 16,
23363
+ padding: 0,
23364
+ cornerRadius: (themed.closeSize ?? 16) / 2,
23365
+ onClick: onRemove,
23366
+ theme: nestedTheme,
23367
+ children: /* @__PURE__ */ require_jsx_runtime.jsx(Text, {
23368
+ text: closeLabel,
23369
+ style: resolvedTextStyle
23370
+ })
23371
+ })
23372
+ ]
23373
+ });
23374
+ }
23375
+ //#endregion
22873
23376
  //#region src/effects/use-effect.ts
22874
23377
  /**
22875
23378
  * Custom hook for Phaser game object effects (shake, pulse, etc.)
@@ -23852,87 +24355,441 @@ function Button(props) {
23852
24355
  });
23853
24356
  }
23854
24357
  //#endregion
23855
- //#region src/components/custom/Graphics.tsx
23856
- /**
23857
- * Graphics component
23858
- * Renders custom vector shapes via imperative drawing API
23859
- *
23860
- * @example
23861
- * ```tsx
23862
- * <Graphics
23863
- * onDraw={(g) => {
23864
- * g.fillStyle(0xff0000, 1)
23865
- * g.fillCircle(50, 50, 50)
23866
- * }}
23867
- * />
23868
- * ```
23869
- */
23870
- function Graphics(props) {
23871
- const { props: themed, nestedTheme } = getThemedProps("Graphics", useTheme(), props);
23872
- return /* @__PURE__ */ require_jsx_runtime.jsx("graphics", {
23873
- ...themed,
23874
- theme: nestedTheme
23875
- });
24358
+ //#region src/components/custom/Checkbox.tsx
24359
+ function getNextCheckedState(checked, tristate) {
24360
+ if (!tristate) return checked === true ? false : true;
24361
+ if (checked === false) return true;
24362
+ if (checked === true) return "indeterminate";
24363
+ return false;
23876
24364
  }
23877
- //#endregion
23878
- //#region src/components/custom/RadioButton.tsx
23879
- /** @jsxImportSource ../.. */
23880
- /**
23881
- * RadioButton component - Selectable option with circle indicator and label
23882
- */
23883
- /**
23884
- * RadioButton component - displays a selectable circle with label
23885
- * @param props - RadioButton properties
23886
- * @returns RadioButton JSX element
23887
- */
23888
- function RadioButton(props) {
23889
- const { props: themed, nestedTheme } = getThemedProps("RadioButton", void 0, {});
23890
- const size = themed.size ?? 16;
23891
- const innerSize = themed.innerSize ?? size * .75;
23892
- const innerRadius = innerSize * .5;
23893
- const outerRadius = size * .5;
24365
+ function normalizeCheckedState(checked, tristate) {
24366
+ return checked === "indeterminate" && !tristate ? false : checked;
24367
+ }
24368
+ function drawDefaultIndicator(graphics, indicatorProps) {
24369
+ const { checked, disabled, size, color, checkedColor, indeterminateColor } = indicatorProps;
24370
+ const borderWidth = Math.max(2, Math.round(size * .1));
24371
+ const inset = Math.max(4, Math.round(size * .22));
24372
+ const radius = Math.max(2, Math.round(size * .15));
24373
+ const alpha = disabled ? .45 : 1;
24374
+ graphics.clear();
24375
+ graphics.lineStyle(borderWidth, checked === false ? color : checkedColor, alpha);
24376
+ graphics.strokeRoundedRect(borderWidth / 2, borderWidth / 2, size - borderWidth, size - borderWidth, radius);
24377
+ if (checked === true) {
24378
+ graphics.lineStyle(borderWidth + 1, checkedColor, alpha);
24379
+ graphics.beginPath();
24380
+ graphics.moveTo(inset, size * .52);
24381
+ graphics.lineTo(size * .43, size - inset);
24382
+ graphics.lineTo(size - inset, inset);
24383
+ graphics.strokePath();
24384
+ }
24385
+ if (checked === "indeterminate") {
24386
+ graphics.lineStyle(borderWidth + 1, indeterminateColor, alpha);
24387
+ graphics.beginPath();
24388
+ graphics.moveTo(inset, size / 2);
24389
+ graphics.lineTo(size - inset, size / 2);
24390
+ graphics.strokePath();
24391
+ }
24392
+ }
24393
+ function Checkbox(props) {
24394
+ const { props: themed, nestedTheme } = getThemedProps("Checkbox", useTheme(), props.theme ?? {});
24395
+ const size = themed.size ?? 20;
24396
+ const gap = themed.gap ?? 8;
24397
+ const labelPosition = props.labelPosition ?? themed.labelPosition ?? "right";
24398
+ const disabled = props.disabled ?? false;
24399
+ const disabledAlpha = themed.disabledAlpha ?? .5;
24400
+ const tristate = props.tristate ?? false;
24401
+ const [checked, setChecked] = useState(normalizeCheckedState(props.checked ?? props.defaultChecked ?? false, tristate));
24402
+ useEffect(() => {
24403
+ if (props.checked !== void 0) setChecked(normalizeCheckedState(props.checked, tristate));
24404
+ }, [props.checked, tristate]);
24405
+ const indicatorProps = {
24406
+ checked,
24407
+ disabled,
24408
+ size,
24409
+ color: themed.color ?? 7829367,
24410
+ checkedColor: themed.checkedColor ?? 5025616,
24411
+ indeterminateColor: themed.indeterminateColor ?? themed.checkedColor ?? 5025616
24412
+ };
24413
+ const handleToggle = () => {
24414
+ if (disabled) return;
24415
+ const nextChecked = getNextCheckedState(checked, tristate);
24416
+ if (props.checked === void 0) setChecked(nextChecked);
24417
+ props.onChange?.(nextChecked);
24418
+ };
24419
+ const indicator = props.indicator ?? props.renderIndicator?.(indicatorProps) ?? /* @__PURE__ */ require_jsx_runtime.jsx(Graphics, {
24420
+ width: size,
24421
+ height: size,
24422
+ headless: false,
24423
+ dependencies: [
24424
+ indicatorProps.checked,
24425
+ indicatorProps.disabled,
24426
+ indicatorProps.size,
24427
+ indicatorProps.color,
24428
+ indicatorProps.checkedColor,
24429
+ indicatorProps.indeterminateColor
24430
+ ],
24431
+ onDraw: (graphics) => drawDefaultIndicator(graphics, indicatorProps)
24432
+ });
24433
+ const label = props.label && labelPosition !== "none" ? /* @__PURE__ */ require_jsx_runtime.jsx(Text, {
24434
+ text: props.label,
24435
+ style: themed.labelStyle,
24436
+ alpha: disabled ? disabledAlpha : 1
24437
+ }) : null;
23894
24438
  return /* @__PURE__ */ require_jsx_runtime.jsxs(View, {
23895
24439
  direction: "row",
23896
24440
  alignItems: "center",
23897
- enableGestures: true,
23898
- onTouch: () => props.onClick?.(),
24441
+ enableGestures: !disabled,
24442
+ onTouch: handleToggle,
23899
24443
  theme: nestedTheme,
23900
- gap: themed.gap,
23901
- children: [/* @__PURE__ */ require_jsx_runtime.jsx(View, {
23902
- width: size,
23903
- height: size,
23904
- backgroundColor: themed.color,
23905
- alignItems: "center",
23906
- justifyContent: "center",
23907
- backgroundAlpha: 1,
23908
- padding: 0,
23909
- cornerRadius: outerRadius,
23910
- children: /* @__PURE__ */ require_jsx_runtime.jsx(View, {
23911
- width: innerSize,
23912
- height: innerSize,
23913
- backgroundColor: themed.selectedColor,
23914
- visible: props.selected ?? false,
23915
- cornerRadius: innerRadius
23916
- })
23917
- }), /* @__PURE__ */ require_jsx_runtime.jsx(Text, {
23918
- text: props.label,
23919
- style: themed.labelStyle
23920
- })]
24444
+ gap,
24445
+ alpha: disabled ? disabledAlpha : 1,
24446
+ children: [
24447
+ labelPosition === "left" && label,
24448
+ indicator,
24449
+ labelPosition !== "left" && label
24450
+ ]
23921
24451
  }, props.key);
23922
24452
  }
23923
24453
  //#endregion
23924
- //#region src/components/custom/RadioGroup.tsx
23925
- /** @jsxImportSource ../.. */
24454
+ //#region src/design-tokens/use-theme-tokens.ts
23926
24455
  /**
23927
- * RadioGroup component - Manages a group of radio buttons with single-selection logic
24456
+ * Hook to access complete design token system
24457
+ * Combines colors with text styles, spacing, sizes, and radius tokens
23928
24458
  */
23929
24459
  /**
23930
- * RadioGroup component - displays a group of radio buttons with single-selection
23931
- * @param props - RadioGroup properties
23932
- * @returns RadioGroup JSX element
23933
- */
23934
- function RadioGroup(props) {
23935
- const { props: themed, nestedTheme } = getThemedProps("RadioButton", void 0, {});
24460
+ * Hook to access complete design token system from theme context
24461
+ * Provides colors, text styles, spacing, sizes, and radius tokens
24462
+ * Automatically updates when color mode or preset changes
24463
+ * @returns Current DesignTokens or undefined
24464
+ * @example
24465
+ * ```typescript
24466
+ * function MyComponent() {
24467
+ * const tokens = useThemeTokens()
24468
+ *
24469
+ * if (!tokens) return null
24470
+ *
24471
+ * return (
24472
+ * <View
24473
+ * backgroundColor={tokens.colors.surface.DEFAULT}
24474
+ * padding={tokens.spacing.lg}
24475
+ * cornerRadius={tokens.radius.md}
24476
+ * >
24477
+ * <Text text="Title" style={tokens.textStyles.title} />
24478
+ * <Text text="Body text" style={tokens.textStyles.DEFAULT} />
24479
+ * </View>
24480
+ * )
24481
+ * }
24482
+ * ```
24483
+ */
24484
+ function useThemeTokens() {
24485
+ const localTheme = useTheme();
24486
+ const getInitialTokens = () => {
24487
+ if (localTheme?.__colorPreset) {
24488
+ const preset = getPresetWithMode(localTheme.__colorPreset.name, localTheme.__colorPreset.mode ?? "light");
24489
+ return {
24490
+ colors: preset.colors,
24491
+ textStyles: createTextStyleTokens(preset.colors.text.DEFAULT.toString()),
24492
+ spacing: defaultSpacingTokens,
24493
+ sizes: defaultSizeTokens,
24494
+ radius: defaultRadiusTokens
24495
+ };
24496
+ }
24497
+ const colors = themeRegistry.getColorTokens();
24498
+ if (!colors) return void 0;
24499
+ return {
24500
+ colors,
24501
+ textStyles: createTextStyleTokens(colors.text.DEFAULT.toString()),
24502
+ spacing: defaultSpacingTokens,
24503
+ sizes: defaultSizeTokens,
24504
+ radius: defaultRadiusTokens
24505
+ };
24506
+ };
24507
+ const [tokens, setTokens] = useState(getInitialTokens());
24508
+ const [, forceUpdate] = useState(0);
24509
+ useEffect(() => {
24510
+ return themeRegistry.subscribe(() => {
24511
+ if (localTheme?.__colorPreset) {
24512
+ const currentMode = themeRegistry.getColorMode();
24513
+ const preset = getPresetWithMode(localTheme.__colorPreset.name, currentMode);
24514
+ setTokens({
24515
+ colors: preset.colors,
24516
+ textStyles: createTextStyleTokens(preset.colors.text.DEFAULT.toString()),
24517
+ spacing: defaultSpacingTokens,
24518
+ sizes: defaultSizeTokens,
24519
+ radius: defaultRadiusTokens
24520
+ });
24521
+ } else {
24522
+ const colors = themeRegistry.getColorTokens();
24523
+ if (colors) setTokens({
24524
+ colors,
24525
+ textStyles: createTextStyleTokens(colors.text.DEFAULT.toString()),
24526
+ spacing: defaultSpacingTokens,
24527
+ sizes: defaultSizeTokens,
24528
+ radius: defaultRadiusTokens
24529
+ });
24530
+ }
24531
+ forceUpdate((n) => n + 1);
24532
+ });
24533
+ }, [localTheme]);
24534
+ return tokens;
24535
+ }
24536
+ //#endregion
24537
+ //#region src/components/custom/DebugPanel.tsx
24538
+ /** @jsxImportSource ../.. */
24539
+ /**
24540
+ * DebugPanel component - lightweight runtime diagnostics for Phaser + PhaserJSX
24541
+ * Renders selected metrics as text rows or a compact single-line summary.
24542
+ */
24543
+ var PRESET_METRICS = {
24544
+ fps: ["fps", "frameMs"],
24545
+ perf: [
24546
+ "fps",
24547
+ "frameMs",
24548
+ "renderer",
24549
+ "viewport"
24550
+ ],
24551
+ vdom: [
24552
+ "mountsTotal",
24553
+ "mountsByType",
24554
+ "mountsByParent",
24555
+ "mountsByKey"
24556
+ ],
24557
+ textures: ["textureCount"],
24558
+ full: [
24559
+ "fps",
24560
+ "frameMs",
24561
+ "phaserVersion",
24562
+ "renderer",
24563
+ "viewport",
24564
+ "textureCount",
24565
+ "mountsTotal",
24566
+ "mountsByType",
24567
+ "mountsByParent",
24568
+ "mountsByKey",
24569
+ "debugFlags"
24570
+ ]
24571
+ };
24572
+ var DEFAULT_LABELS = {
24573
+ fps: "FPS",
24574
+ frameMs: "Frame ms",
24575
+ phaserVersion: "Phaser",
24576
+ renderer: "Renderer",
24577
+ viewport: "Viewport",
24578
+ textureCount: "Textures",
24579
+ mountsTotal: "Mounts",
24580
+ mountsByType: "Mounts/type",
24581
+ mountsByParent: "Mounts/parent",
24582
+ mountsByKey: "Mounts/key",
24583
+ debugFlags: "Debug flags"
24584
+ };
24585
+ function resolveRendererName(scene) {
24586
+ const renderer = scene.renderer;
24587
+ const canvasType = phaser.CANVAS;
24588
+ if (renderer.type === phaser.WEBGL) return "WebGL";
24589
+ if (canvasType !== void 0 && renderer.type === canvasType) return "Canvas";
24590
+ if (renderer.constructor?.name) return renderer.constructor.name;
24591
+ if (typeof renderer.type === "number") return `Type ${renderer.type}`;
24592
+ return "Unknown";
24593
+ }
24594
+ function resolveTextureCount(scene) {
24595
+ const manager = scene.textures;
24596
+ const keys = typeof manager.getTextureKeys === "function" ? manager.getTextureKeys() : Object.keys(manager.list ?? {});
24597
+ const internalKeys = new Set([
24598
+ "__DEFAULT",
24599
+ "__MISSING",
24600
+ "__NORMAL",
24601
+ "__WHITE"
24602
+ ]);
24603
+ return keys.filter((key) => !internalKeys.has(key.toUpperCase())).length;
24604
+ }
24605
+ function resolveFrameStats(scene) {
24606
+ const loop = scene.game.loop;
24607
+ const rawFps = loop?.actualFps ?? loop?.fps ?? 0;
24608
+ const rawFrameMs = loop?.delta ?? loop?.frameDelta ?? 0;
24609
+ return {
24610
+ fps: Number.isFinite(rawFps) ? Number(rawFps.toFixed(1)) : 0,
24611
+ frameMs: Number.isFinite(rawFrameMs) ? Number(rawFrameMs.toFixed(2)) : 0
24612
+ };
24613
+ }
24614
+ function summarizeMap(map, maxEntries = 3) {
24615
+ return Array.from(map.entries()).sort((a, b) => b[1] - a[1]).slice(0, maxEntries).map(([key, value]) => `${String(key)}:${value}`).join(" | ") || "-";
24616
+ }
24617
+ function resolveDebugFlags() {
24618
+ const debugConfig = DevConfig.debug;
24619
+ if (!debugConfig.enabled) return "off";
24620
+ const enabledFlags = Object.entries(debugConfig).filter(([key, value]) => key !== "enabled" && value).map(([key]) => key);
24621
+ return enabledFlags.length ? enabledFlags.join(",") : "enabled";
24622
+ }
24623
+ function collectSnapshot(scene) {
24624
+ const { fps, frameMs } = resolveFrameStats(scene);
24625
+ const mountStats = getMountStats();
24626
+ return {
24627
+ fps,
24628
+ frameMs,
24629
+ phaserVersion: phaser.VERSION,
24630
+ renderer: resolveRendererName(scene),
24631
+ viewport: `${scene.scale.width}x${scene.scale.height}`,
24632
+ textureCount: resolveTextureCount(scene),
24633
+ mountsTotal: mountStats.totalMounts,
24634
+ mountsByType: summarizeMap(mountStats.byType),
24635
+ mountsByParent: summarizeMap(mountStats.byParent),
24636
+ mountsByKey: summarizeMap(mountStats.byKey),
24637
+ debugFlags: resolveDebugFlags()
24638
+ };
24639
+ }
24640
+ function formatDefault(value) {
24641
+ return typeof value === "number" ? String(value) : value;
24642
+ }
24643
+ /**
24644
+ * DebugPanel component
24645
+ * Shows selected diagnostics from Phaser + PhaserJSX as overlay-ready content.
24646
+ */
24647
+ function DebugPanel(props) {
24648
+ const scene = useScene();
24649
+ const { preset = "fps", metrics, intervalMs = 250, compact = false, maxRows, labels, formatters, innerProps, ...viewProps } = props;
24650
+ const selectedMetrics = useMemo(() => {
24651
+ const source = metrics && metrics.length > 0 ? metrics : PRESET_METRICS[preset];
24652
+ return Array.from(new Set(source));
24653
+ }, [metrics, preset]);
24654
+ const [snapshot, setSnapshot] = useState(collectSnapshot(scene));
24655
+ useEffect(() => {
24656
+ const delay = Math.max(50, intervalMs);
24657
+ setSnapshot(collectSnapshot(scene));
24658
+ const timer = window.setInterval(() => {
24659
+ setSnapshot(collectSnapshot(scene));
24660
+ }, delay);
24661
+ return () => {
24662
+ window.clearInterval(timer);
24663
+ };
24664
+ }, [
24665
+ scene,
24666
+ intervalMs,
24667
+ selectedMetrics
24668
+ ]);
24669
+ const tokens = useThemeTokens();
24670
+ const rowStyle = tokens?.textStyles.small;
24671
+ const valueStyle = tokens?.textStyles.small;
24672
+ const rows = selectedMetrics.map((metric) => {
24673
+ const rawValue = snapshot[metric];
24674
+ const label = labels?.[metric] ?? DEFAULT_LABELS[metric];
24675
+ const formatter = formatters?.[metric];
24676
+ return {
24677
+ metric,
24678
+ label,
24679
+ value: formatter ? formatter(rawValue) : formatDefault(rawValue)
24680
+ };
24681
+ });
24682
+ const limitedRows = typeof maxRows === "number" ? rows.slice(0, Math.max(1, maxRows)) : rows;
24683
+ if (compact) {
24684
+ const compactText = limitedRows.map((row) => `${row.label} ${row.value}`).join(" | ");
24685
+ return /* @__PURE__ */ require_jsx_runtime.jsx(View, {
24686
+ ...viewProps,
24687
+ children: /* @__PURE__ */ require_jsx_runtime.jsx(Text, {
24688
+ text: compactText,
24689
+ style: valueStyle
24690
+ })
24691
+ });
24692
+ }
24693
+ return /* @__PURE__ */ require_jsx_runtime.jsx(View, {
24694
+ direction: "column",
24695
+ gap: 4,
24696
+ ...viewProps,
24697
+ children: limitedRows.map((row) => /* @__PURE__ */ require_jsx_runtime.jsxs(View, {
24698
+ direction: "row",
24699
+ gap: 8,
24700
+ ...innerProps,
24701
+ children: [/* @__PURE__ */ require_jsx_runtime.jsx(Text, {
24702
+ text: `${row.label}:`,
24703
+ style: rowStyle
24704
+ }), /* @__PURE__ */ require_jsx_runtime.jsx(Text, {
24705
+ text: row.value,
24706
+ style: valueStyle
24707
+ })]
24708
+ }, `debug-${row.metric}`))
24709
+ });
24710
+ }
24711
+ //#endregion
24712
+ //#region src/components/custom/Graphics.tsx
24713
+ /**
24714
+ * Graphics component
24715
+ * Renders custom vector shapes via imperative drawing API
24716
+ *
24717
+ * @example
24718
+ * ```tsx
24719
+ * <Graphics
24720
+ * onDraw={(g) => {
24721
+ * g.fillStyle(0xff0000, 1)
24722
+ * g.fillCircle(50, 50, 50)
24723
+ * }}
24724
+ * />
24725
+ * ```
24726
+ */
24727
+ function Graphics(props) {
24728
+ const { props: themed, nestedTheme } = getThemedProps("Graphics", useTheme(), props);
24729
+ return /* @__PURE__ */ require_jsx_runtime.jsx("graphics", {
24730
+ ...themed,
24731
+ theme: nestedTheme
24732
+ });
24733
+ }
24734
+ //#endregion
24735
+ //#region src/components/custom/RadioButton.tsx
24736
+ /** @jsxImportSource ../.. */
24737
+ /**
24738
+ * RadioButton component - Selectable option with circle indicator and label
24739
+ */
24740
+ /**
24741
+ * RadioButton component - displays a selectable circle with label
24742
+ * @param props - RadioButton properties
24743
+ * @returns RadioButton JSX element
24744
+ */
24745
+ function RadioButton(props) {
24746
+ const { props: themed, nestedTheme } = getThemedProps("RadioButton", void 0, {});
24747
+ const size = themed.size ?? 16;
24748
+ const innerSize = themed.innerSize ?? size * .75;
24749
+ const innerRadius = innerSize * .5;
24750
+ const outerRadius = size * .5;
24751
+ return /* @__PURE__ */ require_jsx_runtime.jsxs(View, {
24752
+ direction: "row",
24753
+ alignItems: "center",
24754
+ enableGestures: true,
24755
+ onTouch: () => props.onClick?.(),
24756
+ theme: nestedTheme,
24757
+ gap: themed.gap,
24758
+ children: [/* @__PURE__ */ require_jsx_runtime.jsx(View, {
24759
+ width: size,
24760
+ height: size,
24761
+ backgroundColor: themed.color,
24762
+ alignItems: "center",
24763
+ justifyContent: "center",
24764
+ backgroundAlpha: 1,
24765
+ padding: 0,
24766
+ cornerRadius: outerRadius,
24767
+ children: /* @__PURE__ */ require_jsx_runtime.jsx(View, {
24768
+ width: innerSize,
24769
+ height: innerSize,
24770
+ backgroundColor: themed.selectedColor,
24771
+ visible: props.selected ?? false,
24772
+ cornerRadius: innerRadius
24773
+ })
24774
+ }), /* @__PURE__ */ require_jsx_runtime.jsx(Text, {
24775
+ text: props.label,
24776
+ style: themed.labelStyle
24777
+ })]
24778
+ }, props.key);
24779
+ }
24780
+ //#endregion
24781
+ //#region src/components/custom/RadioGroup.tsx
24782
+ /** @jsxImportSource ../.. */
24783
+ /**
24784
+ * RadioGroup component - Manages a group of radio buttons with single-selection logic
24785
+ */
24786
+ /**
24787
+ * RadioGroup component - displays a group of radio buttons with single-selection
24788
+ * @param props - RadioGroup properties
24789
+ * @returns RadioGroup JSX element
24790
+ */
24791
+ function RadioGroup(props) {
24792
+ const { props: themed, nestedTheme } = getThemedProps("RadioButton", void 0, {});
23936
24793
  const [selected, setSelected] = useState(props.value ?? "");
23937
24794
  const handleSelect = (value) => {
23938
24795
  setSelected(value);
@@ -24064,11 +24921,506 @@ function TileSprite(props) {
24064
24921
  });
24065
24922
  }
24066
24923
  //#endregion
24067
- //#region src/components/custom/Particles.tsx
24068
- function Particles(props) {
24069
- return /* @__PURE__ */ require_jsx_runtime.jsx("particles", { ...props });
24070
- }
24071
- //#endregion
24924
+ //#region src/components/custom/Particles.tsx
24925
+ function Particles(props) {
24926
+ return /* @__PURE__ */ require_jsx_runtime.jsx("particles", { ...props });
24927
+ }
24928
+ //#endregion
24929
+ //#region src/components/custom/Portal.tsx
24930
+ /** @jsxImportSource ../.. */
24931
+ /**
24932
+ * Portal component - renders children into a separate tree at specified depth
24933
+ * Enables overlays, modals, tooltips without affecting parent tree layout
24934
+ */
24935
+ /**
24936
+ * Portal component
24937
+ * Renders child VNode into a separate container tree at specified depth.
24938
+ * Automatically injects event blockers to prevent click-through and scrolling.
24939
+ *
24940
+ * @example
24941
+ * ```tsx
24942
+ * // Basic portal (automatically blocks events in content area)
24943
+ * <Portal depth={1000}>
24944
+ * <View>Overlay content</View>
24945
+ * </Portal>
24946
+ *
24947
+ * // Non-blocking portal (allows click-through)
24948
+ * <Portal depth={500} blockEvents={false}>
24949
+ * <View>Info overlay - clickable background</View>
24950
+ * </Portal>
24951
+ *
24952
+ * // Portal with custom event handling (original handlers are preserved)
24953
+ * <Portal depth={1000}>
24954
+ * <View onTouch={(e) => console.log('clicked')}>
24955
+ * Content
24956
+ * </View>
24957
+ * </Portal>
24958
+ * ```
24959
+ */
24960
+ function Portal(props) {
24961
+ const portalId = useMemo(() => props.id ?? portalRegistry.generateId(), [props.id]);
24962
+ const depth = props.depth ?? 1e3;
24963
+ const scene = useScene();
24964
+ const blockEvents = props.blockEvents ?? true;
24965
+ const mountedNodesRef = useRef([]);
24966
+ const previousChildrenRef = useRef([]);
24967
+ const normalizeChildren = (children) => {
24968
+ if (!children) return [];
24969
+ return (Array.isArray(children) ? children.flat(Infinity) : [children]).filter((child) => !!child && typeof child === "object" && "type" in child);
24970
+ };
24971
+ useEffect(() => {
24972
+ const portalContainer = portalRegistry.register(portalId, depth, scene, {
24973
+ type: "View",
24974
+ props: {},
24975
+ children: []
24976
+ }, scene.add.container(0, 0));
24977
+ let blockerContainer = null;
24978
+ if (blockEvents) {
24979
+ blockerContainer = scene.add.container(0, 0);
24980
+ portalContainer.add(blockerContainer);
24981
+ blockerContainer.setDepth(-1);
24982
+ }
24983
+ const children = normalizeChildren(props.children);
24984
+ const mountedNodes = [];
24985
+ for (const child of children) if (child) {
24986
+ const mountedNode = mount(portalContainer, child);
24987
+ if (mountedNode) {
24988
+ portalContainer.add(mountedNode);
24989
+ mountedNodes.push(mountedNode);
24990
+ }
24991
+ }
24992
+ mountedNodesRef.current = mountedNodes;
24993
+ previousChildrenRef.current = children;
24994
+ const gestureManager = getGestureManager(scene);
24995
+ if (blockEvents && blockerContainer) {
24996
+ const blocker = blockerContainer;
24997
+ DeferredLayoutQueue.defer(() => {
24998
+ const firstChild = portalContainer.getAt(1);
24999
+ if (!firstChild) return;
25000
+ const { width, height } = firstChild.__cachedLayoutSize ? firstChild.__cachedLayoutSize : firstChild.__getLayoutSize ? firstChild.__getLayoutSize() : (() => {
25001
+ const bounds = firstChild.getBounds();
25002
+ return {
25003
+ width: bounds.width,
25004
+ height: bounds.height
25005
+ };
25006
+ })();
25007
+ const x = firstChild.x;
25008
+ const y = firstChild.y;
25009
+ blocker.setPosition(x, y);
25010
+ const hitArea = new phaser.Geom.Rectangle(0, 0, width, height);
25011
+ gestureManager.registerContainer(blocker, {
25012
+ onTouch: (e) => {
25013
+ e.stopPropagation();
25014
+ },
25015
+ onTouchMove: (e) => {
25016
+ e.stopPropagation();
25017
+ }
25018
+ }, hitArea);
25019
+ });
25020
+ }
25021
+ return () => {
25022
+ if (blockEvents && blockerContainer) gestureManager.unregisterContainer(blockerContainer);
25023
+ portalRegistry.unregister(portalId);
25024
+ };
25025
+ }, [
25026
+ portalId,
25027
+ depth,
25028
+ scene,
25029
+ blockEvents
25030
+ ]);
25031
+ useEffect(() => {
25032
+ const portal = portalRegistry.get(portalId);
25033
+ if (!portal) return;
25034
+ const portalContainer = portal.container;
25035
+ const newChildren = normalizeChildren(props.children);
25036
+ const oldChildren = previousChildrenRef.current;
25037
+ const maxLen = Math.max(oldChildren.length, newChildren.length);
25038
+ for (let i = 0; i < maxLen; i++) {
25039
+ const oldChild = oldChildren[i];
25040
+ const newChild = newChildren[i];
25041
+ if (oldChild && newChild) patchVNode(portalContainer, oldChild, newChild);
25042
+ else if (!oldChild && newChild) {
25043
+ const mountedNode = mount(portalContainer, newChild);
25044
+ if (mountedNode) {
25045
+ portalContainer.add(mountedNode);
25046
+ mountedNodesRef.current.push(mountedNode);
25047
+ }
25048
+ } else if (oldChild && !newChild) unmount(oldChild);
25049
+ }
25050
+ previousChildrenRef.current = newChildren;
25051
+ }, [props.children, portalId]);
25052
+ return null;
25053
+ }
25054
+ //#endregion
25055
+ //#region src/components/custom/Popover.tsx
25056
+ function getMainPlacement(placement) {
25057
+ return placement.split("-")[0];
25058
+ }
25059
+ function getCrossPlacement(placement) {
25060
+ return placement.split("-")[1] ?? "center";
25061
+ }
25062
+ function calculateOverlayPosition(options) {
25063
+ const { anchor, content, placement, offset } = options;
25064
+ const main = getMainPlacement(placement);
25065
+ const cross = getCrossPlacement(placement);
25066
+ let x;
25067
+ let y;
25068
+ if (main === "top" || main === "bottom") {
25069
+ y = main === "top" ? anchor.y - content.height - offset : anchor.y + anchor.height + offset;
25070
+ if (cross === "start") x = anchor.x;
25071
+ else if (cross === "end") x = anchor.x + anchor.width - content.width;
25072
+ else x = anchor.x + (anchor.width - content.width) / 2;
25073
+ } else {
25074
+ x = main === "left" ? anchor.x - content.width - offset : anchor.x + anchor.width + offset;
25075
+ if (cross === "start") y = anchor.y;
25076
+ else if (cross === "end") y = anchor.y + anchor.height - content.height;
25077
+ else y = anchor.y + (anchor.height - content.height) / 2;
25078
+ }
25079
+ const viewport = options.viewport;
25080
+ if (viewport) {
25081
+ const padding = options.viewportPadding ?? 8;
25082
+ x = Math.min(Math.max(padding, x), Math.max(padding, viewport.width - content.width - padding));
25083
+ y = Math.min(Math.max(padding, y), Math.max(padding, viewport.height - content.height - padding));
25084
+ }
25085
+ return {
25086
+ x,
25087
+ y,
25088
+ placement
25089
+ };
25090
+ }
25091
+ function getLayoutSize(container) {
25092
+ const withLayout = container;
25093
+ return withLayout?.__getLayoutSize?.() ?? withLayout?.__cachedLayoutSize ?? {
25094
+ width: 0,
25095
+ height: 0
25096
+ };
25097
+ }
25098
+ function getWorldPosition(container) {
25099
+ const matrix = container.getWorldTransformMatrix?.();
25100
+ if (matrix) return {
25101
+ x: matrix.tx,
25102
+ y: matrix.ty
25103
+ };
25104
+ return {
25105
+ x: container.x ?? 0,
25106
+ y: container.y ?? 0
25107
+ };
25108
+ }
25109
+ function getAnchorRect(container) {
25110
+ if (!container) return {
25111
+ x: 0,
25112
+ y: 0,
25113
+ width: 0,
25114
+ height: 0
25115
+ };
25116
+ const size = getLayoutSize(container);
25117
+ const position = getWorldPosition(container);
25118
+ return {
25119
+ x: position.x,
25120
+ y: position.y,
25121
+ width: size.width,
25122
+ height: size.height
25123
+ };
25124
+ }
25125
+ var FALLBACK_CONTENT_SIZE = {
25126
+ width: 220,
25127
+ height: 120
25128
+ };
25129
+ function assignRef(ref, value) {
25130
+ if (!ref) return;
25131
+ if (typeof ref === "function") {
25132
+ ref(value);
25133
+ return;
25134
+ }
25135
+ ref.current = value;
25136
+ }
25137
+ function Popover(props) {
25138
+ const { props: themed, nestedTheme } = getThemedProps("Popover", useTheme(), props.theme ?? {});
25139
+ const scene = useScene();
25140
+ const triggerRef = useRef(null);
25141
+ const contentRef = useRef(null);
25142
+ const [measuredContentSize, setMeasuredContentSize] = useState(null);
25143
+ const [internalOpen, setInternalOpen] = useState(props.defaultOpen ?? false);
25144
+ const isControlled = props.isOpen !== void 0;
25145
+ const isOpen = isControlled ? props.isOpen === true : internalOpen;
25146
+ const placement = props.placement ?? themed.placement ?? "bottom";
25147
+ const offset = props.offset ?? themed.offset ?? 8;
25148
+ const depth = props.depth ?? themed.depth ?? 1100;
25149
+ const closeOnOutside = props.closeOnOutside ?? themed.closeOnOutside ?? true;
25150
+ const closeOnEscape = props.closeOnEscape ?? themed.closeOnEscape ?? true;
25151
+ const viewportPadding = props.viewportPadding ?? themed.viewportPadding ?? 8;
25152
+ const explicitContentWidth = props.contentWidth ?? themed.contentWidth;
25153
+ const explicitContentHeight = props.contentHeight ?? themed.contentHeight;
25154
+ const viewport = portalRegistry.getViewportSize(scene);
25155
+ const anchor = getAnchorRect(triggerRef.current);
25156
+ const measuredWidth = measuredContentSize?.width ?? FALLBACK_CONTENT_SIZE.width;
25157
+ const measuredHeight = measuredContentSize?.height ?? FALLBACK_CONTENT_SIZE.height;
25158
+ const isPositionReady = !(explicitContentWidth === void 0 || explicitContentHeight === void 0) || measuredContentSize !== null;
25159
+ const contentWidth = props.matchTriggerWidth ? Math.max(anchor.width, explicitContentWidth ?? measuredWidth) : explicitContentWidth ?? measuredWidth;
25160
+ const overlayPosition = calculateOverlayPosition({
25161
+ anchor,
25162
+ content: {
25163
+ width: contentWidth,
25164
+ height: explicitContentHeight ?? measuredHeight
25165
+ },
25166
+ placement,
25167
+ offset,
25168
+ viewport,
25169
+ viewportPadding
25170
+ });
25171
+ const setOpen = (open) => {
25172
+ if (props.disabled && open) return;
25173
+ if (!isControlled) setInternalOpen(open);
25174
+ props.onOpenChange?.(open);
25175
+ };
25176
+ const toggleOpen = (event) => {
25177
+ event.stopPropagation();
25178
+ setOpen(!isOpen);
25179
+ };
25180
+ const close = (event) => {
25181
+ event?.stopPropagation();
25182
+ setOpen(false);
25183
+ };
25184
+ const contentProps = props.contentProps ?? {};
25185
+ const contentPropsRef = contentProps.ref;
25186
+ const contentPropsAlpha = contentProps.alpha;
25187
+ const handleContentRef = (container) => {
25188
+ contentRef.current = container;
25189
+ assignRef(contentPropsRef, container);
25190
+ };
25191
+ useEffect(() => {
25192
+ if (!closeOnEscape || !isOpen) return;
25193
+ const handleKeyDown = (event) => {
25194
+ if (event.key === "Escape") setOpen(false);
25195
+ };
25196
+ window.addEventListener("keydown", handleKeyDown);
25197
+ return () => window.removeEventListener("keydown", handleKeyDown);
25198
+ }, [closeOnEscape, isOpen]);
25199
+ useEffect(() => {
25200
+ if (!isOpen) return;
25201
+ DeferredLayoutQueue.defer(() => {
25202
+ const size = getLayoutSize(contentRef.current);
25203
+ if (size.width <= 0 || size.height <= 0) return;
25204
+ setMeasuredContentSize((current) => {
25205
+ if (current?.width === size.width && current?.height === size.height) return current;
25206
+ return {
25207
+ width: size.width,
25208
+ height: size.height
25209
+ };
25210
+ });
25211
+ });
25212
+ }, [
25213
+ isOpen,
25214
+ props.children,
25215
+ explicitContentWidth,
25216
+ explicitContentHeight
25217
+ ]);
25218
+ return /* @__PURE__ */ require_jsx_runtime.jsxs(require_jsx_runtime.Fragment, { children: [/* @__PURE__ */ require_jsx_runtime.jsx(View, {
25219
+ ...props.triggerProps ?? {},
25220
+ ref: triggerRef,
25221
+ enableGestures: !props.disabled,
25222
+ onTouch: toggleOpen,
25223
+ children: props.trigger
25224
+ }), isOpen && /* @__PURE__ */ require_jsx_runtime.jsxs(Portal, {
25225
+ depth,
25226
+ blockEvents: false,
25227
+ children: [closeOnOutside && /* @__PURE__ */ require_jsx_runtime.jsx(View, {
25228
+ width: viewport.width,
25229
+ height: viewport.height,
25230
+ backgroundColor: 0,
25231
+ backgroundAlpha: 0,
25232
+ onTouch: close,
25233
+ onTouchMove: (event) => event.stopPropagation()
25234
+ }), /* @__PURE__ */ require_jsx_runtime.jsx(View, {
25235
+ ...contentProps,
25236
+ ref: handleContentRef,
25237
+ x: overlayPosition.x,
25238
+ y: overlayPosition.y,
25239
+ ...props.matchTriggerWidth || explicitContentWidth !== void 0 ? { width: contentWidth } : {},
25240
+ direction: "column",
25241
+ backgroundColor: themed.backgroundColor ?? 1120295,
25242
+ borderColor: themed.borderColor ?? 3359061,
25243
+ borderWidth: themed.borderWidth ?? 1,
25244
+ cornerRadius: themed.cornerRadius ?? 8,
25245
+ padding: themed.padding ?? 10,
25246
+ gap: themed.gap ?? 8,
25247
+ ...isPositionReady ? contentPropsAlpha !== void 0 ? { alpha: contentPropsAlpha } : {} : { alpha: 0 },
25248
+ onTouch: (event) => event.stopPropagation(),
25249
+ theme: nestedTheme,
25250
+ children: props.children
25251
+ })]
25252
+ })] });
25253
+ }
25254
+ function ContextMenu(props) {
25255
+ const { items, width: explicitWidth, onSelect, isOpen: explicitOpen, defaultOpen, onOpenChange, ...popoverProps } = props;
25256
+ const { props: themed, nestedTheme } = getThemedProps("ContextMenu", useTheme(), props.theme ?? {});
25257
+ const [internalOpen, setInternalOpen] = useState(defaultOpen ?? false);
25258
+ const isControlled = explicitOpen !== void 0;
25259
+ const isOpen = isControlled ? explicitOpen === true : internalOpen;
25260
+ const width = explicitWidth ?? themed.width ?? 220;
25261
+ const itemHeight = themed.itemHeight ?? 34;
25262
+ const setOpen = (open) => {
25263
+ if (!isControlled) setInternalOpen(open);
25264
+ onOpenChange?.(open);
25265
+ };
25266
+ const handleSelect = (item) => {
25267
+ if (item.disabled) return;
25268
+ item.onSelect?.();
25269
+ onSelect?.(item);
25270
+ setOpen(false);
25271
+ };
25272
+ return /* @__PURE__ */ require_jsx_runtime.jsx(Popover, {
25273
+ ...popoverProps,
25274
+ isOpen,
25275
+ onOpenChange: setOpen,
25276
+ contentWidth: width,
25277
+ contentHeight: Math.max(1, items.length) * itemHeight + 20,
25278
+ contentProps: {
25279
+ padding: themed.padding ?? 6,
25280
+ gap: themed.gap ?? 2,
25281
+ backgroundColor: themed.backgroundColor,
25282
+ borderColor: themed.borderColor,
25283
+ borderWidth: themed.borderWidth,
25284
+ cornerRadius: themed.cornerRadius,
25285
+ theme: nestedTheme
25286
+ },
25287
+ children: items.map((item) => {
25288
+ const textColor = item.disabled ? themed.disabledTextColor ?? "#64748b" : item.danger ? themed.dangerTextColor ?? "#fecaca" : themed.textStyle?.color ?? "#f8fafc";
25289
+ return /* @__PURE__ */ require_jsx_runtime.jsxs(View, {
25290
+ width: "fill",
25291
+ height: itemHeight,
25292
+ direction: "row",
25293
+ alignItems: "center",
25294
+ gap: themed.itemGap ?? 8,
25295
+ padding: themed.itemPadding ?? {
25296
+ left: 10,
25297
+ right: 10,
25298
+ top: 6,
25299
+ bottom: 6
25300
+ },
25301
+ cornerRadius: themed.itemCornerRadius ?? 5,
25302
+ backgroundColor: item.danger ? themed.dangerBackgroundColor ?? 2042167 : 0,
25303
+ backgroundAlpha: item.danger ? .35 : 0,
25304
+ enableGestures: !item.disabled,
25305
+ onTouch: (event) => {
25306
+ event.stopPropagation();
25307
+ handleSelect(item);
25308
+ },
25309
+ children: [
25310
+ item.prefix,
25311
+ /* @__PURE__ */ require_jsx_runtime.jsx(View, {
25312
+ flex: 1,
25313
+ children: /* @__PURE__ */ require_jsx_runtime.jsx(Text, {
25314
+ text: item.label,
25315
+ style: {
25316
+ ...themed.textStyle ?? {},
25317
+ color: textColor
25318
+ }
25319
+ })
25320
+ }),
25321
+ item.suffix
25322
+ ]
25323
+ }, item.id);
25324
+ })
25325
+ });
25326
+ }
25327
+ //#endregion
25328
+ //#region src/components/custom/ProgressBar.tsx
25329
+ /** @jsxImportSource ../.. */
25330
+ /**
25331
+ * ProgressBar component - determinate progress indicator for health, loading, cooldowns, and XP.
25332
+ */
25333
+ function clampProgressValue(value, min, max) {
25334
+ if (!Number.isFinite(value)) return min;
25335
+ return Math.min(Math.max(value, min), max);
25336
+ }
25337
+ function getProgressRatio(value, min, max) {
25338
+ if (!Number.isFinite(min) || !Number.isFinite(max) || min === max) return 0;
25339
+ const normalizedMin = Math.min(min, max);
25340
+ const normalizedMax = Math.max(min, max);
25341
+ return (clampProgressValue(value, normalizedMin, normalizedMax) - normalizedMin) / (normalizedMax - normalizedMin);
25342
+ }
25343
+ function formatDefaultValue(props) {
25344
+ return `${Math.round(props.percent)}%`;
25345
+ }
25346
+ function ProgressBar(props) {
25347
+ const { value, min = 0, max = 100, orientation: explicitOrientation, label, showValue, labelPosition: explicitLabelPosition, formatValue, trackColor: explicitTrackColor, fillColor: explicitFillColor, borderColor: explicitBorderColor, borderWidth: explicitBorderWidth, cornerRadius: explicitCornerRadius, labelStyle: explicitLabelStyle, disabled = false, disabledAlpha: explicitDisabledAlpha, theme, width, height, alpha, ...viewProps } = props;
25348
+ const { props: themed, nestedTheme } = getThemedProps("ProgressBar", useTheme(), theme ?? {});
25349
+ const orientation = explicitOrientation ?? themed.orientation ?? "horizontal";
25350
+ const ratio = getProgressRatio(value, min, max);
25351
+ const percent = ratio * 100;
25352
+ const labelPosition = explicitLabelPosition ?? themed.labelPosition ?? (showValue || label ? "right" : "none");
25353
+ const shouldShowText = labelPosition !== "none" && Boolean(showValue || label);
25354
+ const text = shouldShowText ? [label, showValue ? (formatValue ?? themed.formatValue ?? formatDefaultValue)({
25355
+ value,
25356
+ min,
25357
+ max,
25358
+ ratio,
25359
+ percent
25360
+ }) : void 0].filter(Boolean).join(" ") : "";
25361
+ const resolvedWidth = width ?? themed.width ?? (orientation === "horizontal" ? 240 : 24);
25362
+ const resolvedHeight = height ?? themed.height ?? (orientation === "horizontal" ? 22 : 160);
25363
+ const trackColor = explicitTrackColor ?? themed.trackColor ?? 2042167;
25364
+ const fillColor = explicitFillColor ?? themed.fillColor ?? 2278750;
25365
+ const borderColor = explicitBorderColor ?? themed.borderColor ?? 3359061;
25366
+ const borderWidth = explicitBorderWidth ?? themed.borderWidth ?? 1;
25367
+ const cornerRadius = explicitCornerRadius ?? themed.cornerRadius ?? (orientation === "horizontal" ? 11 : 8);
25368
+ const labelStyle = explicitLabelStyle ?? themed.labelStyle ?? {
25369
+ color: "#ffffff",
25370
+ fontSize: "12px"
25371
+ };
25372
+ const disabledAlpha = explicitDisabledAlpha ?? themed.disabledAlpha ?? .5;
25373
+ const effectiveAlpha = disabled ? disabledAlpha : alpha;
25374
+ const rootDirection = labelPosition === "left" || labelPosition === "right" ? "row" : labelPosition === "top" || labelPosition === "bottom" ? "column" : "column";
25375
+ const labelNode = shouldShowText ? /* @__PURE__ */ require_jsx_runtime.jsx(Text, {
25376
+ text,
25377
+ style: labelStyle
25378
+ }) : null;
25379
+ const fillProps = orientation === "horizontal" ? {
25380
+ width: `${percent}%`,
25381
+ height: "fill"
25382
+ } : {
25383
+ width: "fill",
25384
+ height: `${percent}%`
25385
+ };
25386
+ const track = /* @__PURE__ */ require_jsx_runtime.jsxs(View, {
25387
+ width: resolvedWidth,
25388
+ height: resolvedHeight,
25389
+ direction: orientation === "horizontal" ? "stack" : "column",
25390
+ justifyContent: orientation === "vertical" ? "end" : "start",
25391
+ backgroundColor: trackColor,
25392
+ borderColor,
25393
+ borderWidth,
25394
+ cornerRadius,
25395
+ overflow: "hidden",
25396
+ theme: nestedTheme,
25397
+ children: [/* @__PURE__ */ require_jsx_runtime.jsx(View, {
25398
+ ...fillProps,
25399
+ backgroundColor: fillColor,
25400
+ cornerRadius
25401
+ }), labelPosition === "inside" && shouldShowText && /* @__PURE__ */ require_jsx_runtime.jsx(View, {
25402
+ width: "fill",
25403
+ height: "fill",
25404
+ justifyContent: "center",
25405
+ alignItems: "center",
25406
+ children: labelNode
25407
+ })]
25408
+ });
25409
+ return /* @__PURE__ */ require_jsx_runtime.jsxs(View, {
25410
+ ...viewProps,
25411
+ direction: rootDirection,
25412
+ alignItems: "center",
25413
+ gap: themed.gap ?? 8,
25414
+ ...effectiveAlpha !== void 0 ? { alpha: effectiveAlpha } : {},
25415
+ theme: nestedTheme,
25416
+ children: [
25417
+ labelPosition === "top" || labelPosition === "left" ? labelNode : null,
25418
+ track,
25419
+ labelPosition !== "inside" && (labelPosition === "bottom" || labelPosition === "right") ? labelNode : null
25420
+ ]
25421
+ });
25422
+ }
25423
+ //#endregion
24072
25424
  //#region src/components/index.ts
24073
25425
  /**
24074
25426
  * Built-in component implementations
@@ -24082,7 +25434,6 @@ function Particles(props) {
24082
25434
  /**
24083
25435
  * Registers all built-in components with the host
24084
25436
  * This should be called during library initialization
24085
- * Note: TileSprite is currently dummy (throws on use)
24086
25437
  */
24087
25438
  function registerBuiltins() {
24088
25439
  register("view", {
@@ -25217,132 +26568,6 @@ function Accordion(props) {
25217
26568
  }, props.key);
25218
26569
  }
25219
26570
  //#endregion
25220
- //#region src/components/custom/Portal.tsx
25221
- /** @jsxImportSource ../.. */
25222
- /**
25223
- * Portal component - renders children into a separate tree at specified depth
25224
- * Enables overlays, modals, tooltips without affecting parent tree layout
25225
- */
25226
- /**
25227
- * Portal component
25228
- * Renders child VNode into a separate container tree at specified depth.
25229
- * Automatically injects event blockers to prevent click-through and scrolling.
25230
- *
25231
- * @example
25232
- * ```tsx
25233
- * // Basic portal (automatically blocks events in content area)
25234
- * <Portal depth={1000}>
25235
- * <View>Overlay content</View>
25236
- * </Portal>
25237
- *
25238
- * // Non-blocking portal (allows click-through)
25239
- * <Portal depth={500} blockEvents={false}>
25240
- * <View>Info overlay - clickable background</View>
25241
- * </Portal>
25242
- *
25243
- * // Portal with custom event handling (original handlers are preserved)
25244
- * <Portal depth={1000}>
25245
- * <View onTouch={(e) => console.log('clicked')}>
25246
- * Content
25247
- * </View>
25248
- * </Portal>
25249
- * ```
25250
- */
25251
- function Portal(props) {
25252
- const portalId = useMemo(() => props.id ?? portalRegistry.generateId(), [props.id]);
25253
- const depth = props.depth ?? 1e3;
25254
- const scene = useScene();
25255
- const blockEvents = props.blockEvents ?? true;
25256
- const mountedNodesRef = useRef([]);
25257
- const previousChildrenRef = useRef([]);
25258
- const normalizeChildren = (children) => {
25259
- if (!children) return [];
25260
- return (Array.isArray(children) ? children.flat(Infinity) : [children]).filter((child) => !!child && typeof child === "object" && "type" in child);
25261
- };
25262
- useEffect(() => {
25263
- const portalContainer = portalRegistry.register(portalId, depth, scene, {
25264
- type: "View",
25265
- props: {},
25266
- children: []
25267
- }, scene.add.container(0, 0));
25268
- let blockerContainer = null;
25269
- if (blockEvents) {
25270
- blockerContainer = scene.add.container(0, 0);
25271
- portalContainer.add(blockerContainer);
25272
- blockerContainer.setDepth(-1);
25273
- }
25274
- const children = normalizeChildren(props.children);
25275
- const mountedNodes = [];
25276
- for (const child of children) if (child) {
25277
- const mountedNode = mount(portalContainer, child);
25278
- if (mountedNode) {
25279
- portalContainer.add(mountedNode);
25280
- mountedNodes.push(mountedNode);
25281
- }
25282
- }
25283
- mountedNodesRef.current = mountedNodes;
25284
- previousChildrenRef.current = children;
25285
- const gestureManager = getGestureManager(scene);
25286
- if (blockEvents && blockerContainer) {
25287
- const blocker = blockerContainer;
25288
- DeferredLayoutQueue.defer(() => {
25289
- const firstChild = portalContainer.getAt(1);
25290
- if (!firstChild) return;
25291
- const { width, height } = firstChild.__cachedLayoutSize ? firstChild.__cachedLayoutSize : firstChild.__getLayoutSize ? firstChild.__getLayoutSize() : (() => {
25292
- const bounds = firstChild.getBounds();
25293
- return {
25294
- width: bounds.width,
25295
- height: bounds.height
25296
- };
25297
- })();
25298
- const x = firstChild.x;
25299
- const y = firstChild.y;
25300
- blocker.setPosition(x, y);
25301
- const hitArea = new phaser.Geom.Rectangle(0, 0, width, height);
25302
- gestureManager.registerContainer(blocker, {
25303
- onTouch: (e) => {
25304
- e.stopPropagation();
25305
- },
25306
- onTouchMove: (e) => {
25307
- e.stopPropagation();
25308
- }
25309
- }, hitArea);
25310
- });
25311
- }
25312
- return () => {
25313
- if (blockEvents && blockerContainer) gestureManager.unregisterContainer(blockerContainer);
25314
- portalRegistry.unregister(portalId);
25315
- };
25316
- }, [
25317
- portalId,
25318
- depth,
25319
- scene,
25320
- blockEvents
25321
- ]);
25322
- useEffect(() => {
25323
- const portal = portalRegistry.get(portalId);
25324
- if (!portal) return;
25325
- const portalContainer = portal.container;
25326
- const newChildren = normalizeChildren(props.children);
25327
- const oldChildren = previousChildrenRef.current;
25328
- const maxLen = Math.max(oldChildren.length, newChildren.length);
25329
- for (let i = 0; i < maxLen; i++) {
25330
- const oldChild = oldChildren[i];
25331
- const newChild = newChildren[i];
25332
- if (oldChild && newChild) patchVNode(portalContainer, oldChild, newChild);
25333
- else if (!oldChild && newChild) {
25334
- const mountedNode = mount(portalContainer, newChild);
25335
- if (mountedNode) {
25336
- portalContainer.add(mountedNode);
25337
- mountedNodesRef.current.push(mountedNode);
25338
- }
25339
- } else if (oldChild && !newChild) unmount(oldChild);
25340
- }
25341
- previousChildrenRef.current = newChildren;
25342
- }, [props.children, portalId]);
25343
- return null;
25344
- }
25345
- //#endregion
25346
26571
  //#region src/components/custom/Modal.tsx
25347
26572
  /** @jsxImportSource ../.. */
25348
26573
  /**
@@ -26566,442 +27791,184 @@ function CharTextInput(props) {
26566
27791
  */
26567
27792
  const isPrintableKey = (event) => {
26568
27793
  if (event.ctrlKey || event.metaKey || event.altKey) return false;
26569
- if (event.key.length === 1) return true;
26570
- return false;
26571
- };
26572
- /**
26573
- * Handle printable character input
26574
- */
26575
- const handleCharacterInput = (char) => {
26576
- const currentValue = refCurrentValue.current;
26577
- const cursorPosition = refCursorPosition.current;
26578
- const anchor = refSelectionAnchor.current;
26579
- let newValue;
26580
- let newCursorPos;
26581
- if (anchor >= 0) {
26582
- const selStart = Math.min(anchor, cursorPosition);
26583
- const selEnd = Math.max(anchor, cursorPosition);
26584
- newValue = currentValue.slice(0, selStart) + char + currentValue.slice(selEnd);
26585
- newCursorPos = selStart + char.length;
26586
- } else {
26587
- newValue = currentValue.slice(0, cursorPosition) + char + currentValue.slice(cursorPosition);
26588
- newCursorPos = cursorPosition + char.length;
26589
- }
26590
- if (props.maxLength !== void 0 && newValue.length > props.maxLength) return;
26591
- if (props.multiline && char === "\n" && props.maxLines !== void 0) {
26592
- if (newValue.split("\n").length > props.maxLines) return;
26593
- }
26594
- if (!props.multiline && charTextApiRef.current) {
26595
- const insertPosition = anchor >= 0 ? Math.min(anchor, cursorPosition) : cursorPosition;
26596
- if (!charTextApiRef.current.canFitChar(char, insertPosition)) return;
26597
- }
26598
- updateValue(newValue);
26599
- setCursorPosition(newCursorPos);
26600
- refCursorPosition.current = newCursorPos;
26601
- setSelectionAnchor(-1);
26602
- refSelectionAnchor.current = -1;
26603
- };
26604
- /**
26605
- * Handle backspace key
26606
- */
26607
- const handleBackspace = (event) => {
26608
- event.preventDefault();
26609
- const currentValue = refCurrentValue.current;
26610
- const cursorPosition = refCursorPosition.current;
26611
- const anchor = refSelectionAnchor.current;
26612
- if (anchor >= 0) {
26613
- const selStart = Math.min(anchor, cursorPosition);
26614
- const selEnd = Math.max(anchor, cursorPosition);
26615
- updateValue(currentValue.slice(0, selStart) + currentValue.slice(selEnd));
26616
- setCursorPosition(selStart);
26617
- setSelectionAnchor(-1);
26618
- } else if (cursorPosition > 0) {
26619
- updateValue(currentValue.slice(0, cursorPosition - 1) + currentValue.slice(cursorPosition));
26620
- setCursorPosition(cursorPosition - 1);
26621
- }
26622
- };
26623
- /**
26624
- * Handle delete key
26625
- */
26626
- const handleDelete = (event) => {
26627
- event.preventDefault();
26628
- const currentValue = refCurrentValue.current;
26629
- const cursorPosition = refCursorPosition.current;
26630
- const anchor = refSelectionAnchor.current;
26631
- if (anchor >= 0) {
26632
- const selStart = Math.min(anchor, cursorPosition);
26633
- const selEnd = Math.max(anchor, cursorPosition);
26634
- updateValue(currentValue.slice(0, selStart) + currentValue.slice(selEnd));
26635
- setCursorPosition(selStart);
26636
- setSelectionAnchor(-1);
26637
- } else if (cursorPosition < currentValue.length) updateValue(currentValue.slice(0, cursorPosition) + currentValue.slice(cursorPosition + 1));
26638
- };
26639
- /**
26640
- * Handle arrow left key
26641
- */
26642
- const handleArrowLeft = (event) => {
26643
- event.preventDefault();
26644
- const cursorPosition = refCursorPosition.current;
26645
- const anchor = refSelectionAnchor.current;
26646
- if (event.shiftKey) {
26647
- if (anchor < 0) setSelectionAnchor(cursorPosition);
26648
- if (cursorPosition > 0) setCursorPosition(cursorPosition - 1);
26649
- } else {
26650
- setSelectionAnchor(-1);
26651
- if (cursorPosition > 0) setCursorPosition(cursorPosition - 1);
26652
- }
26653
- };
26654
- /**
26655
- * Handle arrow right key
26656
- */
26657
- const handleArrowRight = (event) => {
26658
- event.preventDefault();
26659
- const currentValue = refCurrentValue.current;
26660
- const cursorPosition = refCursorPosition.current;
26661
- const anchor = refSelectionAnchor.current;
26662
- if (event.shiftKey) {
26663
- if (anchor < 0) setSelectionAnchor(cursorPosition);
26664
- if (cursorPosition < currentValue.length) setCursorPosition(cursorPosition + 1);
26665
- } else {
26666
- setSelectionAnchor(-1);
26667
- if (cursorPosition < currentValue.length) setCursorPosition(cursorPosition + 1);
26668
- }
26669
- };
26670
- /**
26671
- * Handle arrow up key (multiline)
26672
- */
26673
- const handleArrowUp = (event) => {
26674
- event.preventDefault();
26675
- };
26676
- /**
26677
- * Handle arrow down key (multiline)
26678
- */
26679
- const handleArrowDown = (event) => {
26680
- event.preventDefault();
26681
- };
26682
- /**
26683
- * Handle home key
26684
- */
26685
- const handleHome = (event) => {
26686
- event.preventDefault();
26687
- setSelectionAnchor(-1);
26688
- setCursorPosition(0);
27794
+ if (event.key.length === 1) return true;
27795
+ return false;
26689
27796
  };
26690
27797
  /**
26691
- * Handle end key
27798
+ * Handle printable character input
26692
27799
  */
26693
- const handleEnd = (event) => {
26694
- event.preventDefault();
27800
+ const handleCharacterInput = (char) => {
26695
27801
  const currentValue = refCurrentValue.current;
27802
+ const cursorPosition = refCursorPosition.current;
27803
+ const anchor = refSelectionAnchor.current;
27804
+ let newValue;
27805
+ let newCursorPos;
27806
+ if (anchor >= 0) {
27807
+ const selStart = Math.min(anchor, cursorPosition);
27808
+ const selEnd = Math.max(anchor, cursorPosition);
27809
+ newValue = currentValue.slice(0, selStart) + char + currentValue.slice(selEnd);
27810
+ newCursorPos = selStart + char.length;
27811
+ } else {
27812
+ newValue = currentValue.slice(0, cursorPosition) + char + currentValue.slice(cursorPosition);
27813
+ newCursorPos = cursorPosition + char.length;
27814
+ }
27815
+ if (props.maxLength !== void 0 && newValue.length > props.maxLength) return;
27816
+ if (props.multiline && char === "\n" && props.maxLines !== void 0) {
27817
+ if (newValue.split("\n").length > props.maxLines) return;
27818
+ }
27819
+ if (!props.multiline && charTextApiRef.current) {
27820
+ const insertPosition = anchor >= 0 ? Math.min(anchor, cursorPosition) : cursorPosition;
27821
+ if (!charTextApiRef.current.canFitChar(char, insertPosition)) return;
27822
+ }
27823
+ updateValue(newValue);
27824
+ setCursorPosition(newCursorPos);
27825
+ refCursorPosition.current = newCursorPos;
26696
27826
  setSelectionAnchor(-1);
26697
- setCursorPosition(currentValue.length);
27827
+ refSelectionAnchor.current = -1;
26698
27828
  };
26699
27829
  /**
26700
- * Handle select all (Ctrl+A / Cmd+A)
27830
+ * Handle backspace key
26701
27831
  */
26702
- const handleSelectAll = (event) => {
27832
+ const handleBackspace = (event) => {
26703
27833
  event.preventDefault();
26704
27834
  const currentValue = refCurrentValue.current;
26705
- setSelectionAnchor(0);
26706
- setCursorPosition(currentValue.length);
26707
- };
26708
- /**
26709
- * Update value (controlled or uncontrolled)
26710
- */
26711
- const updateValue = (newValue) => {
26712
- if (isControlled) props.onChange?.(newValue);
26713
- else {
26714
- setInternalValue(newValue);
26715
- props.onChange?.(newValue);
27835
+ const cursorPosition = refCursorPosition.current;
27836
+ const anchor = refSelectionAnchor.current;
27837
+ if (anchor >= 0) {
27838
+ const selStart = Math.min(anchor, cursorPosition);
27839
+ const selEnd = Math.max(anchor, cursorPosition);
27840
+ updateValue(currentValue.slice(0, selStart) + currentValue.slice(selEnd));
27841
+ setCursorPosition(selStart);
27842
+ setSelectionAnchor(-1);
27843
+ } else if (cursorPosition > 0) {
27844
+ updateValue(currentValue.slice(0, cursorPosition - 1) + currentValue.slice(cursorPosition));
27845
+ setCursorPosition(cursorPosition - 1);
26716
27846
  }
26717
27847
  };
26718
27848
  /**
26719
- * Handle cursor position change from CharText click
26720
- */
26721
- const handleCursorPositionChange = (position) => {
26722
- setCursorPosition(position);
26723
- setSelectionAnchor(-1);
26724
- };
26725
- /**
26726
- * Handle selection change from CharText drag
27849
+ * Handle delete key
26727
27850
  */
26728
- const handleSelectionChange = (start, end) => {
26729
- if (start >= 0 && end > start) {
26730
- setSelectionAnchor(start);
26731
- setCursorPosition(end);
26732
- } else setSelectionAnchor(-1);
26733
- };
26734
- const { value: _value, placeholder: _placeholder, onChange: _onChange, onFocus: _onFocus, onBlur: _onBlur, onSubmit: _onSubmit, ...viewProps } = props;
26735
- const displayText = currentValue || (props.placeholder && !isFocused ? props.placeholder : "");
26736
- return /* @__PURE__ */ require_jsx_runtime.jsx(CharText, {
26737
- forwardRef: (r) => containerRef.current = r,
26738
- ...viewProps,
26739
- text: displayText,
26740
- showCursor: isFocused,
26741
- cursorPosition,
26742
- selectionStart,
26743
- selectionEnd,
26744
- onCursorPositionChange: handleCursorPositionChange,
26745
- onSelectionChange: handleSelectionChange,
26746
- onApiReady: (api) => charTextApiRef.current = api
26747
- });
26748
- }
26749
- //#endregion
26750
- //#region src/design-tokens/use-theme-tokens.ts
26751
- /**
26752
- * Hook to access complete design token system
26753
- * Combines colors with text styles, spacing, sizes, and radius tokens
26754
- */
26755
- /**
26756
- * Hook to access complete design token system from theme context
26757
- * Provides colors, text styles, spacing, sizes, and radius tokens
26758
- * Automatically updates when color mode or preset changes
26759
- * @returns Current DesignTokens or undefined
26760
- * @example
26761
- * ```typescript
26762
- * function MyComponent() {
26763
- * const tokens = useThemeTokens()
26764
- *
26765
- * if (!tokens) return null
26766
- *
26767
- * return (
26768
- * <View
26769
- * backgroundColor={tokens.colors.surface.DEFAULT}
26770
- * padding={tokens.spacing.lg}
26771
- * cornerRadius={tokens.radius.md}
26772
- * >
26773
- * <Text text="Title" style={tokens.textStyles.title} />
26774
- * <Text text="Body text" style={tokens.textStyles.DEFAULT} />
26775
- * </View>
26776
- * )
26777
- * }
26778
- * ```
26779
- */
26780
- function useThemeTokens() {
26781
- const localTheme = useTheme();
26782
- const getInitialTokens = () => {
26783
- if (localTheme?.__colorPreset) {
26784
- const preset = getPresetWithMode(localTheme.__colorPreset.name, localTheme.__colorPreset.mode ?? "light");
26785
- return {
26786
- colors: preset.colors,
26787
- textStyles: createTextStyleTokens(preset.colors.text.DEFAULT.toString()),
26788
- spacing: defaultSpacingTokens,
26789
- sizes: defaultSizeTokens,
26790
- radius: defaultRadiusTokens
26791
- };
26792
- }
26793
- const colors = themeRegistry.getColorTokens();
26794
- if (!colors) return void 0;
26795
- return {
26796
- colors,
26797
- textStyles: createTextStyleTokens(colors.text.DEFAULT.toString()),
26798
- spacing: defaultSpacingTokens,
26799
- sizes: defaultSizeTokens,
26800
- radius: defaultRadiusTokens
26801
- };
26802
- };
26803
- const [tokens, setTokens] = useState(getInitialTokens());
26804
- const [, forceUpdate] = useState(0);
26805
- useEffect(() => {
26806
- return themeRegistry.subscribe(() => {
26807
- if (localTheme?.__colorPreset) {
26808
- const currentMode = themeRegistry.getColorMode();
26809
- const preset = getPresetWithMode(localTheme.__colorPreset.name, currentMode);
26810
- setTokens({
26811
- colors: preset.colors,
26812
- textStyles: createTextStyleTokens(preset.colors.text.DEFAULT.toString()),
26813
- spacing: defaultSpacingTokens,
26814
- sizes: defaultSizeTokens,
26815
- radius: defaultRadiusTokens
26816
- });
26817
- } else {
26818
- const colors = themeRegistry.getColorTokens();
26819
- if (colors) setTokens({
26820
- colors,
26821
- textStyles: createTextStyleTokens(colors.text.DEFAULT.toString()),
26822
- spacing: defaultSpacingTokens,
26823
- sizes: defaultSizeTokens,
26824
- radius: defaultRadiusTokens
26825
- });
26826
- }
26827
- forceUpdate((n) => n + 1);
26828
- });
26829
- }, [localTheme]);
26830
- return tokens;
26831
- }
26832
- //#endregion
26833
- //#region src/components/custom/DebugPanel.tsx
26834
- /** @jsxImportSource ../.. */
26835
- /**
26836
- * DebugPanel component - lightweight runtime diagnostics for Phaser + PhaserJSX
26837
- * Renders selected metrics as text rows or a compact single-line summary.
26838
- */
26839
- var PRESET_METRICS = {
26840
- fps: ["fps", "frameMs"],
26841
- perf: [
26842
- "fps",
26843
- "frameMs",
26844
- "renderer",
26845
- "viewport"
26846
- ],
26847
- vdom: [
26848
- "mountsTotal",
26849
- "mountsByType",
26850
- "mountsByParent",
26851
- "mountsByKey"
26852
- ],
26853
- textures: ["textureCount"],
26854
- full: [
26855
- "fps",
26856
- "frameMs",
26857
- "phaserVersion",
26858
- "renderer",
26859
- "viewport",
26860
- "textureCount",
26861
- "mountsTotal",
26862
- "mountsByType",
26863
- "mountsByParent",
26864
- "mountsByKey",
26865
- "debugFlags"
26866
- ]
26867
- };
26868
- var DEFAULT_LABELS = {
26869
- fps: "FPS",
26870
- frameMs: "Frame ms",
26871
- phaserVersion: "Phaser",
26872
- renderer: "Renderer",
26873
- viewport: "Viewport",
26874
- textureCount: "Textures",
26875
- mountsTotal: "Mounts",
26876
- mountsByType: "Mounts/type",
26877
- mountsByParent: "Mounts/parent",
26878
- mountsByKey: "Mounts/key",
26879
- debugFlags: "Debug flags"
26880
- };
26881
- function resolveRendererName(scene) {
26882
- const renderer = scene.renderer;
26883
- const canvasType = phaser.CANVAS;
26884
- if (renderer.type === phaser.WEBGL) return "WebGL";
26885
- if (canvasType !== void 0 && renderer.type === canvasType) return "Canvas";
26886
- if (renderer.constructor?.name) return renderer.constructor.name;
26887
- if (typeof renderer.type === "number") return `Type ${renderer.type}`;
26888
- return "Unknown";
26889
- }
26890
- function resolveTextureCount(scene) {
26891
- const manager = scene.textures;
26892
- const keys = typeof manager.getTextureKeys === "function" ? manager.getTextureKeys() : Object.keys(manager.list ?? {});
26893
- const internalKeys = new Set([
26894
- "__DEFAULT",
26895
- "__MISSING",
26896
- "__NORMAL",
26897
- "__WHITE"
26898
- ]);
26899
- return keys.filter((key) => !internalKeys.has(key.toUpperCase())).length;
26900
- }
26901
- function resolveFrameStats(scene) {
26902
- const loop = scene.game.loop;
26903
- const rawFps = loop?.actualFps ?? loop?.fps ?? 0;
26904
- const rawFrameMs = loop?.delta ?? loop?.frameDelta ?? 0;
26905
- return {
26906
- fps: Number.isFinite(rawFps) ? Number(rawFps.toFixed(1)) : 0,
26907
- frameMs: Number.isFinite(rawFrameMs) ? Number(rawFrameMs.toFixed(2)) : 0
27851
+ const handleDelete = (event) => {
27852
+ event.preventDefault();
27853
+ const currentValue = refCurrentValue.current;
27854
+ const cursorPosition = refCursorPosition.current;
27855
+ const anchor = refSelectionAnchor.current;
27856
+ if (anchor >= 0) {
27857
+ const selStart = Math.min(anchor, cursorPosition);
27858
+ const selEnd = Math.max(anchor, cursorPosition);
27859
+ updateValue(currentValue.slice(0, selStart) + currentValue.slice(selEnd));
27860
+ setCursorPosition(selStart);
27861
+ setSelectionAnchor(-1);
27862
+ } else if (cursorPosition < currentValue.length) updateValue(currentValue.slice(0, cursorPosition) + currentValue.slice(cursorPosition + 1));
26908
27863
  };
26909
- }
26910
- function summarizeMap(map, maxEntries = 3) {
26911
- return Array.from(map.entries()).sort((a, b) => b[1] - a[1]).slice(0, maxEntries).map(([key, value]) => `${String(key)}:${value}`).join(" | ") || "-";
26912
- }
26913
- function resolveDebugFlags() {
26914
- const debugConfig = DevConfig.debug;
26915
- if (!debugConfig.enabled) return "off";
26916
- const enabledFlags = Object.entries(debugConfig).filter(([key, value]) => key !== "enabled" && value).map(([key]) => key);
26917
- return enabledFlags.length ? enabledFlags.join(",") : "enabled";
26918
- }
26919
- function collectSnapshot(scene) {
26920
- const { fps, frameMs } = resolveFrameStats(scene);
26921
- const mountStats = getMountStats();
26922
- return {
26923
- fps,
26924
- frameMs,
26925
- phaserVersion: phaser.VERSION,
26926
- renderer: resolveRendererName(scene),
26927
- viewport: `${scene.scale.width}x${scene.scale.height}`,
26928
- textureCount: resolveTextureCount(scene),
26929
- mountsTotal: mountStats.totalMounts,
26930
- mountsByType: summarizeMap(mountStats.byType),
26931
- mountsByParent: summarizeMap(mountStats.byParent),
26932
- mountsByKey: summarizeMap(mountStats.byKey),
26933
- debugFlags: resolveDebugFlags()
27864
+ /**
27865
+ * Handle arrow left key
27866
+ */
27867
+ const handleArrowLeft = (event) => {
27868
+ event.preventDefault();
27869
+ const cursorPosition = refCursorPosition.current;
27870
+ const anchor = refSelectionAnchor.current;
27871
+ if (event.shiftKey) {
27872
+ if (anchor < 0) setSelectionAnchor(cursorPosition);
27873
+ if (cursorPosition > 0) setCursorPosition(cursorPosition - 1);
27874
+ } else {
27875
+ setSelectionAnchor(-1);
27876
+ if (cursorPosition > 0) setCursorPosition(cursorPosition - 1);
27877
+ }
26934
27878
  };
26935
- }
26936
- function formatDefault(value) {
26937
- return typeof value === "number" ? String(value) : value;
26938
- }
26939
- /**
26940
- * DebugPanel component
26941
- * Shows selected diagnostics from Phaser + PhaserJSX as overlay-ready content.
26942
- */
26943
- function DebugPanel(props) {
26944
- const scene = useScene();
26945
- const { preset = "fps", metrics, intervalMs = 250, compact = false, maxRows, labels, formatters, innerProps, ...viewProps } = props;
26946
- const selectedMetrics = useMemo(() => {
26947
- const source = metrics && metrics.length > 0 ? metrics : PRESET_METRICS[preset];
26948
- return Array.from(new Set(source));
26949
- }, [metrics, preset]);
26950
- const [snapshot, setSnapshot] = useState(collectSnapshot(scene));
26951
- useEffect(() => {
26952
- const delay = Math.max(50, intervalMs);
26953
- setSnapshot(collectSnapshot(scene));
26954
- const timer = window.setInterval(() => {
26955
- setSnapshot(collectSnapshot(scene));
26956
- }, delay);
26957
- return () => {
26958
- window.clearInterval(timer);
26959
- };
26960
- }, [
26961
- scene,
26962
- intervalMs,
26963
- selectedMetrics
26964
- ]);
26965
- const tokens = useThemeTokens();
26966
- const rowStyle = tokens?.textStyles.small;
26967
- const valueStyle = tokens?.textStyles.small;
26968
- const rows = selectedMetrics.map((metric) => {
26969
- const rawValue = snapshot[metric];
26970
- const label = labels?.[metric] ?? DEFAULT_LABELS[metric];
26971
- const formatter = formatters?.[metric];
26972
- return {
26973
- metric,
26974
- label,
26975
- value: formatter ? formatter(rawValue) : formatDefault(rawValue)
26976
- };
26977
- });
26978
- const limitedRows = typeof maxRows === "number" ? rows.slice(0, Math.max(1, maxRows)) : rows;
26979
- if (compact) {
26980
- const compactText = limitedRows.map((row) => `${row.label} ${row.value}`).join(" | ");
26981
- return /* @__PURE__ */ require_jsx_runtime.jsx(View, {
26982
- ...viewProps,
26983
- children: /* @__PURE__ */ require_jsx_runtime.jsx(Text, {
26984
- text: compactText,
26985
- style: valueStyle
26986
- })
26987
- });
26988
- }
26989
- return /* @__PURE__ */ require_jsx_runtime.jsx(View, {
26990
- direction: "column",
26991
- gap: 4,
27879
+ /**
27880
+ * Handle arrow right key
27881
+ */
27882
+ const handleArrowRight = (event) => {
27883
+ event.preventDefault();
27884
+ const currentValue = refCurrentValue.current;
27885
+ const cursorPosition = refCursorPosition.current;
27886
+ const anchor = refSelectionAnchor.current;
27887
+ if (event.shiftKey) {
27888
+ if (anchor < 0) setSelectionAnchor(cursorPosition);
27889
+ if (cursorPosition < currentValue.length) setCursorPosition(cursorPosition + 1);
27890
+ } else {
27891
+ setSelectionAnchor(-1);
27892
+ if (cursorPosition < currentValue.length) setCursorPosition(cursorPosition + 1);
27893
+ }
27894
+ };
27895
+ /**
27896
+ * Handle arrow up key (multiline)
27897
+ */
27898
+ const handleArrowUp = (event) => {
27899
+ event.preventDefault();
27900
+ };
27901
+ /**
27902
+ * Handle arrow down key (multiline)
27903
+ */
27904
+ const handleArrowDown = (event) => {
27905
+ event.preventDefault();
27906
+ };
27907
+ /**
27908
+ * Handle home key
27909
+ */
27910
+ const handleHome = (event) => {
27911
+ event.preventDefault();
27912
+ setSelectionAnchor(-1);
27913
+ setCursorPosition(0);
27914
+ };
27915
+ /**
27916
+ * Handle end key
27917
+ */
27918
+ const handleEnd = (event) => {
27919
+ event.preventDefault();
27920
+ const currentValue = refCurrentValue.current;
27921
+ setSelectionAnchor(-1);
27922
+ setCursorPosition(currentValue.length);
27923
+ };
27924
+ /**
27925
+ * Handle select all (Ctrl+A / Cmd+A)
27926
+ */
27927
+ const handleSelectAll = (event) => {
27928
+ event.preventDefault();
27929
+ const currentValue = refCurrentValue.current;
27930
+ setSelectionAnchor(0);
27931
+ setCursorPosition(currentValue.length);
27932
+ };
27933
+ /**
27934
+ * Update value (controlled or uncontrolled)
27935
+ */
27936
+ const updateValue = (newValue) => {
27937
+ if (isControlled) props.onChange?.(newValue);
27938
+ else {
27939
+ setInternalValue(newValue);
27940
+ props.onChange?.(newValue);
27941
+ }
27942
+ };
27943
+ /**
27944
+ * Handle cursor position change from CharText click
27945
+ */
27946
+ const handleCursorPositionChange = (position) => {
27947
+ setCursorPosition(position);
27948
+ setSelectionAnchor(-1);
27949
+ };
27950
+ /**
27951
+ * Handle selection change from CharText drag
27952
+ */
27953
+ const handleSelectionChange = (start, end) => {
27954
+ if (start >= 0 && end > start) {
27955
+ setSelectionAnchor(start);
27956
+ setCursorPosition(end);
27957
+ } else setSelectionAnchor(-1);
27958
+ };
27959
+ const { value: _value, placeholder: _placeholder, onChange: _onChange, onFocus: _onFocus, onBlur: _onBlur, onSubmit: _onSubmit, ...viewProps } = props;
27960
+ const displayText = currentValue || (props.placeholder && !isFocused ? props.placeholder : "");
27961
+ return /* @__PURE__ */ require_jsx_runtime.jsx(CharText, {
27962
+ forwardRef: (r) => containerRef.current = r,
26992
27963
  ...viewProps,
26993
- children: limitedRows.map((row) => /* @__PURE__ */ require_jsx_runtime.jsxs(View, {
26994
- direction: "row",
26995
- gap: 8,
26996
- ...innerProps,
26997
- children: [/* @__PURE__ */ require_jsx_runtime.jsx(Text, {
26998
- text: `${row.label}:`,
26999
- style: rowStyle
27000
- }), /* @__PURE__ */ require_jsx_runtime.jsx(Text, {
27001
- text: row.value,
27002
- style: valueStyle
27003
- })]
27004
- }, `debug-${row.metric}`))
27964
+ text: displayText,
27965
+ showCursor: isFocused,
27966
+ cursorPosition,
27967
+ selectionStart,
27968
+ selectionEnd,
27969
+ onCursorPositionChange: handleCursorPositionChange,
27970
+ onSelectionChange: handleSelectionChange,
27971
+ onApiReady: (api) => charTextApiRef.current = api
27005
27972
  });
27006
27973
  }
27007
27974
  //#endregion
@@ -28366,7 +29333,7 @@ function Joystick(props) {
28366
29333
  y: 0
28367
29334
  });
28368
29335
  useLayoutEffect(() => {
28369
- const size = getLayoutSize(outerRef.current);
29336
+ const size = getLayoutSize$1(outerRef.current);
28370
29337
  if (size != null) {
28371
29338
  const newCenter = {
28372
29339
  x: size.width / 2,
@@ -29635,6 +30602,12 @@ Object.defineProperty(exports, "AlertDialog", {
29635
30602
  return AlertDialog;
29636
30603
  }
29637
30604
  });
30605
+ Object.defineProperty(exports, "Badge", {
30606
+ enumerable: true,
30607
+ get: function() {
30608
+ return Badge;
30609
+ }
30610
+ });
29638
30611
  Object.defineProperty(exports, "Button", {
29639
30612
  enumerable: true,
29640
30613
  get: function() {
@@ -29653,6 +30626,18 @@ Object.defineProperty(exports, "CharTextInput", {
29653
30626
  return CharTextInput;
29654
30627
  }
29655
30628
  });
30629
+ Object.defineProperty(exports, "Checkbox", {
30630
+ enumerable: true,
30631
+ get: function() {
30632
+ return Checkbox;
30633
+ }
30634
+ });
30635
+ Object.defineProperty(exports, "ContextMenu", {
30636
+ enumerable: true,
30637
+ get: function() {
30638
+ return ContextMenu;
30639
+ }
30640
+ });
29656
30641
  Object.defineProperty(exports, "DEFAULT_EFFECT", {
29657
30642
  enumerable: true,
29658
30643
  get: function() {
@@ -29785,12 +30770,24 @@ Object.defineProperty(exports, "Particles", {
29785
30770
  return Particles;
29786
30771
  }
29787
30772
  });
30773
+ Object.defineProperty(exports, "Popover", {
30774
+ enumerable: true,
30775
+ get: function() {
30776
+ return Popover;
30777
+ }
30778
+ });
29788
30779
  Object.defineProperty(exports, "Portal", {
29789
30780
  enumerable: true,
29790
30781
  get: function() {
29791
30782
  return Portal;
29792
30783
  }
29793
30784
  });
30785
+ Object.defineProperty(exports, "ProgressBar", {
30786
+ enumerable: true,
30787
+ get: function() {
30788
+ return ProgressBar;
30789
+ }
30790
+ });
29794
30791
  Object.defineProperty(exports, "RadioButton", {
29795
30792
  enumerable: true,
29796
30793
  get: function() {
@@ -29875,6 +30872,12 @@ Object.defineProperty(exports, "Tabs", {
29875
30872
  return Tabs;
29876
30873
  }
29877
30874
  });
30875
+ Object.defineProperty(exports, "Tag", {
30876
+ enumerable: true,
30877
+ get: function() {
30878
+ return Tag;
30879
+ }
30880
+ });
29878
30881
  Object.defineProperty(exports, "Text", {
29879
30882
  enumerable: true,
29880
30883
  get: function() {
@@ -29977,12 +30980,24 @@ Object.defineProperty(exports, "buildEmitZoneFromLayout", {
29977
30980
  return buildEmitZoneFromLayout;
29978
30981
  }
29979
30982
  });
30983
+ Object.defineProperty(exports, "calculateOverlayPosition", {
30984
+ enumerable: true,
30985
+ get: function() {
30986
+ return calculateOverlayPosition;
30987
+ }
30988
+ });
29980
30989
  Object.defineProperty(exports, "calculateSliderSize", {
29981
30990
  enumerable: true,
29982
30991
  get: function() {
29983
30992
  return calculateSliderSize;
29984
30993
  }
29985
30994
  });
30995
+ Object.defineProperty(exports, "clampProgressValue", {
30996
+ enumerable: true,
30997
+ get: function() {
30998
+ return clampProgressValue;
30999
+ }
31000
+ });
29986
31001
  Object.defineProperty(exports, "createBounceEffect", {
29987
31002
  enumerable: true,
29988
31003
  get: function() {
@@ -30205,6 +31220,12 @@ Object.defineProperty(exports, "forestGreenPreset", {
30205
31220
  return forestGreenPreset;
30206
31221
  }
30207
31222
  });
31223
+ Object.defineProperty(exports, "formatBadgeCount", {
31224
+ enumerable: true,
31225
+ get: function() {
31226
+ return formatBadgeCount;
31227
+ }
31228
+ });
30208
31229
  Object.defineProperty(exports, "generateColorScale", {
30209
31230
  enumerable: true,
30210
31231
  get: function() {
@@ -30217,6 +31238,18 @@ Object.defineProperty(exports, "getBackgroundGraphics", {
30217
31238
  return getBackgroundGraphics;
30218
31239
  }
30219
31240
  });
31241
+ Object.defineProperty(exports, "getBadgeSizeConfig", {
31242
+ enumerable: true,
31243
+ get: function() {
31244
+ return getBadgeSizeConfig;
31245
+ }
31246
+ });
31247
+ Object.defineProperty(exports, "getBadgeText", {
31248
+ enumerable: true,
31249
+ get: function() {
31250
+ return getBadgeText;
31251
+ }
31252
+ });
30220
31253
  Object.defineProperty(exports, "getContrastRatio", {
30221
31254
  enumerable: true,
30222
31255
  get: function() {
@@ -30250,7 +31283,7 @@ Object.defineProperty(exports, "getLayoutRect", {
30250
31283
  Object.defineProperty(exports, "getLayoutSize", {
30251
31284
  enumerable: true,
30252
31285
  get: function() {
30253
- return getLayoutSize;
31286
+ return getLayoutSize$1;
30254
31287
  }
30255
31288
  });
30256
31289
  Object.defineProperty(exports, "getMountStats", {
@@ -30271,6 +31304,12 @@ Object.defineProperty(exports, "getPresetWithMode", {
30271
31304
  return getPresetWithMode;
30272
31305
  }
30273
31306
  });
31307
+ Object.defineProperty(exports, "getProgressRatio", {
31308
+ enumerable: true,
31309
+ get: function() {
31310
+ return getProgressRatio;
31311
+ }
31312
+ });
30274
31313
  Object.defineProperty(exports, "getRenderContext", {
30275
31314
  enumerable: true,
30276
31315
  get: function() {
@@ -30505,6 +31544,12 @@ Object.defineProperty(exports, "remountAll", {
30505
31544
  return remountAll;
30506
31545
  }
30507
31546
  });
31547
+ Object.defineProperty(exports, "resolveBadgeTextStyle", {
31548
+ enumerable: true,
31549
+ get: function() {
31550
+ return resolveBadgeTextStyle;
31551
+ }
31552
+ });
30508
31553
  Object.defineProperty(exports, "resolveEffect", {
30509
31554
  enumerable: true,
30510
31555
  get: function() {
@@ -30764,4 +31809,4 @@ Object.defineProperty(exports, "withHooks", {
30764
31809
  }
30765
31810
  });
30766
31811
 
30767
- //# sourceMappingURL=custom-DTd4LxDn.cjs.map
31812
+ //# sourceMappingURL=custom-37gL0VZG.cjs.map