@almadar/ui 5.29.0 → 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.
@@ -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
 
@@ -20758,7 +20762,84 @@ var init_DashboardLayout = __esm({
20758
20762
  NavLinkBottom.displayName = "NavLinkBottom";
20759
20763
  }
20760
20764
  });
20761
- 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;
20762
20843
  var init_Menu = __esm({
20763
20844
  "components/core/molecules/Menu.tsx"() {
20764
20845
  "use client";
@@ -20769,6 +20850,14 @@ var init_Menu = __esm({
20769
20850
  init_Badge();
20770
20851
  init_cn();
20771
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
+ );
20772
20861
  exports.Menu = ({
20773
20862
  trigger,
20774
20863
  items,
@@ -20776,9 +20865,10 @@ var init_Menu = __esm({
20776
20865
  className
20777
20866
  }) => {
20778
20867
  const eventBus = useEventBus();
20779
- const { t, direction } = hooks.useTranslate();
20868
+ const { direction } = hooks.useTranslate();
20780
20869
  const [isOpen, setIsOpen] = React74.useState(false);
20781
20870
  const [activeSubMenu, setActiveSubMenu] = React74.useState(null);
20871
+ const [activeSubMenuRef, setActiveSubMenuRef] = React74.useState(null);
20782
20872
  const [triggerRect, setTriggerRect] = React74.useState(null);
20783
20873
  const triggerRef = React74.useRef(null);
20784
20874
  const menuRef = React74.useRef(null);
@@ -20793,13 +20883,14 @@ var init_Menu = __esm({
20793
20883
  }
20794
20884
  setIsOpen(!isOpen);
20795
20885
  setActiveSubMenu(null);
20886
+ setActiveSubMenuRef(null);
20796
20887
  };
20797
- const handleItemClick = (item) => {
20888
+ const handleItemClick = (item, itemId) => {
20798
20889
  if (item.disabled) return;
20799
20890
  if (item.subMenu && item.subMenu.length > 0) {
20800
- setActiveSubMenu(item.id ?? null);
20891
+ setActiveSubMenu(itemId);
20801
20892
  } else {
20802
- 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 });
20803
20894
  item.onClick?.();
20804
20895
  setIsOpen(false);
20805
20896
  }
@@ -20814,22 +20905,12 @@ var init_Menu = __esm({
20814
20905
  if (isOpen && menuRef.current && !menuRef.current.contains(e.target) && triggerRef.current && !triggerRef.current.contains(e.target)) {
20815
20906
  setIsOpen(false);
20816
20907
  setActiveSubMenu(null);
20908
+ setActiveSubMenuRef(null);
20817
20909
  }
20818
20910
  };
20819
20911
  document.addEventListener("mousedown", handleClickOutside);
20820
20912
  return () => document.removeEventListener("mousedown", handleClickOutside);
20821
20913
  }, [isOpen]);
20822
- const positionClasses = {
20823
- "top-left": "bottom-full left-0 mb-2",
20824
- "top-right": "bottom-full right-0 mb-2",
20825
- "bottom-left": "top-full left-0 mt-2",
20826
- "bottom-right": "top-full right-0 mt-2",
20827
- // Aliases for pattern compatibility
20828
- "top-start": "bottom-full left-0 mb-2",
20829
- "top-end": "bottom-full right-0 mb-2",
20830
- "bottom-start": "top-full left-0 mt-2",
20831
- "bottom-end": "top-full right-0 mt-2"
20832
- };
20833
20914
  const rtlMirror = {
20834
20915
  "top-left": "top-right",
20835
20916
  "top-right": "top-left",
@@ -20841,7 +20922,6 @@ var init_Menu = __esm({
20841
20922
  "bottom-end": "bottom-start"
20842
20923
  };
20843
20924
  const effectivePosition = direction === "rtl" ? rtlMirror[position] ?? position : position;
20844
- const subMenuSideClass = direction === "rtl" ? "right-full mr-2" : "left-full ml-2";
20845
20925
  const triggerChild = React74__namespace.default.isValidElement(trigger) ? trigger : /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "small", as: "span", children: trigger });
20846
20926
  const triggerElement = React74__namespace.default.cloneElement(
20847
20927
  triggerChild,
@@ -20850,94 +20930,83 @@ var init_Menu = __esm({
20850
20930
  onClick: handleToggle
20851
20931
  }
20852
20932
  );
20853
- const menuContainerStyles = cn(
20854
- "bg-card",
20855
- "border-[length:var(--border-width)] border-border",
20856
- "shadow-elevation-popover",
20857
- "rounded-sm",
20858
- "min-w-0 sm:min-w-[200px] max-w-[calc(100vw-1rem)] py-1"
20859
- );
20860
- const renderMenuItem = (item, hasSubMenu, index) => {
20933
+ const renderMenuItems = (menuItems) => menuItems.map((item, index) => {
20934
+ const isDivider = item.id === "divider" || item.label === "divider";
20861
20935
  const itemId = item.id ?? `item-${item.label.toLowerCase().replace(/\s+/g, "-")}-${index}`;
20936
+ const hasSubMenu = !!(item.subMenu && item.subMenu.length > 0);
20862
20937
  const isDanger = item.variant === "danger";
20863
- return /* @__PURE__ */ jsxRuntime.jsx(
20864
- exports.Box,
20865
- {
20866
- as: "button",
20867
- onClick: () => !item.disabled && handleItemClick({ ...item, id: itemId }),
20868
- "aria-disabled": item.disabled || void 0,
20869
- onMouseEnter: () => hasSubMenu && setActiveSubMenu(itemId),
20870
- "data-testid": item.event ? `action-${item.event}` : void 0,
20871
- className: cn(
20872
- "w-full flex items-center justify-between gap-3 px-4 py-2 text-start",
20873
- "text-sm transition-colors",
20874
- "hover:bg-muted",
20875
- "focus:outline-none focus:bg-muted",
20876
- "disabled:opacity-50 disabled:cursor-not-allowed",
20877
- item.disabled && "cursor-not-allowed",
20878
- isDanger && "text-error hover:bg-error/10"
20879
- ),
20880
- children: /* @__PURE__ */ jsxRuntime.jsxs(exports.Box, { className: "flex items-center gap-3 flex-1 min-w-0", children: [
20881
- 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" })),
20882
- /* @__PURE__ */ jsxRuntime.jsx(
20883
- exports.Typography,
20884
- {
20885
- variant: "small",
20886
- className: cn("flex-1", isDanger && "text-red-600"),
20887
- 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);
20888
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"
20889
20963
  ),
20890
- item.badge !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(exports.Badge, { variant: "default", size: "sm", children: item.badge }),
20891
- hasSubMenu && /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: direction === "rtl" ? "chevron-left" : "chevron-right", size: "sm", className: "flex-shrink-0" })
20892
- ] })
20893
- },
20894
- itemId
20895
- );
20896
- };
20897
- const renderMenuItems = (menuItems) => {
20898
- return menuItems.map((item, index) => {
20899
- const hasSubMenu = item.subMenu && item.subMenu.length > 0;
20900
- const isDivider = item.id === "divider" || item.label === "divider";
20901
- const itemId = item.id ?? `item-${item.label.toLowerCase().replace(/\s+/g, "-")}-${index}`;
20902
- if (isDivider) {
20903
- return /* @__PURE__ */ jsxRuntime.jsx(exports.Divider, { className: "my-1" }, `divider-${index}`);
20904
- }
20905
- return /* @__PURE__ */ jsxRuntime.jsxs(exports.Box, { children: [
20906
- renderMenuItem(item, !!hasSubMenu, index),
20907
- hasSubMenu && activeSubMenu === itemId && item.subMenu && /* @__PURE__ */ jsxRuntime.jsx(
20908
- exports.Box,
20909
- {
20910
- className: cn(
20911
- "absolute top-0 z-50",
20912
- subMenuSideClass,
20913
- 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
+ }
20914
20973
  ),
20915
- children: renderMenuItems(item.subMenu)
20916
- }
20917
- )
20918
- ] }, itemId);
20919
- });
20920
- };
20921
- 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: [
20922
21008
  triggerElement,
20923
- isOpen && triggerRect && /* @__PURE__ */ jsxRuntime.jsx(
20924
- exports.Box,
20925
- {
20926
- ref: menuRef,
20927
- className: cn(
20928
- "absolute z-50",
20929
- menuContainerStyles,
20930
- positionClasses[effectivePosition],
20931
- className
20932
- ),
20933
- style: {
20934
- left: effectivePosition.includes("left") ? 0 : "auto",
20935
- right: effectivePosition.includes("right") ? 0 : "auto"
20936
- },
20937
- role: "menu",
20938
- children: renderMenuItems(items)
20939
- }
20940
- )
21009
+ panel && typeof document !== "undefined" ? reactDom.createPortal(panel, document.body) : panel
20941
21010
  ] });
20942
21011
  };
20943
21012
  exports.Menu.displayName = "Menu";
@@ -22524,6 +22593,583 @@ var init_JsonTreeEditor = __esm({
22524
22593
  };
22525
22594
  }
22526
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
+ });
22527
23173
  function normalize(value) {
22528
23174
  const wasArray = Array.isArray(value);
22529
23175
  const pattern = wasArray ? value[0] : value;
@@ -22542,6 +23188,7 @@ var init_NodeSlotEditor = __esm({
22542
23188
  init_Select();
22543
23189
  init_Typography();
22544
23190
  init_JsonTreeEditor();
23191
+ init_PropertyInspector();
22545
23192
  init_pattern_resolver();
22546
23193
  init_cn();
22547
23194
  isObj2 = (v) => v !== null && v !== void 0 && typeof v === "object" && !Array.isArray(v);
@@ -22566,6 +23213,21 @@ var init_NodeSlotEditor = __esm({
22566
23213
  const pattern = { type: nextType, ...nextProps };
22567
23214
  onChange(wasArray || value === void 0 ? [pattern] : pattern);
22568
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;
22569
23231
  return /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "xs", className: cn("w-full", className), children: [
22570
23232
  /* @__PURE__ */ jsxRuntime.jsx(
22571
23233
  exports.Select,
@@ -22577,7 +23239,16 @@ var init_NodeSlotEditor = __esm({
22577
23239
  ),
22578
23240
  type !== "" && /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "none", className: "pl-1", children: [
22579
23241
  /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "caption", color: "muted", children: "props" }),
22580
- /* @__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 : {}) })
22581
23252
  ] })
22582
23253
  ] });
22583
23254
  };
@@ -25160,303 +25831,6 @@ var init_FlipCard = __esm({
25160
25831
  exports.FlipCard.displayName = "FlipCard";
25161
25832
  }
25162
25833
  });
25163
- var ALL_CATEGORY; exports.GridPicker = void 0;
25164
- var init_GridPicker = __esm({
25165
- "components/core/molecules/GridPicker.tsx"() {
25166
- "use client";
25167
- init_cn();
25168
- init_Input();
25169
- init_Badge();
25170
- init_Stack();
25171
- ALL_CATEGORY = "__all__";
25172
- exports.GridPicker = ({
25173
- items,
25174
- value,
25175
- onChange,
25176
- categories,
25177
- searchPlaceholder,
25178
- renderThumbnail,
25179
- cellSize = 32,
25180
- className
25181
- }) => {
25182
- const [search, setSearch] = React74.useState("");
25183
- const [activeCategory, setActiveCategory] = React74.useState(ALL_CATEGORY);
25184
- const gridRef = React74.useRef(null);
25185
- const categoryChips = React74.useMemo(() => {
25186
- if (categories !== void 0) return categories;
25187
- const seen = [];
25188
- for (const item of items) {
25189
- if (!seen.includes(item.category)) seen.push(item.category);
25190
- }
25191
- return seen;
25192
- }, [categories, items]);
25193
- const filtered = React74.useMemo(() => {
25194
- const needle = search.trim().toLowerCase();
25195
- return items.filter((item) => {
25196
- const matchesCategory = activeCategory === ALL_CATEGORY || item.category === activeCategory;
25197
- const matchesSearch = needle === "" || item.label.toLowerCase().includes(needle);
25198
- return matchesCategory && matchesSearch;
25199
- });
25200
- }, [items, search, activeCategory]);
25201
- const select = React74.useCallback(
25202
- (item) => {
25203
- onChange(item.id);
25204
- },
25205
- [onChange]
25206
- );
25207
- const handleKeyDown = React74.useCallback(
25208
- (e, index) => {
25209
- const cells = gridRef.current?.querySelectorAll(
25210
- "[data-gridpicker-cell]"
25211
- );
25212
- if (cells === void 0 || cells.length === 0) return;
25213
- const columns = (() => {
25214
- const grid = gridRef.current;
25215
- if (grid === null) return 1;
25216
- const style = window.getComputedStyle(grid);
25217
- const cols = style.gridTemplateColumns.split(" ").filter(Boolean).length;
25218
- return cols > 0 ? cols : 1;
25219
- })();
25220
- let next = -1;
25221
- if (e.key === "ArrowRight") next = index + 1;
25222
- else if (e.key === "ArrowLeft") next = index - 1;
25223
- else if (e.key === "ArrowDown") next = index + columns;
25224
- else if (e.key === "ArrowUp") next = index - columns;
25225
- else if (e.key === "Enter" || e.key === " ") {
25226
- e.preventDefault();
25227
- select(filtered[index]);
25228
- return;
25229
- } else {
25230
- return;
25231
- }
25232
- e.preventDefault();
25233
- if (next >= 0 && next < cells.length) {
25234
- cells[next].focus();
25235
- }
25236
- },
25237
- [filtered, select]
25238
- );
25239
- return /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "sm", className: cn("w-full", className), children: [
25240
- /* @__PURE__ */ jsxRuntime.jsx(
25241
- exports.Input,
25242
- {
25243
- type: "search",
25244
- icon: "search",
25245
- value: search,
25246
- placeholder: searchPlaceholder,
25247
- clearable: true,
25248
- onClear: () => setSearch(""),
25249
- onChange: (e) => setSearch(e.target.value)
25250
- }
25251
- ),
25252
- categoryChips.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(exports.HStack, { gap: "xs", wrap: true, children: [
25253
- /* @__PURE__ */ jsxRuntime.jsx(
25254
- exports.Badge,
25255
- {
25256
- variant: activeCategory === ALL_CATEGORY ? "primary" : "neutral",
25257
- size: "sm",
25258
- role: "button",
25259
- tabIndex: 0,
25260
- "aria-pressed": activeCategory === ALL_CATEGORY,
25261
- className: "cursor-pointer",
25262
- onClick: () => setActiveCategory(ALL_CATEGORY),
25263
- onKeyDown: (e) => {
25264
- if (e.key === "Enter" || e.key === " ") {
25265
- e.preventDefault();
25266
- setActiveCategory(ALL_CATEGORY);
25267
- }
25268
- },
25269
- children: "All"
25270
- }
25271
- ),
25272
- categoryChips.map((category) => /* @__PURE__ */ jsxRuntime.jsx(
25273
- exports.Badge,
25274
- {
25275
- variant: activeCategory === category ? "primary" : "neutral",
25276
- size: "sm",
25277
- role: "button",
25278
- tabIndex: 0,
25279
- "aria-pressed": activeCategory === category,
25280
- className: "cursor-pointer",
25281
- onClick: () => setActiveCategory(category),
25282
- onKeyDown: (e) => {
25283
- if (e.key === "Enter" || e.key === " ") {
25284
- e.preventDefault();
25285
- setActiveCategory(category);
25286
- }
25287
- },
25288
- children: category
25289
- },
25290
- category
25291
- ))
25292
- ] }),
25293
- /* @__PURE__ */ jsxRuntime.jsx(
25294
- "div",
25295
- {
25296
- ref: gridRef,
25297
- role: "listbox",
25298
- className: "grid gap-1 overflow-y-auto max-h-64 p-1",
25299
- style: {
25300
- gridTemplateColumns: `repeat(auto-fill, minmax(${cellSize}px, 1fr))`
25301
- },
25302
- children: filtered.map((item, index) => {
25303
- const selected = item.id === value;
25304
- return /* @__PURE__ */ jsxRuntime.jsx(
25305
- "button",
25306
- {
25307
- type: "button",
25308
- role: "option",
25309
- "aria-selected": selected,
25310
- "aria-label": item.label,
25311
- title: item.label,
25312
- "data-gridpicker-cell": true,
25313
- tabIndex: selected || value === void 0 && index === 0 ? 0 : -1,
25314
- onClick: () => select(item),
25315
- onKeyDown: (e) => handleKeyDown(e, index),
25316
- className: cn(
25317
- "flex items-center justify-center rounded-sm",
25318
- "transition-colors hover:bg-muted",
25319
- "focus:outline-none focus:ring-1 focus:ring-ring",
25320
- selected && "bg-primary/10 ring-1 ring-primary"
25321
- ),
25322
- style: { width: cellSize, height: cellSize },
25323
- children: renderThumbnail(item)
25324
- },
25325
- item.id
25326
- );
25327
- })
25328
- }
25329
- )
25330
- ] });
25331
- };
25332
- exports.GridPicker.displayName = "GridPicker";
25333
- }
25334
- });
25335
- function iconForKind(kind) {
25336
- if (kind === "audio") return "music";
25337
- if (kind === "model") return "box";
25338
- return "file";
25339
- }
25340
- var THUMB_PX, IMAGE_KINDS; exports.AssetPicker = void 0;
25341
- var init_AssetPicker = __esm({
25342
- "components/core/molecules/AssetPicker.tsx"() {
25343
- "use client";
25344
- init_GridPicker();
25345
- init_Icon();
25346
- THUMB_PX = 32;
25347
- IMAGE_KINDS = /* @__PURE__ */ new Set([
25348
- "image",
25349
- "spritesheet",
25350
- "scene",
25351
- "portrait"
25352
- ]);
25353
- exports.AssetPicker = ({
25354
- assets,
25355
- value,
25356
- onChange,
25357
- className
25358
- }) => {
25359
- const byUrl = React74.useMemo(() => {
25360
- const map = /* @__PURE__ */ new Map();
25361
- for (const entry of assets) map.set(entry.url, entry);
25362
- return map;
25363
- }, [assets]);
25364
- const items = React74.useMemo(
25365
- () => assets.map((entry) => ({
25366
- id: entry.url,
25367
- label: entry.name,
25368
- category: entry.category
25369
- })),
25370
- [assets]
25371
- );
25372
- const categories = React74.useMemo(() => {
25373
- const seen = [];
25374
- for (const entry of assets) {
25375
- if (!seen.includes(entry.category)) seen.push(entry.category);
25376
- }
25377
- return seen;
25378
- }, [assets]);
25379
- const renderThumbnail = React74.useCallback(
25380
- (item) => {
25381
- const entry = byUrl.get(item.id);
25382
- if (entry === void 0) return null;
25383
- if (IMAGE_KINDS.has(entry.kind)) {
25384
- return /* @__PURE__ */ jsxRuntime.jsx(
25385
- "img",
25386
- {
25387
- src: entry.thumbnailUrl ?? entry.url,
25388
- alt: entry.name,
25389
- loading: "lazy",
25390
- width: THUMB_PX,
25391
- height: THUMB_PX,
25392
- style: { width: THUMB_PX, height: THUMB_PX, objectFit: "cover" }
25393
- }
25394
- );
25395
- }
25396
- return /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: iconForKind(entry.kind), size: "sm" });
25397
- },
25398
- [byUrl]
25399
- );
25400
- return /* @__PURE__ */ jsxRuntime.jsx(
25401
- exports.GridPicker,
25402
- {
25403
- items,
25404
- value,
25405
- onChange,
25406
- categories,
25407
- renderThumbnail,
25408
- cellSize: THUMB_PX,
25409
- className
25410
- }
25411
- );
25412
- };
25413
- exports.AssetPicker.displayName = "AssetPicker";
25414
- }
25415
- });
25416
- function pascalToKebab(name) {
25417
- return name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([A-Z])([A-Z][a-z])/g, "$1-$2").toLowerCase();
25418
- }
25419
- function kebabToPascal3(name) {
25420
- return name.split("-").map((part) => /^\d+$/.test(part) ? part : part.charAt(0).toUpperCase() + part.slice(1)).join("");
25421
- }
25422
- var ICON_ITEMS; exports.IconPicker = void 0;
25423
- var init_IconPicker = __esm({
25424
- "components/core/molecules/IconPicker.tsx"() {
25425
- "use client";
25426
- init_Icon();
25427
- init_GridPicker();
25428
- ICON_ITEMS = (() => {
25429
- const items = [];
25430
- for (const [exportName, candidate] of Object.entries(LucideIcons2__namespace)) {
25431
- if (!/^[A-Z]/.test(exportName)) continue;
25432
- if (exportName.endsWith("Icon")) continue;
25433
- if (exportName.startsWith("Lucide")) continue;
25434
- const isComponent = candidate !== null && (typeof candidate === "object" || typeof candidate === "function") && "$$typeof" in candidate;
25435
- if (!isComponent) continue;
25436
- const kebab = pascalToKebab(exportName);
25437
- if (kebabToPascal3(kebab) !== exportName) continue;
25438
- items.push({ id: kebab, label: kebab, category: "icons" });
25439
- }
25440
- return items;
25441
- })();
25442
- exports.IconPicker = ({ value, onChange, className }) => {
25443
- const items = React74.useMemo(() => ICON_ITEMS, []);
25444
- return /* @__PURE__ */ jsxRuntime.jsx(
25445
- exports.GridPicker,
25446
- {
25447
- items,
25448
- value,
25449
- onChange,
25450
- searchPlaceholder: "Search icons\u2026",
25451
- renderThumbnail: (it) => /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: it.id }),
25452
- cellSize: 32,
25453
- className
25454
- }
25455
- );
25456
- };
25457
- exports.IconPicker.displayName = "IconPicker";
25458
- }
25459
- });
25460
25834
  function toISODate(d) {
25461
25835
  return d.toISOString().slice(0, 10);
25462
25836
  }
