@almadar/ui 5.28.5 → 5.30.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 (55) hide show
  1. package/dist/avl/index.cjs +178 -113
  2. package/dist/avl/index.js +178 -113
  3. package/dist/components/core/molecules/DocumentViewer.d.ts +0 -2
  4. package/dist/components/core/molecules/Header.d.ts +0 -4
  5. package/dist/components/core/molecules/Navigation.d.ts +0 -2
  6. package/dist/components/core/molecules/PageHeader.d.ts +0 -2
  7. package/dist/components/core/molecules/PropertyInspector.d.ts +8 -1
  8. package/dist/components/core/organisms/index.d.ts +1 -1
  9. package/dist/components/core/templates/index.d.ts +3 -0
  10. package/dist/components/game/{organisms → molecules}/GameCanvas3D.d.ts +1 -3
  11. package/dist/components/game/molecules/index.d.ts +1 -0
  12. package/dist/components/game/{organisms → molecules}/three/hooks/useGameCanvas3DEvents.d.ts +1 -1
  13. package/dist/components/game/{organisms → molecules}/three/index.cjs +29 -4
  14. package/dist/components/game/{organisms → molecules}/three/index.css +3 -3
  15. package/dist/components/game/{organisms → molecules}/three/index.js +29 -4
  16. package/dist/components/game/{organisms → molecules}/three/renderers/FeatureRenderer.d.ts +1 -1
  17. package/dist/components/game/{organisms → molecules}/three/renderers/FeatureRenderer3D.d.ts +1 -1
  18. package/dist/components/game/{organisms → molecules}/three/renderers/TileRenderer.d.ts +1 -1
  19. package/dist/components/game/{organisms → molecules}/three/renderers/UnitRenderer.d.ts +1 -1
  20. package/dist/components/game/organisms/TraitSlot.d.ts +3 -1
  21. package/dist/components/game/organisms/index.d.ts +0 -9
  22. package/dist/components/game/organisms/types/isometric.d.ts +2 -0
  23. package/dist/components/index.cjs +786 -692
  24. package/dist/components/index.js +788 -694
  25. package/dist/providers/index.cjs +178 -113
  26. package/dist/providers/index.js +178 -113
  27. package/dist/renderer/pattern-resolver.d.ts +2 -5
  28. package/dist/runtime/index.cjs +178 -113
  29. package/dist/runtime/index.js +178 -113
  30. package/package.json +9 -4
  31. package/dist/components/game/organisms/CombatLog.d.ts +0 -2
  32. package/dist/components/game/organisms/DialogueBox.d.ts +0 -2
  33. package/dist/components/game/organisms/GameHud.d.ts +0 -2
  34. package/dist/components/game/organisms/GameMenu.d.ts +0 -2
  35. package/dist/components/game/organisms/GameOverScreen.d.ts +0 -2
  36. package/dist/components/game/organisms/InventoryPanel.d.ts +0 -2
  37. package/dist/components/game/organisms/IsometricCanvas.d.ts +0 -3
  38. package/dist/components/game/organisms/PlatformerCanvas.d.ts +0 -2
  39. /package/dist/components/game/{organisms → molecules}/three/Camera3D.d.ts +0 -0
  40. /package/dist/components/game/{organisms → molecules}/three/Lighting3D.d.ts +0 -0
  41. /package/dist/components/game/{organisms → molecules}/three/Scene3D.d.ts +0 -0
  42. /package/dist/components/game/{organisms → molecules}/three/components/Canvas3DErrorBoundary.d.ts +0 -0
  43. /package/dist/components/game/{organisms → molecules}/three/components/Canvas3DLoadingState.d.ts +0 -0
  44. /package/dist/components/game/{organisms → molecules}/three/components/ModelLoader.d.ts +0 -0
  45. /package/dist/components/game/{organisms → molecules}/three/components/PhysicsObject3D.d.ts +0 -0
  46. /package/dist/components/game/{organisms → molecules}/three/components/index.d.ts +0 -0
  47. /package/dist/components/game/{organisms → molecules}/three/hooks/useAssetLoader.d.ts +0 -0
  48. /package/dist/components/game/{organisms → molecules}/three/hooks/useRaycaster.d.ts +0 -0
  49. /package/dist/components/game/{organisms → molecules}/three/hooks/useSceneGraph.d.ts +0 -0
  50. /package/dist/components/game/{organisms → molecules}/three/hooks/useThree.d.ts +0 -0
  51. /package/dist/components/game/{organisms → molecules}/three/index.d.ts +0 -0
  52. /package/dist/components/game/{organisms → molecules}/three/loaders/AssetLoader.d.ts +0 -0
  53. /package/dist/components/game/{organisms → molecules}/three/renderers/index.d.ts +0 -0
  54. /package/dist/components/game/{organisms → molecules}/three/utils/culling.d.ts +0 -0
  55. /package/dist/components/game/{organisms → molecules}/three/utils/grid3D.d.ts +0 -0
