@almadar/ui 5.87.0 → 5.89.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.
@@ -0,0 +1,38 @@
1
+ /**
2
+ * StateGraph (molecule)
3
+ *
4
+ * Dumb visual renderer for a state-machine graph: lays states out on a circle,
5
+ * draws the player's transitions as SVG arrows, and emits a node-click event.
6
+ * All state (which transitions exist, the pending "from" selection, test results)
7
+ * lives in the .lolo FSM — this molecule only renders and reports clicks.
8
+ */
9
+ import * as React from 'react';
10
+ import type { EventKey } from '@almadar/core';
11
+ export interface StateGraphTransition {
12
+ from: string;
13
+ to: string;
14
+ event: string;
15
+ guardHint?: string;
16
+ }
17
+ export interface StateGraphProps {
18
+ /** All states in the machine (node labels). */
19
+ states: string[];
20
+ /** Player-built transitions rendered as arrows. */
21
+ transitions?: StateGraphTransition[];
22
+ /** State highlighted as current (test playback / initial). */
23
+ currentState?: string;
24
+ /** State the player has selected (first click). */
25
+ selectedState?: string;
26
+ /** When set, the graph is in "pick a target" mode from this state. */
27
+ addingFrom?: string;
28
+ /** The machine's initial state (ring-marked). */
29
+ initialState?: string;
30
+ /** Graph canvas width. */
31
+ width?: number;
32
+ /** Graph canvas height. */
33
+ height?: number;
34
+ /** Emits UI:{nodeClickEvent} with { stateId } when a node is clicked. */
35
+ nodeClickEvent?: EventKey;
36
+ className?: string;
37
+ }
38
+ export declare function StateGraph({ states, transitions, currentState, selectedState, addingFrom, initialState, width, height, nodeClickEvent, className, }: StateGraphProps): React.JSX.Element;
@@ -32,6 +32,7 @@ export { InventoryGrid, type InventoryGridProps, type InventoryGridItem } from '
32
32
  export { ResourceBar, type ResourceBarProps, type ResourceBarResource } from './ResourceBar';
33
33
  export { GameHud, type GameHudProps, type GameHudStat, type GameHudElement } from './GameHud';
34
34
  export { GameMenu, type GameMenuProps, type MenuOption } from './GameMenu';
35
+ export { StateGraph, type StateGraphProps, type StateGraphTransition } from './StateGraph';
35
36
  export { Canvas2D, type Canvas2DProps, type Projection, type CameraMode, type Platform, type SidePlayer, type TileCoord, } from './Canvas2D';
36
37
  export { useUnitSpriteAtlas } from '../../shared/hooks/useUnitSpriteAtlas';
37
38
  export { GameAudioProvider, GameAudioContext, useGameAudioContext, type GameAudioProviderProps, type GameAudioContextValue, } from '../../shared/providers/GameAudioProvider';
@@ -27838,6 +27838,214 @@ var init_GameMenu = __esm({
27838
27838
  GameMenu.displayName = "GameMenu";
27839
27839
  }
27840
27840
  });