@@ -35147,286 +35521,6 @@ var init_PageHeader = __esm({
35147
35521
  exports.PageHeader.displayName = "PageHeader";
35148
35522
  }
35149
35523
  });
35150
- exports.FormSection = void 0; exports.FormLayout = void 0; exports.FormActions = void 0;
35151
- var init_FormSection = __esm({
35152
- "components/core/molecules/FormSection.tsx"() {
35153
- "use client";
35154
- init_cn();
35155
- init_atoms2();
35156
- init_Box();
35157
- init_Typography();
35158
- init_Button();
35159
- init_Stack();
35160
- init_Icon();
35161
- init_useEventBus();
35162
- exports.FormSection = ({
35163
- title,
35164
- description,
35165
- children,
35166
- collapsible = false,
35167
- defaultCollapsed = false,
35168
- card = false,
35169
- columns = 1,
35170
- className
35171
- }) => {
35172
- const [collapsed, setCollapsed] = React74__namespace.default.useState(defaultCollapsed);
35173
- const { t } = hooks.useTranslate();
35174
- const eventBus = useEventBus();
35175
- const gridClass = {
35176
- 1: "grid-cols-1",
35177
- 2: "grid-cols-1 md:grid-cols-2",
35178
- 3: "grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
35179
- }[columns];
35180
- React74__namespace.default.useCallback(() => {
35181
- if (collapsible) {
35182
- setCollapsed((prev) => !prev);
35183
- eventBus.emit("UI:TOGGLE_COLLAPSE", { collapsed: !collapsed });
35184
- }
35185
- }, [collapsible, collapsed, eventBus]);
35186
- const content = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
35187
- (title || description) && /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "xs", className: "mb-4", children: [
35188
- title && /* @__PURE__ */ jsxRuntime.jsxs(
35189
- exports.HStack,
35190
- {
35191
- justify: "between",
35192
- align: "center",
35193
- className: cn(collapsible && "cursor-pointer"),
35194
- action: collapsible ? "TOGGLE_COLLAPSE" : void 0,
35195
- children: [
35196
- /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "h3", weight: "semibold", children: title }),
35197
- collapsible && /* @__PURE__ */ jsxRuntime.jsx(
35198
- exports.Button,
35199
- {
35200
- variant: "ghost",
35201
- size: "sm",
35202
- action: "TOGGLE_COLLAPSE",
35203
- children: /* @__PURE__ */ jsxRuntime.jsx(
35204
- exports.Icon,
35205
- {
35206
- icon: LucideIcons2.ChevronDown,
35207
- size: "sm",
35208
- className: cn(
35209
- "text-muted-foreground transition-transform",
35210
- collapsed && "rotate-180"
35211
- )
35212
- }
35213
- )
35214
- }
35215
- )
35216
- ]
35217
- }
35218
- ),
35219
- description && /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "small", color: "secondary", children: description })
35220
- ] }),
35221
- (!collapsible || !collapsed) && /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: cn("grid gap-4", gridClass), children })
35222
- ] });
35223
- if (card) {
35224
- return /* @__PURE__ */ jsxRuntime.jsx(exports.Card, { className: cn("p-6", className), children: content });
35225
- }
35226
- return /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className, children: content });
35227
- };
35228
- exports.FormSection.displayName = "FormSection";
35229
- exports.FormLayout = ({
35230
- children,
35231
- dividers = true,
35232
- className
35233
- }) => {
35234
- return /* @__PURE__ */ jsxRuntime.jsx(
35235
- exports.VStack,
35236
- {
35237
- gap: "lg",
35238
- className: cn(
35239
- dividers && "[&>*+*]:pt-8 [&>*+*]:border-t [&>*+*]:border-border",
35240
- className
35241
- ),
35242
- children
35243
- }
35244
- );
35245
- };
35246
- exports.FormLayout.displayName = "FormLayout";
35247
- exports.FormActions = ({
35248
- children,
35249
- sticky = false,
35250
- align = "right",
35251
- className
35252
- }) => {
35253
- const alignClass2 = {
35254
- left: "justify-start",
35255
- right: "justify-end",
35256
- between: "justify-between",
35257
- center: "justify-center"
35258
- }[align];
35259
- return /* @__PURE__ */ jsxRuntime.jsx(
35260
- exports.HStack,
35261
- {
35262
- gap: "sm",
35263
- align: "center",
35264
- className: cn(
35265
- "pt-6 border-t border-border",
35266
- alignClass2,
35267
- sticky && "sticky bottom-0 bg-card py-4 -mx-6 px-6 shadow-[0_-4px_6px_-1px_rgb(0,0,0,0.05)]",
35268
- className
35269
- ),
35270
- children
35271
- }
35272
- );
35273
- };
35274
- exports.FormActions.displayName = "FormActions";
35275
- }
35276
- });
35277
- function currentValue(decl, override) {
35278
- return override !== void 0 ? override : decl.default;
35279
- }
35280
- function TextLikeControl({
35281
- field,
35282
- numeric,
35283
- value,
35284
- onCommit
35285
- }) {
35286
- const initial = value === void 0 || value === null ? "" : String(value);
35287
- const [draft, setDraft] = React74__namespace.default.useState(initial);
35288
- React74__namespace.default.useEffect(() => setDraft(initial), [initial]);
35289
- const commit = () => {
35290
- if (numeric) {
35291
- const n = draft.trim() === "" ? 0 : Number(draft);
35292
- onCommit(field, Number.isNaN(n) ? 0 : n);
35293
- } else {
35294
- onCommit(field, draft);
35295
- }
35296
- };
35297
- return /* @__PURE__ */ jsxRuntime.jsx(
35298
- exports.Input,
35299
- {
35300
- inputType: numeric ? "number" : "text",
35301
- value: draft,
35302
- onChange: (e) => setDraft(e.target.value),
35303
- onBlur: commit,
35304
- onKeyDown: (e) => {
35305
- if (e.key === "Enter") commit();
35306
- }
35307
- }
35308
- );
35309
- }
35310
- function isTraitConfigObject(v) {
35311
- return v !== null && v !== void 0 && typeof v === "object" && !Array.isArray(v);
35312
- }
35313
- function FieldControl({
35314
- name,
35315
- decl,
35316
- value,
35317
- onChange,
35318
- assets
35319
- }) {
35320
- let control;
35321
- const stringValue = typeof value === "string" ? value : void 0;
35322
- const effectiveValue = value ?? decl.default;
35323
- if (decl.type === "icon") {
35324
- control = /* @__PURE__ */ jsxRuntime.jsx(exports.IconPicker, { value: stringValue, onChange: (icon) => onChange(name, icon) });
35325
- } else if (decl.type === "asset") {
35326
- control = /* @__PURE__ */ jsxRuntime.jsx(
35327
- exports.AssetPicker,
35328
- {
35329
- assets: assets ?? [],
35330
- value: stringValue,
35331
- onChange: (url) => onChange(name, url)
35332
- }
35333
- );
35334
- } else if (decl.type === "boolean") {
35335
- control = /* @__PURE__ */ jsxRuntime.jsx(exports.Switch, { checked: value === true, onChange: (c) => onChange(name, c) });
35336
- } else if (decl.type === "string" && decl.values !== void 0 && decl.values.length > 0) {
35337
- control = /* @__PURE__ */ jsxRuntime.jsx(
35338
- exports.Select,
35339
- {
35340
- options: decl.values.map((v) => ({ value: v, label: v })),
35341
- value: typeof value === "string" ? value : "",
35342
- onChange: (e) => onChange(name, e.target.value)
35343
- }
35344
- );
35345
- } else if (decl.type === "number") {
35346
- control = /* @__PURE__ */ jsxRuntime.jsx(TextLikeControl, { field: name, numeric: true, value, onCommit: onChange });
35347
- } else if (decl.type === "string") {
35348
- control = /* @__PURE__ */ jsxRuntime.jsx(TextLikeControl, { field: name, numeric: false, value, onCommit: onChange });
35349
- } else if (decl.type === "node") {
35350
- control = /* @__PURE__ */ jsxRuntime.jsx(exports.NodeSlotEditor, { value: effectiveValue, onChange: (next) => onChange(name, next) });
35351
- } else if (decl.type.startsWith("[") || Array.isArray(effectiveValue)) {
35352
- const arr = Array.isArray(effectiveValue) ? effectiveValue : [];
35353
- control = /* @__PURE__ */ jsxRuntime.jsx(exports.JsonTreeEditor, { value: arr, onChange: (next) => onChange(name, next) });
35354
- } else if (decl.type === "object" || decl.type.startsWith("Map ") || !SCALAR_TYPES.has(decl.type) && isTraitConfigObject(effectiveValue)) {
35355
- const obj = isTraitConfigObject(effectiveValue) ? effectiveValue : {};
35356
- control = /* @__PURE__ */ jsxRuntime.jsx(exports.JsonTreeEditor, { value: obj, onChange: (next) => onChange(name, next) });
35357
- } else {
35358
- control = /* @__PURE__ */ jsxRuntime.jsxs(exports.Typography, { variant: "caption", color: "muted", children: [
35359
- decl.type,
35360
- " \u2014 edit in source"
35361
- ] });
35362
- }
35363
- return /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "xs", children: [
35364
- /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "label", children: decl.label ?? name }),
35365
- control,
35366
- decl.description !== void 0 && decl.description !== "" && /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "caption", color: "muted", children: decl.description })
35367
- ] });
35368
- }
35369
- var TIER_ORDER, SCALAR_TYPES; exports.PropertyInspector = void 0;
35370
- var init_PropertyInspector = __esm({
35371
- "components/core/molecules/PropertyInspector.tsx"() {
35372
- "use client";
35373
- init_cn();
35374
- init_Stack();
35375
- init_Typography();
35376
- init_Button();
35377
- init_Switch();
35378
- init_Select();
35379
- init_Input();
35380
- init_FormSection();
35381
- init_IconPicker();
35382
- init_AssetPicker();
35383
- init_JsonTreeEditor();
35384
- init_NodeSlotEditor();
35385
- TIER_ORDER = ["presentation", "domain", "policy", "infra", "internal"];
35386
- SCALAR_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "icon", "asset"]);
35387
- exports.PropertyInspector = ({
35388
- config,
35389
- values,
35390
- onChange,
35391
- onReset,
35392
- title,
35393
- className,
35394
- assets
35395
- }) => {
35396
- const fields = Object.entries(config);
35397
- const byTier = /* @__PURE__ */ new Map();
35398
- for (const [name, decl] of fields) {
35399
- const tier = decl.tier ?? "presentation";
35400
- const arr = byTier.get(tier) ?? [];
35401
- arr.push([name, decl]);
35402
- byTier.set(tier, arr);
35403
- }
35404
- const tiers = [...byTier.keys()].sort((a, b) => {
35405
- const ia = TIER_ORDER.indexOf(a);
35406
- const ib = TIER_ORDER.indexOf(b);
35407
- return (ia === -1 ? 99 : ia) - (ib === -1 ? 99 : ib);
35408
- });
35409
- return /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "sm", className: cn("w-full", className), children: [
35410
- /* @__PURE__ */ jsxRuntime.jsxs(exports.HStack, { justify: "between", align: "center", children: [
35411
- /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "caption", weight: "bold", children: title ?? "Config" }),
35412
- onReset !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(exports.Button, { variant: "ghost", size: "sm", icon: "rotate-ccw", label: "Reset", onClick: onReset })
35413
- ] }),
35414
- fields.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "caption", color: "muted", children: "No configurable properties." }),
35415
- 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(
35416
- FieldControl,
35417
- {
35418
- name,
35419
- decl,
35420
- value: currentValue(decl, values?.[name]),
35421
- onChange,
35422
- assets
35423
- },
35424
- name
35425
- )) }) }, tier))
35426
- ] });
35427
- };
35428
- }
35429
- });
35430
35524
  var lookStyles8; exports.Header = void 0;
35431
35525
  var init_Header = __esm({
35432
35526
  "components/core/molecules/Header.tsx"() {
@@ -36782,7 +36876,6 @@ var init_DocumentViewer = __esm({
36782
36876
  showPrint = false,
36783
36877
  actions,
36784
36878
  documents,
36785
- entity,
36786
36879
  isLoading = false,
36787
36880
  error,
36788
36881
  className
@@ -43952,7 +44045,7 @@ function TraitSlot({
43952
44045
  size = "md",
43953
44046
  showTooltip = true,
43954
44047
  categoryColors,
43955
- tooltipFrameUrl,
44048
+ tooltipFrameUrl = "",
43956
44049
  className,
43957
44050
  feedback,
43958
44051
  onItemDrop,