@@ -7129,11 +7129,15 @@ var init_Skeleton = __esm({
7129
7129
  function getKnownPatterns() {
7130
7130
  return Object.keys(componentMapping);
7131
7131
  }
7132
- var componentMapping;
7132
+ function getPatternDefinition(type) {
7133
+ return patternRegistry[type];
7134
+ }
7135
+ var componentMapping, patternRegistry;
7133
7136
  var init_pattern_resolver = __esm({
7134
7137
  "renderer/pattern-resolver.ts"() {
7135
7138
  logger.createLogger("almadar:ui:pattern-resolver");
7136
7139
  componentMapping = {};
7140
+ patternRegistry = {};
7137
7141
  }
7138
7142
  });
7139
7143
 
@@ -10046,13 +10050,6 @@ var init_IsometricCanvas = __esm({
10046
10050
  }
10047
10051
  });
10048
10052
 
10049
- // components/game/organisms/IsometricCanvas.tsx
10050
- var init_IsometricCanvas2 = __esm({
10051
- "components/game/organisms/IsometricCanvas.tsx"() {
10052
- init_IsometricCanvas();
10053
- }
10054
- });
10055
-
10056
10053
  // components/game/organisms/boardEntity.ts
10057
10054
  function boardEntity(entity) {
10058
10055
  if (!entity) return void 0;
@@ -10448,7 +10445,7 @@ var init_BattleBoard = __esm({
10448
10445
  init_Button();
10449
10446
  init_Typography();
10450
10447
  init_Stack();
10451
- init_IsometricCanvas2();
10448
+ init_IsometricCanvas();
10452
10449
  init_boardEntity();
10453
10450
  init_isometric();
10454
10451
  BattleBoard.displayName = "BattleBoard";
@@ -18363,7 +18360,7 @@ var init_CastleBoard = __esm({
18363
18360
  "use client";
18364
18361
  init_cn();
18365
18362
  init_useEventBus();
18366
- init_IsometricCanvas2();
18363
+ init_IsometricCanvas();
18367
18364
  init_boardEntity();
18368
18365
  init_isometric();
18369
18366
  CastleBoard.displayName = "CastleBoard";
@@ -20765,7 +20762,84 @@ var init_DashboardLayout = __esm({
20765
20762
  NavLinkBottom.displayName = "NavLinkBottom";
20766
20763
  }
20767
20764
  });
20768
- exports.Menu = void 0;
20765
+ function computeMenuStyle(position, triggerRect) {
20766
+ const isTop = position.startsWith("top");
20767
+ const isRight = position.endsWith("right") || position.endsWith("end");
20768
+ if (isTop) {
20769
+ return {
20770
+ top: triggerRect.top - MENU_GAP,
20771
+ transform: "translateY(-100%)",
20772
+ ...isRight ? { right: window.innerWidth - triggerRect.right } : { left: triggerRect.left }
20773
+ };
20774
+ }
20775
+ return {
20776
+ top: triggerRect.bottom + MENU_GAP,
20777
+ ...isRight ? { right: window.innerWidth - triggerRect.right } : { left: triggerRect.left }
20778
+ };
20779
+ }
20780
+ function SubMenu({
20781
+ items,
20782
+ itemRef,
20783
+ direction,
20784
+ eventBus
20785
+ }) {
20786
+ const [rect, setRect] = React74.useState(null);
20787
+ React74.useEffect(() => {
20788
+ if (itemRef) {
20789
+ setRect(itemRef.getBoundingClientRect());
20790
+ }
20791
+ }, [itemRef]);
20792
+ if (!rect) return null;
20793
+ const isRtl = direction === "rtl";
20794
+ const style = {
20795
+ top: rect.top,
20796
+ ...isRtl ? { right: window.innerWidth - rect.left } : { left: rect.right }
20797
+ };
20798
+ const panel = /* @__PURE__ */ jsxRuntime.jsx(
20799
+ "div",
20800
+ {
20801
+ className: cn("fixed z-50", menuContainerStyles),
20802
+ style,
20803
+ children: items.map((item, index) => {
20804
+ const isDivider = item.id === "divider" || item.label === "divider";
20805
+ const itemId = item.id ?? `item-${item.label.toLowerCase().replace(/\s+/g, "-")}-${index}`;
20806
+ const isDanger = item.variant === "danger";
20807
+ if (isDivider) {
20808
+ return /* @__PURE__ */ jsxRuntime.jsx(exports.Divider, { className: "my-1" }, `divider-${index}`);
20809
+ }
20810
+ return /* @__PURE__ */ jsxRuntime.jsxs(
20811
+ exports.Box,
20812
+ {
20813
+ as: "button",
20814
+ onClick: () => {
20815
+ if (item.disabled) return;
20816
+ if (item.event) eventBus.emit(`UI:${item.event}`, { itemId, label: item.label });
20817
+ item.onClick?.();
20818
+ },
20819
+ "aria-disabled": item.disabled || void 0,
20820
+ "data-testid": item.event ? `action-${item.event}` : void 0,
20821
+ className: cn(
20822
+ "w-full flex items-center gap-3 px-4 py-2 text-start",
20823
+ "text-sm transition-colors",
20824
+ "hover:bg-muted focus:outline-none focus:bg-muted",
20825
+ "disabled:opacity-50 disabled:cursor-not-allowed",
20826
+ item.disabled && "cursor-not-allowed",
20827
+ isDanger && "text-error hover:bg-error/10"
20828
+ ),
20829
+ children: [
20830
+ item.icon && (typeof item.icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: item.icon, size: "sm", className: "flex-shrink-0" }) : /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { icon: item.icon, size: "sm", className: "flex-shrink-0" })),
20831
+ /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "small", className: cn("flex-1", isDanger && "text-red-600"), children: item.label }),
20832
+ item.badge !== void 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-auto text-xs font-medium", children: item.badge })
20833
+ ]
20834
+ },
20835
+ itemId
20836
+ );
20837
+ })
20838
+ }
20839
+ );
20840
+ return typeof document !== "undefined" ? reactDom.createPortal(panel, document.body) : panel;
20841
+ }
20842
+ var MENU_GAP, menuContainerStyles; exports.Menu = void 0;
20769
20843
  var init_Menu = __esm({
20770
20844
  "components/core/molecules/Menu.tsx"() {
20771
20845
  "use client";
@@ -20776,6 +20850,14 @@ var init_Menu = __esm({
20776
20850
  init_Badge();
20777
20851
  init_cn();
20778
20852
  init_useEventBus();
20853
+ MENU_GAP = 4;
20854
+ menuContainerStyles = cn(
20855
+ "bg-card",
20856
+ "border-[length:var(--border-width)] border-border",
20857
+ "shadow-elevation-popover",
20858
+ "rounded-sm",
20859
+ "min-w-0 sm:min-w-[200px] max-w-[calc(100vw-1rem)] py-1"
20860
+ );
20779
20861
  exports.Menu = ({
20780
20862
  trigger,
20781
20863
  items,
@@ -20783,9 +20865,10 @@ var init_Menu = __esm({
20783
20865
  className
20784
20866
  }) => {
20785
20867
  const eventBus = useEventBus();
20786
- const { t, direction } = hooks.useTranslate();
20868
+ const { direction } = hooks.useTranslate();
20787
20869
  const [isOpen, setIsOpen] = React74.useState(false);
20788
20870
  const [activeSubMenu, setActiveSubMenu] = React74.useState(null);
20871
+ const [activeSubMenuRef, setActiveSubMenuRef] = React74.useState(null);
20789
20872
  const [triggerRect, setTriggerRect] = React74.useState(null);
20790
20873
  const triggerRef = React74.useRef(null);
20791
20874
  const menuRef = React74.useRef(null);
@@ -20800,13 +20883,14 @@ var init_Menu = __esm({
20800
20883
  }
20801
20884
  setIsOpen(!isOpen);
20802
20885
  setActiveSubMenu(null);
20886
+ setActiveSubMenuRef(null);
20803
20887
  };
20804
- const handleItemClick = (item) => {
20888
+ const handleItemClick = (item, itemId) => {
20805
20889
  if (item.disabled) return;
20806
20890
  if (item.subMenu && item.subMenu.length > 0) {
20807
- setActiveSubMenu(item.id ?? null);
20891
+ setActiveSubMenu(itemId);
20808
20892
  } else {
20809
- if (item.event) eventBus.emit(`UI:${item.event}`, { itemId: item.id, label: item.label });
20893
+ if (item.event) eventBus.emit(`UI:${item.event}`, { itemId, label: item.label });
20810
20894
  item.onClick?.();
20811
20895
  setIsOpen(false);
20812
20896
  }
@@ -20821,22 +20905,12 @@ var init_Menu = __esm({
20821
20905
  if (isOpen && menuRef.current && !menuRef.current.contains(e.target) && triggerRef.current && !triggerRef.current.contains(e.target)) {
20822
20906
  setIsOpen(false);
20823
20907
  setActiveSubMenu(null);
20908
+ setActiveSubMenuRef(null);
20824
20909
  }
20825
20910
  };
20826
20911
  document.addEventListener("mousedown", handleClickOutside);
20827
20912
  return () => document.removeEventListener("mousedown", handleClickOutside);
20828
20913
  }, [isOpen]);
20829
- const positionClasses = {
20830
- "top-left": "bottom-full left-0 mb-2",
20831
- "top-right": "bottom-full right-0 mb-2",
20832
- "bottom-left": "top-full left-0 mt-2",
20833
- "bottom-right": "top-full right-0 mt-2",
20834
- // Aliases for pattern compatibility
20835
- "top-start": "bottom-full left-0 mb-2",
20836
- "top-end": "bottom-full right-0 mb-2",
20837
- "bottom-start": "top-full left-0 mt-2",
20838
- "bottom-end": "top-full right-0 mt-2"
20839
- };
20840
20914
  const rtlMirror = {
20841
20915
  "top-left": "top-right",
20842
20916
  "top-right": "top-left",
@@ -20848,7 +20922,6 @@ var init_Menu = __esm({
20848
20922
  "bottom-end": "bottom-start"
20849
20923
  };
20850
20924
  const effectivePosition = direction === "rtl" ? rtlMirror[position] ?? position : position;
20851
- const subMenuSideClass = direction === "rtl" ? "right-full mr-2" : "left-full ml-2";
20852
20925
  const triggerChild = React74__namespace.default.isValidElement(trigger) ? trigger : /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "small", as: "span", children: trigger });
20853
20926
  const triggerElement = React74__namespace.default.cloneElement(
20854
20927
  triggerChild,
@@ -20857,94 +20930,83 @@ var init_Menu = __esm({
20857
20930
  onClick: handleToggle
20858
20931
  }
20859
20932
  );
20860
- const menuContainerStyles = cn(
20861
- "bg-card",
20862
- "border-[length:var(--border-width)] border-border",
20863
- "shadow-elevation-popover",
20864
- "rounded-sm",
20865
- "min-w-0 sm:min-w-[200px] max-w-[calc(100vw-1rem)] py-1"
20866
- );
20867
- const renderMenuItem = (item, hasSubMenu, index) => {
20933
+ const renderMenuItems = (menuItems) => menuItems.map((item, index) => {
20934
+ const isDivider = item.id === "divider" || item.label === "divider";
20868
20935
  const itemId = item.id ?? `item-${item.label.toLowerCase().replace(/\s+/g, "-")}-${index}`;
20936
+ const hasSubMenu = !!(item.subMenu && item.subMenu.length > 0);
20869
20937
  const isDanger = item.variant === "danger";
20870
- return /* @__PURE__ */ jsxRuntime.jsx(
20871
- exports.Box,
20872
- {
20873
- as: "button",
20874
- onClick: () => !item.disabled && handleItemClick({ ...item, id: itemId }),
20875
- "aria-disabled": item.disabled || void 0,
20876
- onMouseEnter: () => hasSubMenu && setActiveSubMenu(itemId),
20877
- "data-testid": item.event ? `action-${item.event}` : void 0,
20878
- className: cn(
20879
- "w-full flex items-center justify-between gap-3 px-4 py-2 text-start",
20880
- "text-sm transition-colors",
20881
- "hover:bg-muted",
20882
- "focus:outline-none focus:bg-muted",
20883
- "disabled:opacity-50 disabled:cursor-not-allowed",
20884
- item.disabled && "cursor-not-allowed",
20885
- isDanger && "text-error hover:bg-error/10"
20886
- ),
20887
- children: /* @__PURE__ */ jsxRuntime.jsxs(exports.Box, { className: "flex items-center gap-3 flex-1 min-w-0", children: [
20888
- item.icon && (typeof item.icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: item.icon, size: "sm", className: "flex-shrink-0" }) : /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { icon: item.icon, size: "sm", className: "flex-shrink-0" })),
20889
- /* @__PURE__ */ jsxRuntime.jsx(
20890
- exports.Typography,
20891
- {
20892
- variant: "small",
20893
- className: cn("flex-1", isDanger && "text-red-600"),
20894
- children: item.label
20938
+ if (isDivider) {
20939
+ return /* @__PURE__ */ jsxRuntime.jsx(exports.Divider, { className: "my-1" }, `divider-${index}`);
20940
+ }
20941
+ return /* @__PURE__ */ jsxRuntime.jsxs(exports.Box, { children: [
20942
+ /* @__PURE__ */ jsxRuntime.jsx(
20943
+ exports.Box,
20944
+ {
20945
+ as: "button",
20946
+ onClick: () => handleItemClick({ ...item, id: itemId }, itemId),
20947
+ "aria-disabled": item.disabled || void 0,
20948
+ onMouseEnter: (e) => {
20949
+ if (hasSubMenu) {
20950
+ setActiveSubMenu(itemId);
20951
+ setActiveSubMenuRef(e.currentTarget);
20895
20952
  }
20953
+ },
20954
+ "data-testid": item.event ? `action-${item.event}` : void 0,
20955
+ className: cn(
20956
+ "w-full flex items-center justify-between gap-3 px-4 py-2 text-start",
20957
+ "text-sm transition-colors",
20958
+ "hover:bg-muted",
20959
+ "focus:outline-none focus:bg-muted",
20960
+ "disabled:opacity-50 disabled:cursor-not-allowed",
20961
+ item.disabled && "cursor-not-allowed",
20962
+ isDanger && "text-error hover:bg-error/10"
20896
20963
  ),
20897
- item.badge !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(exports.Badge, { variant: "default", size: "sm", children: item.badge }),
20898
- hasSubMenu && /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: direction === "rtl" ? "chevron-left" : "chevron-right", size: "sm", className: "flex-shrink-0" })
20899
- ] })
20900
- },
20901
- itemId
20902
- );
20903
- };
20904
- const renderMenuItems = (menuItems) => {
20905
- return menuItems.map((item, index) => {
20906
- const hasSubMenu = item.subMenu && item.subMenu.length > 0;
20907
- const isDivider = item.id === "divider" || item.label === "divider";
20908
- const itemId = item.id ?? `item-${item.label.toLowerCase().replace(/\s+/g, "-")}-${index}`;
20909
- if (isDivider) {
20910
- return /* @__PURE__ */ jsxRuntime.jsx(exports.Divider, { className: "my-1" }, `divider-${index}`);
20911
- }
20912
- return /* @__PURE__ */ jsxRuntime.jsxs(exports.Box, { children: [
20913
- renderMenuItem(item, !!hasSubMenu, index),
20914
- hasSubMenu && activeSubMenu === itemId && item.subMenu && /* @__PURE__ */ jsxRuntime.jsx(
20915
- exports.Box,
20916
- {
20917
- className: cn(
20918
- "absolute top-0 z-50",
20919
- subMenuSideClass,
20920
- menuContainerStyles
20964
+ children: /* @__PURE__ */ jsxRuntime.jsxs(exports.Box, { className: "flex items-center gap-3 flex-1 min-w-0", children: [
20965
+ item.icon && (typeof item.icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: item.icon, size: "sm", className: "flex-shrink-0" }) : /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { icon: item.icon, size: "sm", className: "flex-shrink-0" })),
20966
+ /* @__PURE__ */ jsxRuntime.jsx(
20967
+ exports.Typography,
20968
+ {
20969
+ variant: "small",
20970
+ className: cn("flex-1", isDanger && "text-red-600"),
20971
+ children: item.label
20972
+ }
20921
20973
  ),
20922
- children: renderMenuItems(item.subMenu)
20923
- }
20924
- )
20925
- ] }, itemId);
20926
- });
20927
- };
20928
- return /* @__PURE__ */ jsxRuntime.jsxs(exports.Box, { className: "relative", children: [
20974
+ item.badge !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(exports.Badge, { variant: "default", size: "sm", children: item.badge }),
20975
+ hasSubMenu && /* @__PURE__ */ jsxRuntime.jsx(
20976
+ exports.Icon,
20977
+ {
20978
+ name: direction === "rtl" ? "chevron-left" : "chevron-right",
20979
+ size: "sm",
20980
+ className: "flex-shrink-0"
20981
+ }
20982
+ )
20983
+ ] })
20984
+ }
20985
+ ),
20986
+ hasSubMenu && activeSubMenu === itemId && item.subMenu && /* @__PURE__ */ jsxRuntime.jsx(
20987
+ SubMenu,
20988
+ {
20989
+ items: item.subMenu,
20990
+ itemRef: activeSubMenuRef,
20991
+ direction,
20992
+ eventBus
20993
+ }
20994
+ )
20995
+ ] }, itemId);
20996
+ });
20997
+ const panel = isOpen && triggerRect ? /* @__PURE__ */ jsxRuntime.jsx(
20998
+ "div",
20999
+ {
21000
+ ref: menuRef,
21001
+ className: cn("fixed z-50", menuContainerStyles, className),
21002
+ style: computeMenuStyle(effectivePosition, triggerRect),
21003
+ role: "menu",
21004
+ children: renderMenuItems(items)
21005
+ }
21006
+ ) : null;
21007
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
20929
21008
  triggerElement,
20930
- isOpen && triggerRect && /* @__PURE__ */ jsxRuntime.jsx(
20931
- exports.Box,
20932
- {
20933
- ref: menuRef,
20934
- className: cn(
20935
- "absolute z-50",
20936
- menuContainerStyles,
20937
- positionClasses[effectivePosition],
20938
- className
20939
- ),
20940
- style: {
20941
- left: effectivePosition.includes("left") ? 0 : "auto",
20942
- right: effectivePosition.includes("right") ? 0 : "auto"
20943
- },
20944
- role: "menu",
20945
- children: renderMenuItems(items)
20946
- }
20947
- )
21009
+ panel && typeof document !== "undefined" ? reactDom.createPortal(panel, document.body) : panel
20948
21010
  ] });
20949
21011
  };
20950
21012
  exports.Menu.displayName = "Menu";
@@ -22531,6 +22593,583 @@ var init_JsonTreeEditor = __esm({
22531
22593
  };
22532
22594
  }
22533
22595
  });
22596
+ exports.FormSection = void 0; exports.FormLayout = void 0; exports.FormActions = void 0;
22597
+ var init_FormSection = __esm({
22598
+ "components/core/molecules/FormSection.tsx"() {
22599
+ "use client";
22600
+ init_cn();
22601
+ init_atoms2();
22602
+ init_Box();
22603
+ init_Typography();
22604
+ init_Button();
22605
+ init_Stack();
22606
+ init_Icon();
22607
+ init_useEventBus();
22608
+ exports.FormSection = ({
22609
+ title,
22610
+ description,
22611
+ children,
22612
+ collapsible = false,
22613
+ defaultCollapsed = false,
22614
+ card = false,
22615
+ columns = 1,
22616
+ className
22617
+ }) => {
22618
+ const [collapsed, setCollapsed] = React74__namespace.default.useState(defaultCollapsed);
22619
+ const { t } = hooks.useTranslate();
22620
+ const eventBus = useEventBus();
22621
+ const gridClass = {
22622
+ 1: "grid-cols-1",
22623
+ 2: "grid-cols-1 md:grid-cols-2",
22624
+ 3: "grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
22625
+ }[columns];
22626
+ React74__namespace.default.useCallback(() => {
22627
+ if (collapsible) {
22628
+ setCollapsed((prev) => !prev);
22629
+ eventBus.emit("UI:TOGGLE_COLLAPSE", { collapsed: !collapsed });
22630
+ }
22631
+ }, [collapsible, collapsed, eventBus]);
22632
+ const content = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
22633
+ (title || description) && /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "xs", className: "mb-4", children: [
22634
+ title && /* @__PURE__ */ jsxRuntime.jsxs(
22635
+ exports.HStack,
22636
+ {
22637
+ justify: "between",
22638
+ align: "center",
22639
+ className: cn(collapsible && "cursor-pointer"),
22640
+ action: collapsible ? "TOGGLE_COLLAPSE" : void 0,
22641
+ children: [
22642
+ /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "h3", weight: "semibold", children: title }),
22643
+ collapsible && /* @__PURE__ */ jsxRuntime.jsx(
22644
+ exports.Button,
22645
+ {
22646
+ variant: "ghost",
22647
+ size: "sm",
22648
+ action: "TOGGLE_COLLAPSE",
22649
+ children: /* @__PURE__ */ jsxRuntime.jsx(
22650
+ exports.Icon,
22651
+ {
22652
+ icon: LucideIcons2.ChevronDown,
22653
+ size: "sm",
22654
+ className: cn(
22655
+ "text-muted-foreground transition-transform",
22656
+ collapsed && "rotate-180"
22657
+ )
22658
+ }
22659
+ )
22660
+ }
22661
+ )
22662
+ ]
22663
+ }
22664
+ ),
22665
+ description && /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "small", color: "secondary", children: description })
22666
+ ] }),
22667
+ (!collapsible || !collapsed) && /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: cn("grid gap-4", gridClass), children })
22668
+ ] });
22669
+ if (card) {
22670
+ return /* @__PURE__ */ jsxRuntime.jsx(exports.Card, { className: cn("p-6", className), children: content });
22671
+ }
22672
+ return /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className, children: content });
22673
+ };
22674
+ exports.FormSection.displayName = "FormSection";
22675
+ exports.FormLayout = ({
22676
+ children,
22677
+ dividers = true,
22678
+ className
22679
+ }) => {
22680
+ return /* @__PURE__ */ jsxRuntime.jsx(
22681
+ exports.VStack,
22682
+ {
22683
+ gap: "lg",
22684
+ className: cn(
22685
+ dividers && "[&>*+*]:pt-8 [&>*+*]:border-t [&>*+*]:border-border",
22686
+ className
22687
+ ),
22688
+ children
22689
+ }
22690
+ );
22691
+ };
22692
+ exports.FormLayout.displayName = "FormLayout";
22693
+ exports.FormActions = ({
22694
+ children,
22695
+ sticky = false,
22696
+ align = "right",
22697
+ className
22698
+ }) => {
22699
+ const alignClass2 = {
22700
+ left: "justify-start",
22701
+ right: "justify-end",
22702
+ between: "justify-between",
22703
+ center: "justify-center"
22704
+ }[align];
22705
+ return /* @__PURE__ */ jsxRuntime.jsx(
22706
+ exports.HStack,
22707
+ {
22708
+ gap: "sm",
22709
+ align: "center",
22710
+ className: cn(
22711
+ "pt-6 border-t border-border",
22712
+ alignClass2,
22713
+ sticky && "sticky bottom-0 bg-card py-4 -mx-6 px-6 shadow-[0_-4px_6px_-1px_rgb(0,0,0,0.05)]",
22714
+ className
22715
+ ),
22716
+ children
22717
+ }
22718
+ );
22719
+ };
22720
+ exports.FormActions.displayName = "FormActions";
22721
+ }
22722
+ });
22723
+ var ALL_CATEGORY; exports.GridPicker = void 0;
22724
+ var init_GridPicker = __esm({
22725
+ "components/core/molecules/GridPicker.tsx"() {
22726
+ "use client";
22727
+ init_cn();
22728
+ init_Input();
22729
+ init_Badge();
22730
+ init_Stack();
22731
+ ALL_CATEGORY = "__all__";
22732
+ exports.GridPicker = ({
22733
+ items,
22734
+ value,
22735
+ onChange,
22736
+ categories,
22737
+ searchPlaceholder,
22738
+ renderThumbnail,
22739
+ cellSize = 32,
22740
+ className
22741
+ }) => {
22742
+ const [search, setSearch] = React74.useState("");
22743
+ const [activeCategory, setActiveCategory] = React74.useState(ALL_CATEGORY);
22744
+ const gridRef = React74.useRef(null);
22745
+ const categoryChips = React74.useMemo(() => {
22746
+ if (categories !== void 0) return categories;
22747
+ const seen = [];
22748
+ for (const item of items) {
22749
+ if (!seen.includes(item.category)) seen.push(item.category);
22750
+ }
22751
+ return seen;
22752
+ }, [categories, items]);
22753
+ const filtered = React74.useMemo(() => {
22754
+ const needle = search.trim().toLowerCase();
22755
+ return items.filter((item) => {
22756
+ const matchesCategory = activeCategory === ALL_CATEGORY || item.category === activeCategory;
22757
+ const matchesSearch = needle === "" || item.label.toLowerCase().includes(needle);
22758
+ return matchesCategory && matchesSearch;
22759
+ });
22760
+ }, [items, search, activeCategory]);
22761
+ const select = React74.useCallback(
22762
+ (item) => {
22763
+ onChange(item.id);
22764
+ },
22765
+ [onChange]
22766
+ );
22767
+ const handleKeyDown = React74.useCallback(
22768
+ (e, index) => {
22769
+ const cells = gridRef.current?.querySelectorAll(
22770
+ "[data-gridpicker-cell]"
22771
+ );
22772
+ if (cells === void 0 || cells.length === 0) return;
22773
+ const columns = (() => {
22774
+ const grid = gridRef.current;
22775
+ if (grid === null) return 1;
22776
+ const style = window.getComputedStyle(grid);
22777
+ const cols = style.gridTemplateColumns.split(" ").filter(Boolean).length;
22778
+ return cols > 0 ? cols : 1;
22779
+ })();
22780
+ let next = -1;
22781
+ if (e.key === "ArrowRight") next = index + 1;
22782
+ else if (e.key === "ArrowLeft") next = index - 1;
22783
+ else if (e.key === "ArrowDown") next = index + columns;
22784
+ else if (e.key === "ArrowUp") next = index - columns;
22785
+ else if (e.key === "Enter" || e.key === " ") {
22786
+ e.preventDefault();
22787
+ select(filtered[index]);
22788
+ return;
22789
+ } else {
22790
+ return;
22791
+ }
22792
+ e.preventDefault();
22793
+ if (next >= 0 && next < cells.length) {
22794
+ cells[next].focus();
22795
+ }
22796
+ },
22797
+ [filtered, select]
22798
+ );
22799
+ return /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "sm", className: cn("w-full", className), children: [
22800
+ /* @__PURE__ */ jsxRuntime.jsx(
22801
+ exports.Input,
22802
+ {
22803
+ type: "search",
22804
+ icon: "search",
22805
+ value: search,
22806
+ placeholder: searchPlaceholder,
22807
+ clearable: true,
22808
+ onClear: () => setSearch(""),
22809
+ onChange: (e) => setSearch(e.target.value)
22810
+ }
22811
+ ),
22812
+ categoryChips.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(exports.HStack, { gap: "xs", wrap: true, children: [
22813
+ /* @__PURE__ */ jsxRuntime.jsx(
22814
+ exports.Badge,
22815
+ {
22816
+ variant: activeCategory === ALL_CATEGORY ? "primary" : "neutral",
22817
+ size: "sm",
22818
+ role: "button",
22819
+ tabIndex: 0,
22820
+ "aria-pressed": activeCategory === ALL_CATEGORY,
22821
+ className: "cursor-pointer",
22822
+ onClick: () => setActiveCategory(ALL_CATEGORY),
22823
+ onKeyDown: (e) => {
22824
+ if (e.key === "Enter" || e.key === " ") {
22825
+ e.preventDefault();
22826
+ setActiveCategory(ALL_CATEGORY);
22827
+ }
22828
+ },
22829
+ children: "All"
22830
+ }
22831
+ ),
22832
+ categoryChips.map((category) => /* @__PURE__ */ jsxRuntime.jsx(
22833
+ exports.Badge,
22834
+ {
22835
+ variant: activeCategory === category ? "primary" : "neutral",
22836
+ size: "sm",
22837
+ role: "button",
22838
+ tabIndex: 0,
22839
+ "aria-pressed": activeCategory === category,
22840
+ className: "cursor-pointer",
22841
+ onClick: () => setActiveCategory(category),
22842
+ onKeyDown: (e) => {
22843
+ if (e.key === "Enter" || e.key === " ") {
22844
+ e.preventDefault();
22845
+ setActiveCategory(category);
22846
+ }
22847
+ },
22848
+ children: category
22849
+ },
22850
+ category
22851
+ ))
22852
+ ] }),
22853
+ /* @__PURE__ */ jsxRuntime.jsx(
22854
+ "div",
22855
+ {
22856
+ ref: gridRef,
22857
+ role: "listbox",
22858
+ className: "grid gap-1 overflow-y-auto max-h-64 p-1",
22859
+ style: {
22860
+ gridTemplateColumns: `repeat(auto-fill, minmax(${cellSize}px, 1fr))`
22861
+ },
22862
+ children: filtered.map((item, index) => {
22863
+ const selected = item.id === value;
22864
+ return /* @__PURE__ */ jsxRuntime.jsx(
22865
+ "button",
22866
+ {
22867
+ type: "button",
22868
+ role: "option",
22869
+ "aria-selected": selected,
22870
+ "aria-label": item.label,
22871
+ title: item.label,
22872
+ "data-gridpicker-cell": true,
22873
+ tabIndex: selected || value === void 0 && index === 0 ? 0 : -1,
22874
+ onClick: () => select(item),
22875
+ onKeyDown: (e) => handleKeyDown(e, index),
22876
+ className: cn(
22877
+ "flex items-center justify-center rounded-sm",
22878
+ "transition-colors hover:bg-muted",
22879
+ "focus:outline-none focus:ring-1 focus:ring-ring",
22880
+ selected && "bg-primary/10 ring-1 ring-primary"
22881
+ ),
22882
+ style: { width: cellSize, height: cellSize },
22883
+ children: renderThumbnail(item)
22884
+ },
22885
+ item.id
22886
+ );
22887
+ })
22888
+ }
22889
+ )
22890
+ ] });
22891
+ };
22892
+ exports.GridPicker.displayName = "GridPicker";
22893
+ }
22894
+ });
22895
+ function pascalToKebab(name) {
22896
+ return name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([A-Z])([A-Z][a-z])/g, "$1-$2").toLowerCase();
22897
+ }
22898
+ function kebabToPascal3(name) {
22899
+ return name.split("-").map((part) => /^\d+$/.test(part) ? part : part.charAt(0).toUpperCase() + part.slice(1)).join("");
22900
+ }
22901
+ var ICON_ITEMS; exports.IconPicker = void 0;
22902
+ var init_IconPicker = __esm({
22903
+ "components/core/molecules/IconPicker.tsx"() {
22904
+ "use client";
22905
+ init_Icon();
22906
+ init_GridPicker();
22907
+ ICON_ITEMS = (() => {
22908
+ const items = [];
22909
+ for (const [exportName, candidate] of Object.entries(LucideIcons2__namespace)) {
22910
+ if (!/^[A-Z]/.test(exportName)) continue;
22911
+ if (exportName.endsWith("Icon")) continue;
22912
+ if (exportName.startsWith("Lucide")) continue;
22913
+ const isComponent = candidate !== null && (typeof candidate === "object" || typeof candidate === "function") && "$$typeof" in candidate;
22914
+ if (!isComponent) continue;
22915
+ const kebab = pascalToKebab(exportName);
22916
+ if (kebabToPascal3(kebab) !== exportName) continue;
22917
+ items.push({ id: kebab, label: kebab, category: "icons" });
22918
+ }
22919
+ return items;
22920
+ })();
22921
+ exports.IconPicker = ({ value, onChange, className }) => {
22922
+ const items = React74.useMemo(() => ICON_ITEMS, []);
22923
+ return /* @__PURE__ */ jsxRuntime.jsx(
22924
+ exports.GridPicker,
22925
+ {
22926
+ items,
22927
+ value,
22928
+ onChange,
22929
+ searchPlaceholder: "Search icons\u2026",
22930
+ renderThumbnail: (it) => /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: it.id }),
22931
+ cellSize: 32,
22932
+ className
22933
+ }
22934
+ );
22935
+ };
22936
+ exports.IconPicker.displayName = "IconPicker";
22937
+ }
22938
+ });
22939
+ function iconForKind(kind) {
22940
+ if (kind === "audio") return "music";
22941
+ if (kind === "model") return "box";
22942
+ return "file";
22943
+ }
22944
+ var THUMB_PX, IMAGE_KINDS; exports.AssetPicker = void 0;
22945
+ var init_AssetPicker = __esm({
22946
+ "components/core/molecules/AssetPicker.tsx"() {
22947
+ "use client";
22948
+ init_GridPicker();
22949
+ init_Icon();
22950
+ THUMB_PX = 32;
22951
+ IMAGE_KINDS = /* @__PURE__ */ new Set([
22952
+ "image",
22953
+ "spritesheet",
22954
+ "scene",
22955
+ "portrait"
22956
+ ]);
22957
+ exports.AssetPicker = ({
22958
+ assets,
22959
+ value,
22960
+ onChange,
22961
+ className
22962
+ }) => {
22963
+ const byUrl = React74.useMemo(() => {
22964
+ const map = /* @__PURE__ */ new Map();
22965
+ for (const entry of assets) map.set(entry.url, entry);
22966
+ return map;
22967
+ }, [assets]);
22968
+ const items = React74.useMemo(
22969
+ () => assets.map((entry) => ({
22970
+ id: entry.url,
22971
+ label: entry.name,
22972
+ category: entry.category
22973
+ })),
22974
+ [assets]
22975
+ );
22976
+ const categories = React74.useMemo(() => {
22977
+ const seen = [];
22978
+ for (const entry of assets) {
22979
+ if (!seen.includes(entry.category)) seen.push(entry.category);
22980
+ }
22981
+ return seen;
22982
+ }, [assets]);
22983
+ const renderThumbnail = React74.useCallback(
22984
+ (item) => {
22985
+ const entry = byUrl.get(item.id);
22986
+ if (entry === void 0) return null;
22987
+ if (IMAGE_KINDS.has(entry.kind)) {
22988
+ return /* @__PURE__ */ jsxRuntime.jsx(
22989
+ "img",
22990
+ {
22991
+ src: entry.thumbnailUrl ?? entry.url,
22992
+ alt: entry.name,
22993
+ loading: "lazy",
22994
+ width: THUMB_PX,
22995
+ height: THUMB_PX,
22996
+ style: { width: THUMB_PX, height: THUMB_PX, objectFit: "cover" }
22997
+ }
22998
+ );
22999
+ }
23000
+ return /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: iconForKind(entry.kind), size: "sm" });
23001
+ },
23002
+ [byUrl]
23003
+ );
23004
+ return /* @__PURE__ */ jsxRuntime.jsx(
23005
+ exports.GridPicker,
23006
+ {
23007
+ items,
23008
+ value,
23009
+ onChange,
23010
+ categories,
23011
+ renderThumbnail,
23012
+ cellSize: THUMB_PX,
23013
+ className
23014
+ }
23015
+ );
23016
+ };
23017
+ exports.AssetPicker.displayName = "AssetPicker";
23018
+ }
23019
+ });
23020
+ function currentValue(decl, override) {
23021
+ return override !== void 0 ? override : decl.default;
23022
+ }
23023
+ function TextLikeControl({
23024
+ field,
23025
+ numeric,
23026
+ value,
23027
+ onCommit
23028
+ }) {
23029
+ const initial = value === void 0 || value === null ? "" : String(value);
23030
+ const [draft, setDraft] = React74__namespace.default.useState(initial);
23031
+ React74__namespace.default.useEffect(() => setDraft(initial), [initial]);
23032
+ const commit = () => {
23033
+ if (numeric) {
23034
+ const n = draft.trim() === "" ? 0 : Number(draft);
23035
+ onCommit(field, Number.isNaN(n) ? 0 : n);
23036
+ } else {
23037
+ onCommit(field, draft);
23038
+ }
23039
+ };
23040
+ return /* @__PURE__ */ jsxRuntime.jsx(
23041
+ exports.Input,
23042
+ {
23043
+ inputType: numeric ? "number" : "text",
23044
+ value: draft,
23045
+ onChange: (e) => setDraft(e.target.value),
23046
+ onBlur: commit,
23047
+ onKeyDown: (e) => {
23048
+ if (e.key === "Enter") commit();
23049
+ }
23050
+ }
23051
+ );
23052
+ }
23053
+ function isTraitConfigObject(v) {
23054
+ return v !== null && v !== void 0 && typeof v === "object" && !Array.isArray(v);
23055
+ }
23056
+ function FieldControl({
23057
+ name,
23058
+ decl,
23059
+ value,
23060
+ onChange,
23061
+ assets
23062
+ }) {
23063
+ let control;
23064
+ const stringValue = typeof value === "string" ? value : void 0;
23065
+ const effectiveValue = value ?? decl.default;
23066
+ if (decl.type === "icon") {
23067
+ control = /* @__PURE__ */ jsxRuntime.jsx(exports.IconPicker, { value: stringValue, onChange: (icon) => onChange(name, icon) });
23068
+ } else if (decl.type === "asset") {
23069
+ control = /* @__PURE__ */ jsxRuntime.jsx(
23070
+ exports.AssetPicker,
23071
+ {
23072
+ assets: assets ?? [],
23073
+ value: stringValue,
23074
+ onChange: (url) => onChange(name, url)
23075
+ }
23076
+ );
23077
+ } else if (decl.type === "boolean") {
23078
+ control = /* @__PURE__ */ jsxRuntime.jsx(exports.Switch, { checked: value === true, onChange: (c) => onChange(name, c) });
23079
+ } else if (decl.type === "string" && decl.values !== void 0 && decl.values.length > 0) {
23080
+ control = /* @__PURE__ */ jsxRuntime.jsx(
23081
+ exports.Select,
23082
+ {
23083
+ options: decl.values.map((v) => ({ value: v, label: v })),
23084
+ value: typeof value === "string" ? value : "",
23085
+ onChange: (e) => onChange(name, e.target.value)
23086
+ }
23087
+ );
23088
+ } else if (decl.type === "number") {
23089
+ control = /* @__PURE__ */ jsxRuntime.jsx(TextLikeControl, { field: name, numeric: true, value, onCommit: onChange });
23090
+ } else if (decl.type === "string") {
23091
+ control = /* @__PURE__ */ jsxRuntime.jsx(TextLikeControl, { field: name, numeric: false, value, onCommit: onChange });
23092
+ } else if (decl.type === "node") {
23093
+ control = /* @__PURE__ */ jsxRuntime.jsx(exports.NodeSlotEditor, { value: effectiveValue, onChange: (next) => onChange(name, next) });
23094
+ } else if (decl.type.startsWith("[") || Array.isArray(effectiveValue)) {
23095
+ const arr = Array.isArray(effectiveValue) ? effectiveValue : [];
23096
+ control = /* @__PURE__ */ jsxRuntime.jsx(exports.JsonTreeEditor, { value: arr, onChange: (next) => onChange(name, next) });
23097
+ } else if (decl.type === "object" || decl.type.startsWith("Map ") || !SCALAR_TYPES.has(decl.type) && isTraitConfigObject(effectiveValue)) {
23098
+ const obj = isTraitConfigObject(effectiveValue) ? effectiveValue : {};
23099
+ control = /* @__PURE__ */ jsxRuntime.jsx(exports.JsonTreeEditor, { value: obj, onChange: (next) => onChange(name, next) });
23100
+ } else {
23101
+ control = /* @__PURE__ */ jsxRuntime.jsxs(exports.Typography, { variant: "caption", color: "muted", children: [
23102
+ decl.type,
23103
+ " \u2014 edit in source"
23104
+ ] });
23105
+ }
23106
+ return /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "xs", children: [
23107
+ /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "label", children: decl.label ?? name }),
23108
+ control,
23109
+ decl.description !== void 0 && decl.description !== "" && /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "caption", color: "muted", children: decl.description })
23110
+ ] });
23111
+ }
23112
+ var TIER_ORDER, SCALAR_TYPES; exports.PropertyInspector = void 0;
23113
+ var init_PropertyInspector = __esm({
23114
+ "components/core/molecules/PropertyInspector.tsx"() {
23115
+ "use client";
23116
+ init_cn();
23117
+ init_Stack();
23118
+ init_Typography();
23119
+ init_Button();
23120
+ init_Switch();
23121
+ init_Select();
23122
+ init_Input();
23123
+ init_FormSection();
23124
+ init_IconPicker();
23125
+ init_AssetPicker();
23126
+ init_JsonTreeEditor();
23127
+ init_NodeSlotEditor();
23128
+ TIER_ORDER = ["presentation", "domain", "policy", "infra", "internal"];
23129
+ SCALAR_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "icon", "asset"]);
23130
+ exports.PropertyInspector = ({
23131
+ config,
23132
+ values,
23133
+ onChange,
23134
+ onReset,
23135
+ title,
23136
+ className,
23137
+ assets
23138
+ }) => {
23139
+ const fields = Object.entries(config);
23140
+ const byTier = /* @__PURE__ */ new Map();
23141
+ for (const [name, decl] of fields) {
23142
+ const tier = decl.tier ?? "presentation";
23143
+ const arr = byTier.get(tier) ?? [];
23144
+ arr.push([name, decl]);
23145
+ byTier.set(tier, arr);
23146
+ }
23147
+ const tiers = [...byTier.keys()].sort((a, b) => {
23148
+ const ia = TIER_ORDER.indexOf(a);
23149
+ const ib = TIER_ORDER.indexOf(b);
23150
+ return (ia === -1 ? 99 : ia) - (ib === -1 ? 99 : ib);
23151
+ });
23152
+ return /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "sm", className: cn("w-full", className), children: [
23153
+ /* @__PURE__ */ jsxRuntime.jsxs(exports.HStack, { justify: "between", align: "center", children: [
23154
+ /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "caption", weight: "bold", children: title ?? "Config" }),
23155
+ onReset !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(exports.Button, { variant: "ghost", size: "sm", icon: "rotate-ccw", label: "Reset", onClick: onReset })
23156
+ ] }),
23157
+ fields.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "caption", color: "muted", children: "No configurable properties." }),
23158
+ tiers.map((tier) => /* @__PURE__ */ jsxRuntime.jsx(exports.FormSection, { title: tier, collapsible: true, defaultCollapsed: tier !== "presentation", children: /* @__PURE__ */ jsxRuntime.jsx(exports.VStack, { gap: "sm", children: byTier.get(tier)?.map(([name, decl]) => /* @__PURE__ */ jsxRuntime.jsx(
23159
+ FieldControl,
23160
+ {
23161
+ name,
23162
+ decl,
23163
+ value: currentValue(decl, values?.[name]),
23164
+ onChange,
23165
+ assets
23166
+ },
23167
+ name
23168
+ )) }) }, tier))
23169
+ ] });
23170
+ };
23171
+ }
23172
+ });
22534
23173
  function normalize(value) {
22535
23174
  const wasArray = Array.isArray(value);
22536
23175
  const pattern = wasArray ? value[0] : value;
@@ -22549,6 +23188,7 @@ var init_NodeSlotEditor = __esm({
22549
23188
  init_Select();
22550
23189
  init_Typography();
22551
23190
  init_JsonTreeEditor();
23191
+ init_PropertyInspector();
22552
23192
  init_pattern_resolver();
22553
23193
  init_cn();
22554
23194
  isObj2 = (v) => v !== null && v !== void 0 && typeof v === "object" && !Array.isArray(v);
@@ -22573,6 +23213,21 @@ var init_NodeSlotEditor = __esm({
22573
23213
  const pattern = { type: nextType, ...nextProps };
22574
23214
  onChange(wasArray || value === void 0 ? [pattern] : pattern);
22575
23215
  };
23216
+ const schemaEntries = React74__namespace.default.useMemo(() => {
23217
+ if (!type) return [];
23218
+ const def = getPatternDefinition(type);
23219
+ if (!def?.propsSchema) return [];
23220
+ return Object.entries(def.propsSchema).map(([propName, propDef]) => {
23221
+ const decl = {
23222
+ type: propDef.types?.[0] ?? "string",
23223
+ description: propDef.description,
23224
+ label: propName,
23225
+ ...propDef.enumValues && propDef.enumValues.length > 0 ? { values: propDef.enumValues } : {}
23226
+ };
23227
+ return [propName, decl];
23228
+ });
23229
+ }, [type]);
23230
+ const hasSchema = schemaEntries.length > 0;
22576
23231
  return /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "xs", className: cn("w-full", className), children: [
22577
23232
  /* @__PURE__ */ jsxRuntime.jsx(
22578
23233
  exports.Select,
@@ -22584,7 +23239,16 @@ var init_NodeSlotEditor = __esm({
22584
23239
  ),
22585
23240
  type !== "" && /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "none", className: "pl-1", children: [
22586
23241
  /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "caption", color: "muted", children: "props" }),
22587
- /* @__PURE__ */ jsxRuntime.jsx(exports.JsonTreeEditor, { value: props, onChange: (next) => emit(type, isObj2(next) ? next : {}) })
23242
+ hasSchema ? /* @__PURE__ */ jsxRuntime.jsx(exports.VStack, { gap: "xs", children: schemaEntries.map(([propName, decl]) => /* @__PURE__ */ jsxRuntime.jsx(
23243
+ FieldControl,
23244
+ {
23245
+ name: propName,
23246
+ decl,
23247
+ value: props[propName],
23248
+ onChange: (field, val) => emit(type, { ...props, [field]: val })
23249
+ },
23250
+ propName
23251
+ )) }) : /* @__PURE__ */ jsxRuntime.jsx(exports.JsonTreeEditor, { value: props, onChange: (next) => emit(type, isObj2(next) ? next : {}) })
22588
23252
  ] })
22589
23253
  ] });