27841
+ function StateNode2({
27842
+ name,
27843
+ isCurrent = false,
27844
+ isSelected = false,
27845
+ isInitial = false,
27846
+ position,
27847
+ onClick,
27848
+ className
27849
+ }) {
27850
+ return /* @__PURE__ */ jsxRuntime.jsx(
27851
+ exports.Box,
27852
+ {
27853
+ position: "absolute",
27854
+ display: "flex",
27855
+ className: cn(
27856
+ "items-center justify-center rounded-pill border-3 transition-all cursor-pointer select-none",
27857
+ "min-w-[80px] h-[80px] px-3",
27858
+ isCurrent && "bg-primary/20 border-primary shadow-lg shadow-primary/30 scale-110",
27859
+ isSelected && !isCurrent && "bg-accent/20 border-accent ring-2 ring-accent/50",
27860
+ !isCurrent && !isSelected && "bg-card border-border hover:border-muted-foreground hover:scale-105",
27861
+ className
27862
+ ),
27863
+ style: {
27864
+ left: position.x,
27865
+ top: position.y,
27866
+ transform: "translate(-50%, -50%)"
27867
+ },
27868
+ onClick,
27869
+ children: /* @__PURE__ */ jsxRuntime.jsxs(exports.Box, { className: "text-center", children: [
27870
+ isInitial && /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "caption", className: "text-muted-foreground text-xs block", children: "\u25B6 start" }),
27871
+ /* @__PURE__ */ jsxRuntime.jsx(
27872
+ exports.Typography,
27873
+ {
27874
+ variant: "body2",
27875
+ className: cn(
27876
+ "font-bold whitespace-nowrap",
27877
+ isCurrent ? "text-primary" : "text-foreground"
27878
+ ),
27879
+ children: name
27880
+ }
27881
+ )
27882
+ ] })
27883
+ }
27884
+ );
27885
+ }
27886
+ var init_StateNode = __esm({
27887
+ "components/game/2d/organisms/StateNode.tsx"() {
27888
+ init_atoms();
27889
+ init_cn();
27890
+ StateNode2.displayName = "StateNode";
27891
+ }
27892
+ });
27893
+ function TransitionArrow({
27894
+ from,
27895
+ to,
27896
+ eventLabel,
27897
+ guardHint,
27898
+ isActive = false,
27899
+ onClick,
27900
+ className
27901
+ }) {
27902
+ const dx = to.x - from.x;
27903
+ const dy = to.y - from.y;
27904
+ const dist = Math.sqrt(dx * dx + dy * dy);
27905
+ if (dist === 0) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, {});
27906
+ const nx = dx / dist;
27907
+ const ny = dy / dist;
27908
+ const startX = from.x + nx * NODE_RADIUS;
27909
+ const startY = from.y + ny * NODE_RADIUS;
27910
+ const endX = to.x - nx * NODE_RADIUS;
27911
+ const endY = to.y - ny * NODE_RADIUS;
27912
+ const midX = (startX + endX) / 2;
27913
+ const midY = (startY + endY) / 2;
27914
+ const perpX = -ny * 20;
27915
+ const perpY = nx * 20;
27916
+ const ctrlX = midX + perpX;
27917
+ const ctrlY = midY + perpY;
27918
+ const path = `M ${startX} ${startY} Q ${ctrlX} ${ctrlY} ${endX} ${endY}`;
27919
+ return /* @__PURE__ */ jsxRuntime.jsxs("g", { className: cn("cursor-pointer", className), onClick, children: [
27920
+ /* @__PURE__ */ jsxRuntime.jsx(
27921
+ "path",
27922
+ {
27923
+ d: path,
27924
+ fill: "none",
27925
+ stroke: isActive ? "var(--color-primary)" : "var(--color-border)",
27926
+ strokeWidth: isActive ? 3 : 2,
27927
+ markerEnd: isActive ? "url(#arrowhead-active)" : "url(#arrowhead)"
27928
+ }
27929
+ ),
27930
+ /* @__PURE__ */ jsxRuntime.jsx(
27931
+ "text",
27932
+ {
27933
+ x: ctrlX,
27934
+ y: ctrlY - 8,
27935
+ textAnchor: "middle",
27936
+ fill: isActive ? "var(--color-primary)" : "var(--color-foreground)",
27937
+ fontSize: 12,
27938
+ fontWeight: isActive ? "bold" : "normal",
27939
+ className: "select-none",
27940
+ children: eventLabel
27941
+ }
27942
+ ),
27943
+ guardHint && /* @__PURE__ */ jsxRuntime.jsx(
27944
+ "text",
27945
+ {
27946
+ x: ctrlX,
27947
+ y: ctrlY + 6,
27948
+ textAnchor: "middle",
27949
+ fill: "var(--color-warning)",
27950
+ fontSize: 10,
27951
+ className: "select-none",
27952
+ children: "\u26A0 " + guardHint
27953
+ }
27954
+ )
27955
+ ] });
27956
+ }
27957
+ var NODE_RADIUS;
27958
+ var init_TransitionArrow = __esm({
27959
+ "components/game/2d/organisms/TransitionArrow.tsx"() {
27960
+ init_cn();
27961
+ NODE_RADIUS = 40;
27962
+ TransitionArrow.displayName = "TransitionArrow";
27963
+ }
27964
+ });
27965
+ function layoutStates(states, width, height) {
27966
+ const cx = width / 2;
27967
+ const cy = height / 2;
27968
+ const radius = Math.min(cx, cy) - 60;
27969
+ const positions = {};
27970
+ states.forEach((state, i) => {
27971
+ const angle = 2 * Math.PI * i / Math.max(states.length, 1) - Math.PI / 2;
27972
+ positions[state] = { x: cx + radius * Math.cos(angle), y: cy + radius * Math.sin(angle) };
27973
+ });
27974
+ return positions;
27975
+ }
27976
+ function StateGraph({
27977
+ states,
27978
+ transitions = [],
27979
+ currentState,
27980
+ selectedState,
27981
+ addingFrom,
27982
+ initialState,
27983
+ width = 500,
27984
+ height = 400,
27985
+ nodeClickEvent,
27986
+ className
27987
+ }) {
27988
+ const eventBus = useEventBus();
27989
+ const nodes = states ?? [];
27990
+ const positions = React74__namespace.useMemo(() => layoutStates(nodes, width, height), [nodes, width, height]);
27991
+ return /* @__PURE__ */ jsxRuntime.jsxs(
27992
+ exports.Box,
27993
+ {
27994
+ position: "relative",
27995
+ className: cn("rounded-container border border-border bg-background overflow-hidden", className),
27996
+ style: { width, height },
27997
+ children: [
27998
+ /* @__PURE__ */ jsxRuntime.jsxs("svg", { width, height, className: "absolute inset-0", style: { pointerEvents: "none" }, children: [
27999
+ /* @__PURE__ */ jsxRuntime.jsxs("defs", { children: [
28000
+ /* @__PURE__ */ jsxRuntime.jsx("marker", { id: "arrowhead", markerWidth: "10", markerHeight: "7", refX: "10", refY: "3.5", orient: "auto", children: /* @__PURE__ */ jsxRuntime.jsx("polygon", { points: "0 0, 10 3.5, 0 7", fill: "var(--color-border)" }) }),
28001
+ /* @__PURE__ */ jsxRuntime.jsx("marker", { id: "arrowhead-active", markerWidth: "10", markerHeight: "7", refX: "10", refY: "3.5", orient: "auto", children: /* @__PURE__ */ jsxRuntime.jsx("polygon", { points: "0 0, 10 3.5, 0 7", fill: "var(--color-primary)" }) })
28002
+ ] }),
28003
+ transitions.map((tr, i) => {
28004
+ const fromPos = positions[tr.from];
28005
+ const toPos = positions[tr.to];
28006
+ if (!fromPos || !toPos) return null;
28007
+ return /* @__PURE__ */ jsxRuntime.jsx(
28008
+ TransitionArrow,
28009
+ {
28010
+ from: fromPos,
28011
+ to: toPos,
28012
+ eventLabel: tr.event,
28013
+ guardHint: tr.guardHint,
28014
+ isActive: tr.from === currentState
28015
+ },
28016
+ `${tr.from}-${tr.event}-${tr.to}-${i}`
28017
+ );
28018
+ })
28019
+ ] }),
28020
+ nodes.map((state) => {
28021
+ const pos = positions[state];
28022
+ if (!pos) return null;
28023
+ return /* @__PURE__ */ jsxRuntime.jsx(
28024
+ StateNode2,
28025
+ {
28026
+ name: state,
28027
+ position: pos,
28028
+ isCurrent: state === currentState,
28029
+ isSelected: state === selectedState || state === addingFrom,
28030
+ isInitial: state === initialState,
28031
+ onClick: nodeClickEvent ? () => eventBus.emit(`UI:${nodeClickEvent}`, { stateId: state }) : void 0
28032
+ },
28033
+ state
28034
+ );
28035
+ })
28036
+ ]
28037
+ }
28038
+ );
28039
+ }
28040
+ var init_StateGraph = __esm({
28041
+ "components/game/2d/molecules/StateGraph.tsx"() {
28042
+ init_atoms();
28043
+ init_cn();
28044
+ init_useEventBus();
28045
+ init_StateNode();
28046
+ init_TransitionArrow();
28047
+ }
28048
+ });
27841
28049
  function pickPath(entry) {
27842
28050
  if (Array.isArray(entry.path)) {
27843
28051
  return entry.path[Math.floor(Math.random() * entry.path.length)];
@@ -29582,130 +29790,6 @@ var init_EventHandlerBoard = __esm({
29582
29790
  EventHandlerBoard.displayName = "EventHandlerBoard";
29583
29791
  }
29584
29792
  });
29585
- function StateNode2({
29586
- name,
29587
- isCurrent = false,
29588
- isSelected = false,
29589
- isInitial = false,
29590
- position,
29591
- onClick,
29592
- className
29593
- }) {
29594
- return /* @__PURE__ */ jsxRuntime.jsx(
29595
- exports.Box,
29596
- {
29597
- position: "absolute",
29598
- display: "flex",
29599
- className: cn(
29600
- "items-center justify-center rounded-pill border-3 transition-all cursor-pointer select-none",
29601
- "min-w-[80px] h-[80px] px-3",
29602
- isCurrent && "bg-primary/20 border-primary shadow-lg shadow-primary/30 scale-110",
29603
- isSelected && !isCurrent && "bg-accent/20 border-accent ring-2 ring-accent/50",
29604
- !isCurrent && !isSelected && "bg-card border-border hover:border-muted-foreground hover:scale-105",
29605
- className
29606
- ),
29607
- style: {
29608
- left: position.x,
29609
- top: position.y,
29610
- transform: "translate(-50%, -50%)"
29611
- },
29612
- onClick,
29613
- children: /* @__PURE__ */ jsxRuntime.jsxs(exports.Box, { className: "text-center", children: [
29614
- isInitial && /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "caption", className: "text-muted-foreground text-xs block", children: "\u25B6 start" }),
29615
- /* @__PURE__ */ jsxRuntime.jsx(
29616
- exports.Typography,
29617
- {
29618
- variant: "body2",
29619
- className: cn(
29620
- "font-bold whitespace-nowrap",
29621
- isCurrent ? "text-primary" : "text-foreground"
29622
- ),
29623
- children: name
29624
- }
29625
- )
29626
- ] })
29627
- }
29628
- );
29629
- }
29630
- var init_StateNode = __esm({
29631
- "components/game/2d/organisms/StateNode.tsx"() {
29632
- init_atoms();
29633
- init_cn();
29634
- StateNode2.displayName = "StateNode";
29635
- }
29636
- });
29637
- function TransitionArrow({
29638
- from,
29639
- to,
29640
- eventLabel,
29641
- guardHint,
29642
- isActive = false,
29643
- onClick,
29644
- className
29645
- }) {
29646
- const dx = to.x - from.x;
29647
- const dy = to.y - from.y;
29648
- const dist = Math.sqrt(dx * dx + dy * dy);
29649
- if (dist === 0) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, {});
29650
- const nx = dx / dist;
29651
- const ny = dy / dist;
29652
- const startX = from.x + nx * NODE_RADIUS;
29653
- const startY = from.y + ny * NODE_RADIUS;
29654
- const endX = to.x - nx * NODE_RADIUS;
29655
- const endY = to.y - ny * NODE_RADIUS;
29656
- const midX = (startX + endX) / 2;
29657
- const midY = (startY + endY) / 2;
29658
- const perpX = -ny * 20;
29659
- const perpY = nx * 20;
29660
- const ctrlX = midX + perpX;
29661
- const ctrlY = midY + perpY;
29662
- const path = `M ${startX} ${startY} Q ${ctrlX} ${ctrlY} ${endX} ${endY}`;
29663
- return /* @__PURE__ */ jsxRuntime.jsxs("g", { className: cn("cursor-pointer", className), onClick, children: [
29664
- /* @__PURE__ */ jsxRuntime.jsx(
29665
- "path",
29666
- {
29667
- d: path,
29668
- fill: "none",
29669
- stroke: isActive ? "var(--color-primary)" : "var(--color-border)",
29670
- strokeWidth: isActive ? 3 : 2,
29671
- markerEnd: isActive ? "url(#arrowhead-active)" : "url(#arrowhead)"
29672
- }
29673
- ),
29674
- /* @__PURE__ */ jsxRuntime.jsx(
29675
- "text",
29676
- {
29677
- x: ctrlX,
29678
- y: ctrlY - 8,
29679
- textAnchor: "middle",
29680
- fill: isActive ? "var(--color-primary)" : "var(--color-foreground)",
29681
- fontSize: 12,
29682
- fontWeight: isActive ? "bold" : "normal",
29683
- className: "select-none",
29684
- children: eventLabel
29685
- }
29686
- ),
29687
- guardHint && /* @__PURE__ */ jsxRuntime.jsx(
29688
- "text",
29689
- {
29690
- x: ctrlX,
29691
- y: ctrlY + 6,
29692
- textAnchor: "middle",
29693
- fill: "var(--color-warning)",
29694
- fontSize: 10,
29695
- className: "select-none",
29696
- children: "\u26A0 " + guardHint
29697
- }
29698
- )
29699
- ] });
29700
- }
29701
- var NODE_RADIUS;
29702
- var init_TransitionArrow = __esm({
29703
- "components/game/2d/organisms/TransitionArrow.tsx"() {
29704
- init_cn();
29705
- NODE_RADIUS = 40;
29706
- TransitionArrow.displayName = "TransitionArrow";
29707
- }
29708
- });
29709
29793
  function VariablePanel({
29710
29794
  entityName,
29711
29795
  variables,
@@ -29791,7 +29875,7 @@ var init_StateJsonView = __esm({
29791
29875
  StateJsonView.displayName = "StateJsonView";
29792
29876
  }
29793
29877
  });
29794
- function layoutStates(states, width, height) {
29878
+ function layoutStates2(states, width, height) {
29795
29879
  const cx = width / 2;
29796
29880
  const cy = height / 2;
29797
29881
  const radius = Math.min(cx, cy) - 60;
@@ -29859,7 +29943,7 @@ function StateArchitectBoard({
29859
29943
  }, []);
29860
29944
  const GRAPH_W = 500;
29861
29945
  const GRAPH_H = 400;
29862
- const positions = React74.useMemo(() => layoutStates(entityStates, GRAPH_W, GRAPH_H), [entityStates]);
29946
+ const positions = React74.useMemo(() => layoutStates2(entityStates, GRAPH_W, GRAPH_H), [entityStates]);
29863
29947
  const handleStateClick = React74.useCallback((state) => {
29864
29948
  if (isTesting) return;
29865
29949
  if (addingFrom) {
@@ -31305,6 +31389,7 @@ var init_molecules = __esm({
31305
31389
  init_ResourceBar();
31306
31390
  init_GameHud();
31307
31391
  init_GameMenu();
31392
+ init_StateGraph();
31308
31393
  init_Canvas2D();
31309
31394
  init_useUnitSpriteAtlas();
31310
31395
  init_GameAudioProvider();
@@ -48007,6 +48092,7 @@ var init_component_registry_generated = __esm({
48007
48092
  init_StatCard();
48008
48093
  init_StatDisplay();
48009
48094
  init_StateArchitectBoard();
48095
+ init_StateGraph();
48010
48096
  init_StateIndicator();
48011
48097
  init_StateMachineView();
48012
48098
  init_StatsGrid();
@@ -48318,6 +48404,7 @@ var init_component_registry_generated = __esm({
48318
48404
  "StatCard": exports.StatCard,
48319
48405
  "StatDisplay": exports.StatDisplay,
48320
48406
  "StateArchitectBoard": StateArchitectBoard,
48407
+ "StateGraph": StateGraph,
48321
48408
  "StateIndicator": StateIndicator,
48322
48409
  "StateMachineView": exports.StateMachineView,
48323
48410
  "StatsGrid": exports.StatsGrid,
@@ -51675,6 +51762,7 @@ exports.SlotContentRenderer = SlotContentRenderer;
51675
51762
  exports.Sprite = Sprite;
51676
51763
  exports.StatBadge = StatBadge;
51677
51764
  exports.StateArchitectBoard = StateArchitectBoard;
51765
+ exports.StateGraph = StateGraph;
51678
51766
  exports.StateIndicator = StateIndicator;
51679
51767
  exports.StateJsonView = StateJsonView;
51680
51768
  exports.StateNode = StateNode2;