22590
23254
  };
@@ -25167,303 +25831,6 @@ var init_FlipCard = __esm({
25167
25831
  exports.FlipCard.displayName = "FlipCard";
25168
25832
  }
25169
25833
  });
25170
- var ALL_CATEGORY; exports.GridPicker = void 0;
25171
- var init_GridPicker = __esm({
25172
- "components/core/molecules/GridPicker.tsx"() {
25173
- "use client";
25174
- init_cn();
25175
- init_Input();
25176
- init_Badge();
25177
- init_Stack();
25178
- ALL_CATEGORY = "__all__";
25179
- exports.GridPicker = ({
25180
- items,
25181
- value,
25182
- onChange,
25183
- categories,
25184
- searchPlaceholder,
25185
- renderThumbnail,
25186
- cellSize = 32,
25187
- className
25188
- }) => {
25189
- const [search, setSearch] = React74.useState("");
25190
- const [activeCategory, setActiveCategory] = React74.useState(ALL_CATEGORY);
25191
- const gridRef = React74.useRef(null);
25192
- const categoryChips = React74.useMemo(() => {
25193
- if (categories !== void 0) return categories;
25194
- const seen = [];
25195
- for (const item of items) {
25196
- if (!seen.includes(item.category)) seen.push(item.category);
25197
- }
25198
- return seen;
25199
- }, [categories, items]);
25200
- const filtered = React74.useMemo(() => {
25201
- const needle = search.trim().toLowerCase();
25202
- return items.filter((item) => {
25203
- const matchesCategory = activeCategory === ALL_CATEGORY || item.category === activeCategory;
25204
- const matchesSearch = needle === "" || item.label.toLowerCase().includes(needle);
25205
- return matchesCategory && matchesSearch;
25206
- });
25207
- }, [items, search, activeCategory]);
25208
- const select = React74.useCallback(
25209
- (item) => {
25210
- onChange(item.id);
25211
- },
25212
- [onChange]
25213
- );
25214
- const handleKeyDown = React74.useCallback(
25215
- (e, index) => {
25216
- const cells = gridRef.current?.querySelectorAll(
25217
- "[data-gridpicker-cell]"
25218
- );
25219
- if (cells === void 0 || cells.length === 0) return;
25220
- const columns = (() => {
25221
- const grid = gridRef.current;
25222
- if (grid === null) return 1;
25223
- const style = window.getComputedStyle(grid);
25224
- const cols = style.gridTemplateColumns.split(" ").filter(Boolean).length;
25225
- return cols > 0 ? cols : 1;
25226
- })();
25227
- let next = -1;
25228
- if (e.key === "ArrowRight") next = index + 1;
25229
- else if (e.key === "ArrowLeft") next = index - 1;
25230
- else if (e.key === "ArrowDown") next = index + columns;
25231
- else if (e.key === "ArrowUp") next = index - columns;
25232
- else if (e.key === "Enter" || e.key === " ") {
25233
- e.preventDefault();
25234
- select(filtered[index]);
25235
- return;
25236
- } else {
25237
- return;
25238
- }
25239
- e.preventDefault();
25240
- if (next >= 0 && next < cells.length) {
25241
- cells[next].focus();
25242
- }
25243
- },
25244
- [filtered, select]
25245
- );
25246
- return /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "sm", className: cn("w-full", className), children: [
25247
- /* @__PURE__ */ jsxRuntime.jsx(
25248
- exports.Input,
25249
- {
25250
- type: "search",
25251
- icon: "search",
25252
- value: search,
25253
- placeholder: searchPlaceholder,
25254
- clearable: true,
25255
- onClear: () => setSearch(""),
25256
- onChange: (e) => setSearch(e.target.value)
25257
- }
25258
- ),
25259
- categoryChips.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(exports.HStack, { gap: "xs", wrap: true, children: [
25260
- /* @__PURE__ */ jsxRuntime.jsx(
25261
- exports.Badge,
25262
- {
25263
- variant: activeCategory === ALL_CATEGORY ? "primary" : "neutral",
25264
- size: "sm",
25265
- role: "button",
25266
- tabIndex: 0,
25267
- "aria-pressed": activeCategory === ALL_CATEGORY,
25268
- className: "cursor-pointer",
25269
- onClick: () => setActiveCategory(ALL_CATEGORY),
25270
- onKeyDown: (e) => {
25271
- if (e.key === "Enter" || e.key === " ") {
25272
- e.preventDefault();
25273
- setActiveCategory(ALL_CATEGORY);
25274
- }
25275
- },
25276
- children: "All"
25277
- }
25278
- ),
25279
- categoryChips.map((category) => /* @__PURE__ */ jsxRuntime.jsx(
25280
- exports.Badge,
25281
- {
25282
- variant: activeCategory === category ? "primary" : "neutral",
25283
- size: "sm",
25284
- role: "button",
25285
- tabIndex: 0,
25286
- "aria-pressed": activeCategory === category,
25287
- className: "cursor-pointer",
25288
- onClick: () => setActiveCategory(category),
25289
- onKeyDown: (e) => {
25290
- if (e.key === "Enter" || e.key === " ") {
25291
- e.preventDefault();
25292
- setActiveCategory(category);
25293
- }
25294
- },
25295
- children: category
25296
- },
25297
- category
25298
- ))
25299
- ] }),
25300
- /* @__PURE__ */ jsxRuntime.jsx(
25301
- "div",
25302
- {
25303
- ref: gridRef,
25304
- role: "listbox",
25305
- className: "grid gap-1 overflow-y-auto max-h-64 p-1",
25306
- style: {
25307
- gridTemplateColumns: `repeat(auto-fill, minmax(${cellSize}px, 1fr))`
25308
- },
25309
- children: filtered.map((item, index) => {
25310
- const selected = item.id === value;
25311
- return /* @__PURE__ */ jsxRuntime.jsx(
25312
- "button",
25313
- {
25314
- type: "button",
25315
- role: "option",
25316
- "aria-selected": selected,
25317
- "aria-label": item.label,
25318
- title: item.label,
25319
- "data-gridpicker-cell": true,
25320
- tabIndex: selected || value === void 0 && index === 0 ? 0 : -1,
25321
- onClick: () => select(item),
25322
- onKeyDown: (e) => handleKeyDown(e, index),
25323
- className: cn(
25324
- "flex items-center justify-center rounded-sm",
25325
- "transition-colors hover:bg-muted",
25326
- "focus:outline-none focus:ring-1 focus:ring-ring",
25327
- selected && "bg-primary/10 ring-1 ring-primary"
25328
- ),
25329
- style: { width: cellSize, height: cellSize },
25330
- children: renderThumbnail(item)
25331
- },
25332
- item.id
25333
- );
25334
- })
25335
- }
25336
- )
25337
- ] });
25338
- };
25339
- exports.GridPicker.displayName = "GridPicker";
25340
- }
25341
- });
25342
- function iconForKind(kind) {
25343
- if (kind === "audio") return "music";
25344
- if (kind === "model") return "box";
25345
- return "file";
25346
- }
25347
- var THUMB_PX, IMAGE_KINDS; exports.AssetPicker = void 0;
25348
- var init_AssetPicker = __esm({
25349
- "components/core/molecules/AssetPicker.tsx"() {
25350
- "use client";
25351
- init_GridPicker();
25352
- init_Icon();
25353
- THUMB_PX = 32;
25354
- IMAGE_KINDS = /* @__PURE__ */ new Set([
25355
- "image",
25356
- "spritesheet",
25357
- "scene",
25358
- "portrait"
25359
- ]);
25360
- exports.AssetPicker = ({
25361
- assets,
25362
- value,
25363
- onChange,
25364
- className
25365
- }) => {
25366
- const byUrl = React74.useMemo(() => {
25367
- const map = /* @__PURE__ */ new Map();
25368
- for (const entry of assets) map.set(entry.url, entry);
25369
- return map;
25370
- }, [assets]);
25371
- const items = React74.useMemo(
25372
- () => assets.map((entry) => ({
25373
- id: entry.url,
25374
- label: entry.name,
25375
- category: entry.category
25376
- })),
25377
- [assets]
25378
- );
25379
- const categories = React74.useMemo(() => {
25380
- const seen = [];
25381
- for (const entry of assets) {
25382
- if (!seen.includes(entry.category)) seen.push(entry.category);
25383
- }
25384
- return seen;
25385
- }, [assets]);
25386
- const renderThumbnail = React74.useCallback(
25387
- (item) => {
25388
- const entry = byUrl.get(item.id);
25389
- if (entry === void 0) return null;
25390
- if (IMAGE_KINDS.has(entry.kind)) {
25391
- return /* @__PURE__ */ jsxRuntime.jsx(
25392
- "img",
25393
- {
25394
- src: entry.thumbnailUrl ?? entry.url,
25395
- alt: entry.name,
25396
- loading: "lazy",
25397
- width: THUMB_PX,
25398
- height: THUMB_PX,
25399
- style: { width: THUMB_PX, height: THUMB_PX, objectFit: "cover" }
25400
- }
25401
- );
25402
- }
25403
- return /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: iconForKind(entry.kind), size: "sm" });
25404
- },
25405
- [byUrl]
25406
- );
25407
- return /* @__PURE__ */ jsxRuntime.jsx(
25408
- exports.GridPicker,
25409
- {
25410
- items,
25411
- value,
25412
- onChange,
25413
- categories,
25414
- renderThumbnail,
25415
- cellSize: THUMB_PX,
25416
- className
25417
- }
25418
- );
25419
- };
25420
- exports.AssetPicker.displayName = "AssetPicker";
25421
- }
25422
- });
25423
- function pascalToKebab(name) {
25424
- return name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([A-Z])([A-Z][a-z])/g, "$1-$2").toLowerCase();
25425
- }
25426
- function kebabToPascal3(name) {
25427
- return name.split("-").map((part) => /^\d+$/.test(part) ? part : part.charAt(0).toUpperCase() + part.slice(1)).join("");
25428
- }
25429
- var ICON_ITEMS; exports.IconPicker = void 0;
25430
- var init_IconPicker = __esm({
25431
- "components/core/molecules/IconPicker.tsx"() {
25432
- "use client";
25433
- init_Icon();
25434
- init_GridPicker();
25435
- ICON_ITEMS = (() => {
25436
- const items = [];
25437
- for (const [exportName, candidate] of Object.entries(LucideIcons2__namespace)) {
25438
- if (!/^[A-Z]/.test(exportName)) continue;
25439
- if (exportName.endsWith("Icon")) continue;
25440
- if (exportName.startsWith("Lucide")) continue;
25441
- const isComponent = candidate !== null && (typeof candidate === "object" || typeof candidate === "function") && "$$typeof" in candidate;
25442
- if (!isComponent) continue;
25443
- const kebab = pascalToKebab(exportName);
25444
- if (kebabToPascal3(kebab) !== exportName) continue;
25445
- items.push({ id: kebab, label: kebab, category: "icons" });
25446
- }
25447
- return items;
25448
- })();
25449
- exports.IconPicker = ({ value, onChange, className }) => {
25450
- const items = React74.useMemo(() => ICON_ITEMS, []);
25451
- return /* @__PURE__ */ jsxRuntime.jsx(
25452
- exports.GridPicker,
25453
- {
25454
- items,
25455
- value,
25456
- onChange,
25457
- searchPlaceholder: "Search icons\u2026",
25458
- renderThumbnail: (it) => /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: it.id }),
25459
- cellSize: 32,
25460
- className
25461
- }
25462
- );
25463
- };
25464
- exports.IconPicker.displayName = "IconPicker";
25465
- }
25466
- });
25467
25834
  function toISODate(d) {
25468
25835
  return d.toISOString().slice(0, 10);
25469
25836
  }
@@ -35154,286 +35521,6 @@ var init_PageHeader = __esm({
35154
35521
  exports.PageHeader.displayName = "PageHeader";
35155
35522
  }
35156
35523
  });
35157
- exports.FormSection = void 0; exports.FormLayout = void 0; exports.FormActions = void 0;
35158
- var init_FormSection = __esm({
35159
- "components/core/molecules/FormSection.tsx"() {
35160
- "use client";
35161
- init_cn();
35162
- init_atoms2();
35163
- init_Box();
35164
- init_Typography();
35165
- init_Button();
35166
- init_Stack();
35167
- init_Icon();
35168
- init_useEventBus();
35169
- exports.FormSection = ({
35170
- title,
35171
- description,
35172
- children,
35173
- collapsible = false,
35174
- defaultCollapsed = false,
35175
- card = false,
35176
- columns = 1,
35177
- className
35178
- }) => {
35179
- const [collapsed, setCollapsed] = React74__namespace.default.useState(defaultCollapsed);
35180
- const { t } = hooks.useTranslate();
35181
- const eventBus = useEventBus();
35182
- const gridClass = {
35183
- 1: "grid-cols-1",
35184
- 2: "grid-cols-1 md:grid-cols-2",
35185
- 3: "grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
35186
- }[columns];
35187
- React74__namespace.default.useCallback(() => {
35188
- if (collapsible) {
35189
- setCollapsed((prev) => !prev);
35190
- eventBus.emit("UI:TOGGLE_COLLAPSE", { collapsed: !collapsed });
35191
- }
35192
- }, [collapsible, collapsed, eventBus]);
35193
- const content = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
35194
- (title || description) && /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "xs", className: "mb-4", children: [
35195
- title && /* @__PURE__ */ jsxRuntime.jsxs(
35196
- exports.HStack,
35197
- {
35198
- justify: "between",
35199
- align: "center",
35200
- className: cn(collapsible && "cursor-pointer"),
35201
- action: collapsible ? "TOGGLE_COLLAPSE" : void 0,
35202
- children: [
35203
- /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "h3", weight: "semibold", children: title }),
35204
- collapsible && /* @__PURE__ */ jsxRuntime.jsx(
35205
- exports.Button,
35206
- {
35207
- variant: "ghost",
35208
- size: "sm",
35209
- action: "TOGGLE_COLLAPSE",
35210
- children: /* @__PURE__ */ jsxRuntime.jsx(
35211
- exports.Icon,
35212
- {
35213
- icon: LucideIcons2.ChevronDown,
35214
- size: "sm",
35215
- className: cn(
35216
- "text-muted-foreground transition-transform",
35217
- collapsed && "rotate-180"
35218
- )
35219
- }
35220
- )
35221
- }
35222
- )
35223
- ]
35224
- }
35225
- ),
35226
- description && /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "small", color: "secondary", children: description })
35227
- ] }),
35228
- (!collapsible || !collapsed) && /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: cn("grid gap-4", gridClass), children })
35229
- ] });
35230
- if (card) {
35231
- return /* @__PURE__ */ jsxRuntime.jsx(exports.Card, { className: cn("p-6", className), children: content });
35232
- }
35233
- return /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className, children: content });
35234
- };
35235
- exports.FormSection.displayName = "FormSection";
35236
- exports.FormLayout = ({
35237
- children,
35238
- dividers = true,
35239
- className
35240
- }) => {
35241
- return /* @__PURE__ */ jsxRuntime.jsx(
35242
- exports.VStack,
35243
- {
35244
- gap: "lg",
35245
- className: cn(
35246
- dividers && "[&>*+*]:pt-8 [&>*+*]:border-t [&>*+*]:border-border",
35247
- className
35248
- ),
35249
- children
35250
- }
35251
- );
35252
- };
35253
- exports.FormLayout.displayName = "FormLayout";
35254
- exports.FormActions = ({
35255
- children,
35256
- sticky = false,
35257
- align = "right",
35258
- className
35259
- }) => {
35260
- const alignClass2 = {
35261
- left: "justify-start",
35262
- right: "justify-end",
35263
- between: "justify-between",
35264
- center: "justify-center"
35265
- }[align];
35266
- return /* @__PURE__ */ jsxRuntime.jsx(
35267
- exports.HStack,
35268
- {
35269
- gap: "sm",
35270
- align: "center",
35271
- className: cn(
35272
- "pt-6 border-t border-border",
35273
- alignClass2,
35274
- sticky && "sticky bottom-0 bg-card py-4 -mx-6 px-6 shadow-[0_-4px_6px_-1px_rgb(0,0,0,0.05)]",
35275
- className
35276
- ),
35277
- children
35278
- }
35279
- );
35280
- };
35281
- exports.FormActions.displayName = "FormActions";
35282
- }
35283
- });
35284
- function currentValue(decl, override) {
35285
- return override !== void 0 ? override : decl.default;
35286
- }
35287
- function TextLikeControl({
35288
- field,
35289
- numeric,
35290
- value,
35291
- onCommit
35292
- }) {
35293
- const initial = value === void 0 || value === null ? "" : String(value);
35294
- const [draft, setDraft] = React74__namespace.default.useState(initial);
35295
- React74__namespace.default.useEffect(() => setDraft(initial), [initial]);
35296
- const commit = () => {
35297
- if (numeric) {
35298
- const n = draft.trim() === "" ? 0 : Number(draft);
35299
- onCommit(field, Number.isNaN(n) ? 0 : n);
35300
- } else {
35301
- onCommit(field, draft);
35302
- }
35303
- };
35304
- return /* @__PURE__ */ jsxRuntime.jsx(
35305
- exports.Input,
35306
- {
35307
- inputType: numeric ? "number" : "text",
35308
- value: draft,
35309
- onChange: (e) => setDraft(e.target.value),
35310
- onBlur: commit,
35311
- onKeyDown: (e) => {
35312
- if (e.key === "Enter") commit();
35313
- }
35314
- }
35315
- );
35316
- }
35317
- function isTraitConfigObject(v) {
35318
- return v !== null && v !== void 0 && typeof v === "object" && !Array.isArray(v);
35319
- }
35320
- function FieldControl({
35321
- name,
35322
- decl,
35323
- value,
35324
- onChange,
35325
- assets
35326
- }) {
35327
- let control;
35328
- const stringValue = typeof value === "string" ? value : void 0;
35329
- const effectiveValue = value ?? decl.default;
35330
- if (decl.type === "icon") {
35331
- control = /* @__PURE__ */ jsxRuntime.jsx(exports.IconPicker, { value: stringValue, onChange: (icon) => onChange(name, icon) });
35332
- } else if (decl.type === "asset") {
35333
- control = /* @__PURE__ */ jsxRuntime.jsx(
35334
- exports.AssetPicker,
35335
- {
35336
- assets: assets ?? [],
35337
- value: stringValue,
35338
- onChange: (url) => onChange(name, url)
35339
- }
35340
- );
35341
- } else if (decl.type === "boolean") {
35342
- control = /* @__PURE__ */ jsxRuntime.jsx(exports.Switch, { checked: value === true, onChange: (c) => onChange(name, c) });
35343
- } else if (decl.type === "string" && decl.values !== void 0 && decl.values.length > 0) {
35344
- control = /* @__PURE__ */ jsxRuntime.jsx(
35345
- exports.Select,
35346
- {
35347
- options: decl.values.map((v) => ({ value: v, label: v })),
35348
- value: typeof value === "string" ? value : "",
35349
- onChange: (e) => onChange(name, e.target.value)
35350
- }
35351
- );
35352
- } else if (decl.type === "number") {
35353
- control = /* @__PURE__ */ jsxRuntime.jsx(TextLikeControl, { field: name, numeric: true, value, onCommit: onChange });
35354
- } else if (decl.type === "string") {
35355
- control = /* @__PURE__ */ jsxRuntime.jsx(TextLikeControl, { field: name, numeric: false, value, onCommit: onChange });
35356
- } else if (decl.type === "node") {
35357
- control = /* @__PURE__ */ jsxRuntime.jsx(exports.NodeSlotEditor, { value: effectiveValue, onChange: (next) => onChange(name, next) });
35358
- } else if (decl.type.startsWith("[") || Array.isArray(effectiveValue)) {
35359
- const arr = Array.isArray(effectiveValue) ? effectiveValue : [];
35360
- control = /* @__PURE__ */ jsxRuntime.jsx(exports.JsonTreeEditor, { value: arr, onChange: (next) => onChange(name, next) });
35361
- } else if (decl.type === "object" || decl.type.startsWith("Map ") || !SCALAR_TYPES.has(decl.type) && isTraitConfigObject(effectiveValue)) {
35362
- const obj = isTraitConfigObject(effectiveValue) ? effectiveValue : {};
35363
- control = /* @__PURE__ */ jsxRuntime.jsx(exports.JsonTreeEditor, { value: obj, onChange: (next) => onChange(name, next) });
35364
- } else {
35365
- control = /* @__PURE__ */ jsxRuntime.jsxs(exports.Typography, { variant: "caption", color: "muted", children: [
35366
- decl.type,
35367
- " \u2014 edit in source"
35368
- ] });
35369
- }
35370
- return /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "xs", children: [
35371
- /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "label", children: decl.label ?? name }),
35372
- control,
35373
- decl.description !== void 0 && decl.description !== "" && /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "caption", color: "muted", children: decl.description })
35374
- ] });
35375
- }
35376
- var TIER_ORDER, SCALAR_TYPES; exports.PropertyInspector = void 0;
35377
- var init_PropertyInspector = __esm({
35378
- "components/core/molecules/PropertyInspector.tsx"() {
35379
- "use client";
35380
- init_cn();
35381
- init_Stack();
35382
- init_Typography();
35383
- init_Button();
35384
- init_Switch();
35385
- init_Select();
35386
- init_Input();
35387
- init_FormSection();
35388
- init_IconPicker();
35389
- init_AssetPicker();
35390
- init_JsonTreeEditor();
35391
- init_NodeSlotEditor();
35392
- TIER_ORDER = ["presentation", "domain", "policy", "infra", "internal"];
35393
- SCALAR_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "icon", "asset"]);
35394
- exports.PropertyInspector = ({
35395
- config,
35396
- values,
35397
- onChange,
35398
- onReset,
35399
- title,
35400
- className,
35401
- assets
35402
- }) => {
35403
- const fields = Object.entries(config);
35404
- const byTier = /* @__PURE__ */ new Map();
35405
- for (const [name, decl] of fields) {
35406
- const tier = decl.tier ?? "presentation";
35407
- const arr = byTier.get(tier) ?? [];
35408
- arr.push([name, decl]);
35409
- byTier.set(tier, arr);
35410
- }
35411
- const tiers = [...byTier.keys()].sort((a, b) => {
35412
- const ia = TIER_ORDER.indexOf(a);
35413
- const ib = TIER_ORDER.indexOf(b);
35414
- return (ia === -1 ? 99 : ia) - (ib === -1 ? 99 : ib);
35415
- });
35416
- return /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "sm", className: cn("w-full", className), children: [
35417
- /* @__PURE__ */ jsxRuntime.jsxs(exports.HStack, { justify: "between", align: "center", children: [
35418
- /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "caption", weight: "bold", children: title ?? "Config" }),
35419
- onReset !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(exports.Button, { variant: "ghost", size: "sm", icon: "rotate-ccw", label: "Reset", onClick: onReset })
35420
- ] }),
35421
- fields.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "caption", color: "muted", children: "No configurable properties." }),
35422
- tiers.map((tier) => /* @__PURE__ */ jsxRuntime.jsx(exports.FormSection, { title: tier, collapsible: true, defaultCollapsed: tier !== "presentation", children: /* @__PURE__ */ jsxRuntime.jsx(exports.VStack, { gap: "sm", children: byTier.get(tier)?.map(([name, decl]) => /* @__PURE__ */ jsxRuntime.jsx(
35423
- FieldControl,
35424
- {
35425
- name,
35426
- decl,
35427
- value: currentValue(decl, values?.[name]),
35428
- onChange,
35429
- assets
35430
- },
35431
- name
35432
- )) }) }, tier))
35433
- ] });
35434
- };
35435
- }
35436
- });
35437
35524
  var lookStyles8; exports.Header = void 0;
35438
35525
  var init_Header = __esm({
35439
35526
  "components/core/molecules/Header.tsx"() {
@@ -36789,7 +36876,6 @@ var init_DocumentViewer = __esm({
36789
36876
  showPrint = false,
36790
36877
  actions,
36791
36878
  documents,
36792
- entity,
36793
36879
  isLoading = false,
36794
36880
  error,
36795
36881
  className
@@ -43959,7 +44045,7 @@ function TraitSlot({
43959
44045
  size = "md",
43960
44046
  showTooltip = true,
43961
44047
  categoryColors,
43962
- tooltipFrameUrl,
44048
+ tooltipFrameUrl = "",
43963
44049
  className,
43964
44050
  feedback,
43965
44051
  onItemDrop,
@@ -47048,7 +47134,7 @@ var init_WorldMapBoard = __esm({
47048
47134
  init_useEventBus();
47049
47135
  init_Stack();
47050
47136
  init_LoadingState();
47051
- init_IsometricCanvas2();
47137
+ init_IsometricCanvas();
47052
47138
  init_boardEntity();
47053
47139
  init_isometric();
47054
47140
  WorldMapBoard.displayName = "WorldMapBoard";
@@ -47188,7 +47274,7 @@ function lazyThree(name, loader) {
47188
47274
  ThreeWrapper.displayName = `Lazy(${name})`;
47189
47275
  return ThreeWrapper;
47190
47276
  }
47191
- var FeatureRenderer, COMPONENT_REGISTRY;
47277
+ var FeatureRenderer, GameCanvas3D, GameCanvas3DBattleTemplate, GameCanvas3DCastleTemplate, GameCanvas3DWorldMapTemplate, COMPONENT_REGISTRY;
47192
47278
  var init_component_registry_generated = __esm({
47193
47279
  "components/core/organisms/component-registry.generated.ts"() {
47194
47280
  init_AboutPageTemplate();
@@ -47474,7 +47560,11 @@ var init_component_registry_generated = __esm({
47474
47560
  init_WorldMapBoard();
47475
47561
  init_WorldMapTemplate();
47476
47562
  init_XPBar();
47477
- FeatureRenderer = lazyThree("FeatureRenderer", () => import('@almadar/ui/components/organisms/game/three'));
47563
+ FeatureRenderer = lazyThree("FeatureRenderer", () => import('@almadar/ui/components/molecules/game/three'));
47564
+ GameCanvas3D = lazyThree("GameCanvas3D", () => import('@almadar/ui/components/molecules/game/three'));
47565
+ GameCanvas3DBattleTemplate = lazyThree("GameCanvas3DBattleTemplate", () => import('@almadar/ui/components/molecules/game/three'));
47566
+ GameCanvas3DCastleTemplate = lazyThree("GameCanvas3DCastleTemplate", () => import('@almadar/ui/components/molecules/game/three'));
47567
+ GameCanvas3DWorldMapTemplate = lazyThree("GameCanvas3DWorldMapTemplate", () => import('@almadar/ui/components/molecules/game/three'));
47478
47568
  COMPONENT_REGISTRY = {
47479
47569
  "AboutPageTemplate": exports.AboutPageTemplate,
47480
47570
  "Accordion": exports.Accordion,
@@ -47587,6 +47677,10 @@ var init_component_registry_generated = __esm({
47587
47677
  "GameAudioProvider": GameAudioProvider,
47588
47678
  "GameAudioToggle": GameAudioToggle,
47589
47679
  "GameCanvas2D": GameCanvas2D,
47680
+ "GameCanvas3D": GameCanvas3D,
47681
+ "GameCanvas3DBattleTemplate": GameCanvas3DBattleTemplate,
47682
+ "GameCanvas3DCastleTemplate": GameCanvas3DCastleTemplate,
47683
+ "GameCanvas3DWorldMapTemplate": GameCanvas3DWorldMapTemplate,
47590
47684
  "GameHud": GameHud,
47591
47685
  "GameMenu": GameMenu,
47592
47686
  "GameOverScreen": GameOverScreen,