@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.
@@ -45,7 +45,7 @@ import { useSortable, arrayMove, sortableKeyboardCoordinates, SortableContext, r
45
45
  import { CSS } from '@dnd-kit/utilities';
46
46
  import { useNodeId, ReactFlowProvider, Handle, Position } from '@xyflow/react';
47
47
  import { useUISlots } from '@almadar/ui/context';
48
- import { getPatternDefinition, getComponentForPattern as getComponentForPattern$1 } from '@almadar/patterns';
48
+ import { getPatternDefinition as getPatternDefinition$1, getComponentForPattern as getComponentForPattern$1 } from '@almadar/patterns';
49
49
  import { context, Canvas } from '@react-three/fiber';
50
50
 
51
51
  var __defProp = Object.defineProperty;
@@ -7081,11 +7081,15 @@ var init_Skeleton = __esm({
7081
7081
  function getKnownPatterns() {
7082
7082
  return Object.keys(componentMapping);
7083
7083
  }
7084
- var componentMapping;
7084
+ function getPatternDefinition(type) {
7085
+ return patternRegistry[type];
7086
+ }
7087
+ var componentMapping, patternRegistry;
7085
7088
  var init_pattern_resolver = __esm({
7086
7089
  "renderer/pattern-resolver.ts"() {
7087
7090
  createLogger("almadar:ui:pattern-resolver");
7088
7091
  componentMapping = {};
7092
+ patternRegistry = {};
7089
7093
  }
7090
7094
  });
7091
7095
 
@@ -20710,7 +20714,84 @@ var init_DashboardLayout = __esm({
20710
20714
  NavLinkBottom.displayName = "NavLinkBottom";
20711
20715
  }
20712
20716
  });
20713
- var Menu;
20717
+ function computeMenuStyle(position, triggerRect) {
20718
+ const isTop = position.startsWith("top");
20719
+ const isRight = position.endsWith("right") || position.endsWith("end");
20720
+ if (isTop) {
20721
+ return {
20722
+ top: triggerRect.top - MENU_GAP,
20723
+ transform: "translateY(-100%)",
20724
+ ...isRight ? { right: window.innerWidth - triggerRect.right } : { left: triggerRect.left }
20725
+ };
20726
+ }
20727
+ return {
20728
+ top: triggerRect.bottom + MENU_GAP,
20729
+ ...isRight ? { right: window.innerWidth - triggerRect.right } : { left: triggerRect.left }
20730
+ };
20731
+ }
20732
+ function SubMenu({
20733
+ items,
20734
+ itemRef,
20735
+ direction,
20736
+ eventBus
20737
+ }) {
20738
+ const [rect, setRect] = useState(null);
20739
+ useEffect(() => {
20740
+ if (itemRef) {
20741
+ setRect(itemRef.getBoundingClientRect());
20742
+ }
20743
+ }, [itemRef]);
20744
+ if (!rect) return null;
20745
+ const isRtl = direction === "rtl";
20746
+ const style = {
20747
+ top: rect.top,
20748
+ ...isRtl ? { right: window.innerWidth - rect.left } : { left: rect.right }
20749
+ };
20750
+ const panel = /* @__PURE__ */ jsx(
20751
+ "div",
20752
+ {
20753
+ className: cn("fixed z-50", menuContainerStyles),
20754
+ style,
20755
+ children: items.map((item, index) => {
20756
+ const isDivider = item.id === "divider" || item.label === "divider";
20757
+ const itemId = item.id ?? `item-${item.label.toLowerCase().replace(/\s+/g, "-")}-${index}`;
20758
+ const isDanger = item.variant === "danger";
20759
+ if (isDivider) {
20760
+ return /* @__PURE__ */ jsx(Divider, { className: "my-1" }, `divider-${index}`);
20761
+ }
20762
+ return /* @__PURE__ */ jsxs(
20763
+ Box,
20764
+ {
20765
+ as: "button",
20766
+ onClick: () => {
20767
+ if (item.disabled) return;
20768
+ if (item.event) eventBus.emit(`UI:${item.event}`, { itemId, label: item.label });
20769
+ item.onClick?.();
20770
+ },
20771
+ "aria-disabled": item.disabled || void 0,
20772
+ "data-testid": item.event ? `action-${item.event}` : void 0,
20773
+ className: cn(
20774
+ "w-full flex items-center gap-3 px-4 py-2 text-start",
20775
+ "text-sm transition-colors",
20776
+ "hover:bg-muted focus:outline-none focus:bg-muted",
20777
+ "disabled:opacity-50 disabled:cursor-not-allowed",
20778
+ item.disabled && "cursor-not-allowed",
20779
+ isDanger && "text-error hover:bg-error/10"
20780
+ ),
20781
+ children: [
20782
+ item.icon && (typeof item.icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: item.icon, size: "sm", className: "flex-shrink-0" }) : /* @__PURE__ */ jsx(Icon, { icon: item.icon, size: "sm", className: "flex-shrink-0" })),
20783
+ /* @__PURE__ */ jsx(Typography, { variant: "small", className: cn("flex-1", isDanger && "text-red-600"), children: item.label }),
20784
+ item.badge !== void 0 && /* @__PURE__ */ jsx("span", { className: "ml-auto text-xs font-medium", children: item.badge })
20785
+ ]
20786
+ },
20787
+ itemId
20788
+ );
20789
+ })
20790
+ }
20791
+ );
20792
+ return typeof document !== "undefined" ? createPortal(panel, document.body) : panel;
20793
+ }
20794
+ var MENU_GAP, menuContainerStyles, Menu;
20714
20795
  var init_Menu = __esm({
20715
20796
  "components/core/molecules/Menu.tsx"() {
20716
20797
  "use client";
@@ -20721,6 +20802,14 @@ var init_Menu = __esm({
20721
20802
  init_Badge();
20722
20803
  init_cn();
20723
20804
  init_useEventBus();
20805
+ MENU_GAP = 4;
20806
+ menuContainerStyles = cn(
20807
+ "bg-card",
20808
+ "border-[length:var(--border-width)] border-border",
20809
+ "shadow-elevation-popover",
20810
+ "rounded-sm",
20811
+ "min-w-0 sm:min-w-[200px] max-w-[calc(100vw-1rem)] py-1"
20812
+ );
20724
20813
  Menu = ({
20725
20814
  trigger,
20726
20815
  items,
@@ -20728,9 +20817,10 @@ var init_Menu = __esm({
20728
20817
  className
20729
20818
  }) => {
20730
20819
  const eventBus = useEventBus();
20731
- const { t, direction } = useTranslate();
20820
+ const { direction } = useTranslate();
20732
20821
  const [isOpen, setIsOpen] = useState(false);
20733
20822
  const [activeSubMenu, setActiveSubMenu] = useState(null);
20823
+ const [activeSubMenuRef, setActiveSubMenuRef] = useState(null);
20734
20824
  const [triggerRect, setTriggerRect] = useState(null);
20735
20825
  const triggerRef = useRef(null);
20736
20826
  const menuRef = useRef(null);
@@ -20745,13 +20835,14 @@ var init_Menu = __esm({
20745
20835
  }
20746
20836
  setIsOpen(!isOpen);
20747
20837
  setActiveSubMenu(null);
20838
+ setActiveSubMenuRef(null);
20748
20839
  };
20749
- const handleItemClick = (item) => {
20840
+ const handleItemClick = (item, itemId) => {
20750
20841
  if (item.disabled) return;
20751
20842
  if (item.subMenu && item.subMenu.length > 0) {
20752
- setActiveSubMenu(item.id ?? null);
20843
+ setActiveSubMenu(itemId);
20753
20844
  } else {
20754
- if (item.event) eventBus.emit(`UI:${item.event}`, { itemId: item.id, label: item.label });
20845
+ if (item.event) eventBus.emit(`UI:${item.event}`, { itemId, label: item.label });
20755
20846
  item.onClick?.();
20756
20847
  setIsOpen(false);
20757
20848
  }
@@ -20766,22 +20857,12 @@ var init_Menu = __esm({
20766
20857
  if (isOpen && menuRef.current && !menuRef.current.contains(e.target) && triggerRef.current && !triggerRef.current.contains(e.target)) {
20767
20858
  setIsOpen(false);
20768
20859
  setActiveSubMenu(null);
20860
+ setActiveSubMenuRef(null);
20769
20861
  }
20770
20862
  };
20771
20863
  document.addEventListener("mousedown", handleClickOutside);
20772
20864
  return () => document.removeEventListener("mousedown", handleClickOutside);
20773
20865
  }, [isOpen]);
20774
- const positionClasses = {
20775
- "top-left": "bottom-full left-0 mb-2",
20776
- "top-right": "bottom-full right-0 mb-2",
20777
- "bottom-left": "top-full left-0 mt-2",
20778
- "bottom-right": "top-full right-0 mt-2",
20779
- // Aliases for pattern compatibility
20780
- "top-start": "bottom-full left-0 mb-2",
20781
- "top-end": "bottom-full right-0 mb-2",
20782
- "bottom-start": "top-full left-0 mt-2",
20783
- "bottom-end": "top-full right-0 mt-2"
20784
- };
20785
20866
  const rtlMirror = {
20786
20867
  "top-left": "top-right",
20787
20868
  "top-right": "top-left",
@@ -20793,7 +20874,6 @@ var init_Menu = __esm({
20793
20874
  "bottom-end": "bottom-start"
20794
20875
  };
20795
20876
  const effectivePosition = direction === "rtl" ? rtlMirror[position] ?? position : position;
20796
- const subMenuSideClass = direction === "rtl" ? "right-full mr-2" : "left-full ml-2";
20797
20877
  const triggerChild = React74__default.isValidElement(trigger) ? trigger : /* @__PURE__ */ jsx(Typography, { variant: "small", as: "span", children: trigger });
20798
20878
  const triggerElement = React74__default.cloneElement(
20799
20879
  triggerChild,
@@ -20802,94 +20882,83 @@ var init_Menu = __esm({
20802
20882
  onClick: handleToggle
20803
20883
  }
20804
20884
  );
20805
- const menuContainerStyles = cn(
20806
- "bg-card",
20807
- "border-[length:var(--border-width)] border-border",
20808
- "shadow-elevation-popover",
20809
- "rounded-sm",
20810
- "min-w-0 sm:min-w-[200px] max-w-[calc(100vw-1rem)] py-1"
20811
- );
20812
- const renderMenuItem = (item, hasSubMenu, index) => {
20885
+ const renderMenuItems = (menuItems) => menuItems.map((item, index) => {
20886
+ const isDivider = item.id === "divider" || item.label === "divider";
20813
20887
  const itemId = item.id ?? `item-${item.label.toLowerCase().replace(/\s+/g, "-")}-${index}`;
20888
+ const hasSubMenu = !!(item.subMenu && item.subMenu.length > 0);
20814
20889
  const isDanger = item.variant === "danger";
20815
- return /* @__PURE__ */ jsx(
20816
- Box,
20817
- {
20818
- as: "button",
20819
- onClick: () => !item.disabled && handleItemClick({ ...item, id: itemId }),
20820
- "aria-disabled": item.disabled || void 0,
20821
- onMouseEnter: () => hasSubMenu && setActiveSubMenu(itemId),
20822
- "data-testid": item.event ? `action-${item.event}` : void 0,
20823
- className: cn(
20824
- "w-full flex items-center justify-between gap-3 px-4 py-2 text-start",
20825
- "text-sm transition-colors",
20826
- "hover:bg-muted",
20827
- "focus:outline-none focus:bg-muted",
20828
- "disabled:opacity-50 disabled:cursor-not-allowed",
20829
- item.disabled && "cursor-not-allowed",
20830
- isDanger && "text-error hover:bg-error/10"
20831
- ),
20832
- children: /* @__PURE__ */ jsxs(Box, { className: "flex items-center gap-3 flex-1 min-w-0", children: [
20833
- item.icon && (typeof item.icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: item.icon, size: "sm", className: "flex-shrink-0" }) : /* @__PURE__ */ jsx(Icon, { icon: item.icon, size: "sm", className: "flex-shrink-0" })),
20834
- /* @__PURE__ */ jsx(
20835
- Typography,
20836
- {
20837
- variant: "small",
20838
- className: cn("flex-1", isDanger && "text-red-600"),
20839
- children: item.label
20890
+ if (isDivider) {
20891
+ return /* @__PURE__ */ jsx(Divider, { className: "my-1" }, `divider-${index}`);
20892
+ }
20893
+ return /* @__PURE__ */ jsxs(Box, { children: [
20894
+ /* @__PURE__ */ jsx(
20895
+ Box,
20896
+ {
20897
+ as: "button",
20898
+ onClick: () => handleItemClick({ ...item, id: itemId }, itemId),
20899
+ "aria-disabled": item.disabled || void 0,
20900
+ onMouseEnter: (e) => {
20901
+ if (hasSubMenu) {
20902
+ setActiveSubMenu(itemId);
20903
+ setActiveSubMenuRef(e.currentTarget);
20840
20904
  }
20905
+ },
20906
+ "data-testid": item.event ? `action-${item.event}` : void 0,
20907
+ className: cn(
20908
+ "w-full flex items-center justify-between gap-3 px-4 py-2 text-start",
20909
+ "text-sm transition-colors",
20910
+ "hover:bg-muted",
20911
+ "focus:outline-none focus:bg-muted",
20912
+ "disabled:opacity-50 disabled:cursor-not-allowed",
20913
+ item.disabled && "cursor-not-allowed",
20914
+ isDanger && "text-error hover:bg-error/10"
20841
20915
  ),
20842
- item.badge !== void 0 && /* @__PURE__ */ jsx(Badge, { variant: "default", size: "sm", children: item.badge }),
20843
- hasSubMenu && /* @__PURE__ */ jsx(Icon, { name: direction === "rtl" ? "chevron-left" : "chevron-right", size: "sm", className: "flex-shrink-0" })
20844
- ] })
20845
- },
20846
- itemId
20847
- );
20848
- };
20849
- const renderMenuItems = (menuItems) => {
20850
- return menuItems.map((item, index) => {
20851
- const hasSubMenu = item.subMenu && item.subMenu.length > 0;
20852
- const isDivider = item.id === "divider" || item.label === "divider";
20853
- const itemId = item.id ?? `item-${item.label.toLowerCase().replace(/\s+/g, "-")}-${index}`;
20854
- if (isDivider) {
20855
- return /* @__PURE__ */ jsx(Divider, { className: "my-1" }, `divider-${index}`);
20856
- }
20857
- return /* @__PURE__ */ jsxs(Box, { children: [
20858
- renderMenuItem(item, !!hasSubMenu, index),
20859
- hasSubMenu && activeSubMenu === itemId && item.subMenu && /* @__PURE__ */ jsx(
20860
- Box,
20861
- {
20862
- className: cn(
20863
- "absolute top-0 z-50",
20864
- subMenuSideClass,
20865
- menuContainerStyles
20916
+ children: /* @__PURE__ */ jsxs(Box, { className: "flex items-center gap-3 flex-1 min-w-0", children: [
20917
+ item.icon && (typeof item.icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: item.icon, size: "sm", className: "flex-shrink-0" }) : /* @__PURE__ */ jsx(Icon, { icon: item.icon, size: "sm", className: "flex-shrink-0" })),
20918
+ /* @__PURE__ */ jsx(
20919
+ Typography,
20920
+ {
20921
+ variant: "small",
20922
+ className: cn("flex-1", isDanger && "text-red-600"),
20923
+ children: item.label
20924
+ }
20866
20925
  ),
20867
- children: renderMenuItems(item.subMenu)
20868
- }
20869
- )
20870
- ] }, itemId);
20871
- });
20872
- };
20873
- return /* @__PURE__ */ jsxs(Box, { className: "relative", children: [
20926
+ item.badge !== void 0 && /* @__PURE__ */ jsx(Badge, { variant: "default", size: "sm", children: item.badge }),
20927
+ hasSubMenu && /* @__PURE__ */ jsx(
20928
+ Icon,
20929
+ {
20930
+ name: direction === "rtl" ? "chevron-left" : "chevron-right",
20931
+ size: "sm",
20932
+ className: "flex-shrink-0"
20933
+ }
20934
+ )
20935
+ ] })
20936
+ }
20937
+ ),
20938
+ hasSubMenu && activeSubMenu === itemId && item.subMenu && /* @__PURE__ */ jsx(
20939
+ SubMenu,
20940
+ {
20941
+ items: item.subMenu,
20942
+ itemRef: activeSubMenuRef,
20943
+ direction,
20944
+ eventBus
20945
+ }
20946
+ )
20947
+ ] }, itemId);
20948
+ });
20949
+ const panel = isOpen && triggerRect ? /* @__PURE__ */ jsx(
20950
+ "div",
20951
+ {
20952
+ ref: menuRef,
20953
+ className: cn("fixed z-50", menuContainerStyles, className),
20954
+ style: computeMenuStyle(effectivePosition, triggerRect),
20955
+ role: "menu",
20956
+ children: renderMenuItems(items)
20957
+ }
20958
+ ) : null;
20959
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
20874
20960
  triggerElement,
20875
- isOpen && triggerRect && /* @__PURE__ */ jsx(
20876
- Box,
20877
- {
20878
- ref: menuRef,
20879
- className: cn(
20880
- "absolute z-50",
20881
- menuContainerStyles,
20882
- positionClasses[effectivePosition],
20883
- className
20884
- ),
20885
- style: {
20886
- left: effectivePosition.includes("left") ? 0 : "auto",
20887
- right: effectivePosition.includes("right") ? 0 : "auto"
20888
- },
20889
- role: "menu",
20890
- children: renderMenuItems(items)
20891
- }
20892
- )
20961
+ panel && typeof document !== "undefined" ? createPortal(panel, document.body) : panel
20893
20962
  ] });
20894
20963
  };
20895
20964
  Menu.displayName = "Menu";
@@ -22476,6 +22545,583 @@ var init_JsonTreeEditor = __esm({
22476
22545
  };
22477
22546
  }
22478
22547
  });
22548
+ var FormSection, FormLayout, FormActions;
22549
+ var init_FormSection = __esm({
22550
+ "components/core/molecules/FormSection.tsx"() {
22551
+ "use client";
22552
+ init_cn();
22553
+ init_atoms2();
22554
+ init_Box();
22555
+ init_Typography();
22556
+ init_Button();
22557
+ init_Stack();
22558
+ init_Icon();
22559
+ init_useEventBus();
22560
+ FormSection = ({
22561
+ title,
22562
+ description,
22563
+ children,
22564
+ collapsible = false,
22565
+ defaultCollapsed = false,
22566
+ card = false,
22567
+ columns = 1,
22568
+ className
22569
+ }) => {
22570
+ const [collapsed, setCollapsed] = React74__default.useState(defaultCollapsed);
22571
+ const { t } = useTranslate();
22572
+ const eventBus = useEventBus();
22573
+ const gridClass = {
22574
+ 1: "grid-cols-1",
22575
+ 2: "grid-cols-1 md:grid-cols-2",
22576
+ 3: "grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
22577
+ }[columns];
22578
+ React74__default.useCallback(() => {
22579
+ if (collapsible) {
22580
+ setCollapsed((prev) => !prev);
22581
+ eventBus.emit("UI:TOGGLE_COLLAPSE", { collapsed: !collapsed });
22582
+ }
22583
+ }, [collapsible, collapsed, eventBus]);
22584
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
22585
+ (title || description) && /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: "mb-4", children: [
22586
+ title && /* @__PURE__ */ jsxs(
22587
+ HStack,
22588
+ {
22589
+ justify: "between",
22590
+ align: "center",
22591
+ className: cn(collapsible && "cursor-pointer"),
22592
+ action: collapsible ? "TOGGLE_COLLAPSE" : void 0,
22593
+ children: [
22594
+ /* @__PURE__ */ jsx(Typography, { variant: "h3", weight: "semibold", children: title }),
22595
+ collapsible && /* @__PURE__ */ jsx(
22596
+ Button,
22597
+ {
22598
+ variant: "ghost",
22599
+ size: "sm",
22600
+ action: "TOGGLE_COLLAPSE",
22601
+ children: /* @__PURE__ */ jsx(
22602
+ Icon,
22603
+ {
22604
+ icon: ChevronDown,
22605
+ size: "sm",
22606
+ className: cn(
22607
+ "text-muted-foreground transition-transform",
22608
+ collapsed && "rotate-180"
22609
+ )
22610
+ }
22611
+ )
22612
+ }
22613
+ )
22614
+ ]
22615
+ }
22616
+ ),
22617
+ description && /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: description })
22618
+ ] }),
22619
+ (!collapsible || !collapsed) && /* @__PURE__ */ jsx(Box, { className: cn("grid gap-4", gridClass), children })
22620
+ ] });
22621
+ if (card) {
22622
+ return /* @__PURE__ */ jsx(Card, { className: cn("p-6", className), children: content });
22623
+ }
22624
+ return /* @__PURE__ */ jsx(Box, { className, children: content });
22625
+ };
22626
+ FormSection.displayName = "FormSection";
22627
+ FormLayout = ({
22628
+ children,
22629
+ dividers = true,
22630
+ className
22631
+ }) => {
22632
+ return /* @__PURE__ */ jsx(
22633
+ VStack,
22634
+ {
22635
+ gap: "lg",
22636
+ className: cn(
22637
+ dividers && "[&>*+*]:pt-8 [&>*+*]:border-t [&>*+*]:border-border",
22638
+ className
22639
+ ),
22640
+ children
22641
+ }
22642
+ );
22643
+ };
22644
+ FormLayout.displayName = "FormLayout";
22645
+ FormActions = ({
22646
+ children,
22647
+ sticky = false,
22648
+ align = "right",
22649
+ className
22650
+ }) => {
22651
+ const alignClass2 = {
22652
+ left: "justify-start",
22653
+ right: "justify-end",
22654
+ between: "justify-between",
22655
+ center: "justify-center"
22656
+ }[align];
22657
+ return /* @__PURE__ */ jsx(
22658
+ HStack,
22659
+ {
22660
+ gap: "sm",
22661
+ align: "center",
22662
+ className: cn(
22663
+ "pt-6 border-t border-border",
22664
+ alignClass2,
22665
+ sticky && "sticky bottom-0 bg-card py-4 -mx-6 px-6 shadow-[0_-4px_6px_-1px_rgb(0,0,0,0.05)]",
22666
+ className
22667
+ ),
22668
+ children
22669
+ }
22670
+ );
22671
+ };
22672
+ FormActions.displayName = "FormActions";
22673
+ }
22674
+ });
22675
+ var ALL_CATEGORY, GridPicker;
22676
+ var init_GridPicker = __esm({
22677
+ "components/core/molecules/GridPicker.tsx"() {
22678
+ "use client";
22679
+ init_cn();
22680
+ init_Input();
22681
+ init_Badge();
22682
+ init_Stack();
22683
+ ALL_CATEGORY = "__all__";
22684
+ GridPicker = ({
22685
+ items,
22686
+ value,
22687
+ onChange,
22688
+ categories,
22689
+ searchPlaceholder,
22690
+ renderThumbnail,
22691
+ cellSize = 32,
22692
+ className
22693
+ }) => {
22694
+ const [search, setSearch] = useState("");
22695
+ const [activeCategory, setActiveCategory] = useState(ALL_CATEGORY);
22696
+ const gridRef = useRef(null);
22697
+ const categoryChips = useMemo(() => {
22698
+ if (categories !== void 0) return categories;
22699
+ const seen = [];
22700
+ for (const item of items) {
22701
+ if (!seen.includes(item.category)) seen.push(item.category);
22702
+ }
22703
+ return seen;
22704
+ }, [categories, items]);
22705
+ const filtered = useMemo(() => {
22706
+ const needle = search.trim().toLowerCase();
22707
+ return items.filter((item) => {
22708
+ const matchesCategory = activeCategory === ALL_CATEGORY || item.category === activeCategory;
22709
+ const matchesSearch = needle === "" || item.label.toLowerCase().includes(needle);
22710
+ return matchesCategory && matchesSearch;
22711
+ });
22712
+ }, [items, search, activeCategory]);
22713
+ const select = useCallback(
22714
+ (item) => {
22715
+ onChange(item.id);
22716
+ },
22717
+ [onChange]
22718
+ );
22719
+ const handleKeyDown = useCallback(
22720
+ (e, index) => {
22721
+ const cells = gridRef.current?.querySelectorAll(
22722
+ "[data-gridpicker-cell]"
22723
+ );
22724
+ if (cells === void 0 || cells.length === 0) return;
22725
+ const columns = (() => {
22726
+ const grid = gridRef.current;
22727
+ if (grid === null) return 1;
22728
+ const style = window.getComputedStyle(grid);
22729
+ const cols = style.gridTemplateColumns.split(" ").filter(Boolean).length;
22730
+ return cols > 0 ? cols : 1;
22731
+ })();
22732
+ let next = -1;
22733
+ if (e.key === "ArrowRight") next = index + 1;
22734
+ else if (e.key === "ArrowLeft") next = index - 1;
22735
+ else if (e.key === "ArrowDown") next = index + columns;
22736
+ else if (e.key === "ArrowUp") next = index - columns;
22737
+ else if (e.key === "Enter" || e.key === " ") {
22738
+ e.preventDefault();
22739
+ select(filtered[index]);
22740
+ return;
22741
+ } else {
22742
+ return;
22743
+ }
22744
+ e.preventDefault();
22745
+ if (next >= 0 && next < cells.length) {
22746
+ cells[next].focus();
22747
+ }
22748
+ },
22749
+ [filtered, select]
22750
+ );
22751
+ return /* @__PURE__ */ jsxs(VStack, { gap: "sm", className: cn("w-full", className), children: [
22752
+ /* @__PURE__ */ jsx(
22753
+ Input,
22754
+ {
22755
+ type: "search",
22756
+ icon: "search",
22757
+ value: search,
22758
+ placeholder: searchPlaceholder,
22759
+ clearable: true,
22760
+ onClear: () => setSearch(""),
22761
+ onChange: (e) => setSearch(e.target.value)
22762
+ }
22763
+ ),
22764
+ categoryChips.length > 0 && /* @__PURE__ */ jsxs(HStack, { gap: "xs", wrap: true, children: [
22765
+ /* @__PURE__ */ jsx(
22766
+ Badge,
22767
+ {
22768
+ variant: activeCategory === ALL_CATEGORY ? "primary" : "neutral",
22769
+ size: "sm",
22770
+ role: "button",
22771
+ tabIndex: 0,
22772
+ "aria-pressed": activeCategory === ALL_CATEGORY,
22773
+ className: "cursor-pointer",
22774
+ onClick: () => setActiveCategory(ALL_CATEGORY),
22775
+ onKeyDown: (e) => {
22776
+ if (e.key === "Enter" || e.key === " ") {
22777
+ e.preventDefault();
22778
+ setActiveCategory(ALL_CATEGORY);
22779
+ }
22780
+ },
22781
+ children: "All"
22782
+ }
22783
+ ),
22784
+ categoryChips.map((category) => /* @__PURE__ */ jsx(
22785
+ Badge,
22786
+ {
22787
+ variant: activeCategory === category ? "primary" : "neutral",
22788
+ size: "sm",
22789
+ role: "button",
22790
+ tabIndex: 0,
22791
+ "aria-pressed": activeCategory === category,
22792
+ className: "cursor-pointer",
22793
+ onClick: () => setActiveCategory(category),
22794
+ onKeyDown: (e) => {
22795
+ if (e.key === "Enter" || e.key === " ") {
22796
+ e.preventDefault();
22797
+ setActiveCategory(category);
22798
+ }
22799
+ },
22800
+ children: category
22801
+ },
22802
+ category
22803
+ ))
22804
+ ] }),
22805
+ /* @__PURE__ */ jsx(
22806
+ "div",
22807
+ {
22808
+ ref: gridRef,
22809
+ role: "listbox",
22810
+ className: "grid gap-1 overflow-y-auto max-h-64 p-1",
22811
+ style: {
22812
+ gridTemplateColumns: `repeat(auto-fill, minmax(${cellSize}px, 1fr))`
22813
+ },
22814
+ children: filtered.map((item, index) => {
22815
+ const selected = item.id === value;
22816
+ return /* @__PURE__ */ jsx(
22817
+ "button",
22818
+ {
22819
+ type: "button",
22820
+ role: "option",
22821
+ "aria-selected": selected,
22822
+ "aria-label": item.label,
22823
+ title: item.label,
22824
+ "data-gridpicker-cell": true,
22825
+ tabIndex: selected || value === void 0 && index === 0 ? 0 : -1,
22826
+ onClick: () => select(item),
22827
+ onKeyDown: (e) => handleKeyDown(e, index),
22828
+ className: cn(
22829
+ "flex items-center justify-center rounded-sm",
22830
+ "transition-colors hover:bg-muted",
22831
+ "focus:outline-none focus:ring-1 focus:ring-ring",
22832
+ selected && "bg-primary/10 ring-1 ring-primary"
22833
+ ),
22834
+ style: { width: cellSize, height: cellSize },
22835
+ children: renderThumbnail(item)
22836
+ },
22837
+ item.id
22838
+ );
22839
+ })
22840
+ }
22841
+ )
22842
+ ] });
22843
+ };
22844
+ GridPicker.displayName = "GridPicker";
22845
+ }
22846
+ });
22847
+ function pascalToKebab(name) {
22848
+ return name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([A-Z])([A-Z][a-z])/g, "$1-$2").toLowerCase();
22849
+ }
22850
+ function kebabToPascal3(name) {
22851
+ return name.split("-").map((part) => /^\d+$/.test(part) ? part : part.charAt(0).toUpperCase() + part.slice(1)).join("");
22852
+ }
22853
+ var ICON_ITEMS, IconPicker;
22854
+ var init_IconPicker = __esm({
22855
+ "components/core/molecules/IconPicker.tsx"() {
22856
+ "use client";
22857
+ init_Icon();
22858
+ init_GridPicker();
22859
+ ICON_ITEMS = (() => {
22860
+ const items = [];
22861
+ for (const [exportName, candidate] of Object.entries(LucideIcons2)) {
22862
+ if (!/^[A-Z]/.test(exportName)) continue;
22863
+ if (exportName.endsWith("Icon")) continue;
22864
+ if (exportName.startsWith("Lucide")) continue;
22865
+ const isComponent = candidate !== null && (typeof candidate === "object" || typeof candidate === "function") && "$$typeof" in candidate;
22866
+ if (!isComponent) continue;
22867
+ const kebab = pascalToKebab(exportName);
22868
+ if (kebabToPascal3(kebab) !== exportName) continue;
22869
+ items.push({ id: kebab, label: kebab, category: "icons" });
22870
+ }
22871
+ return items;
22872
+ })();
22873
+ IconPicker = ({ value, onChange, className }) => {
22874
+ const items = useMemo(() => ICON_ITEMS, []);
22875
+ return /* @__PURE__ */ jsx(
22876
+ GridPicker,
22877
+ {
22878
+ items,
22879
+ value,
22880
+ onChange,
22881
+ searchPlaceholder: "Search icons\u2026",
22882
+ renderThumbnail: (it) => /* @__PURE__ */ jsx(Icon, { name: it.id }),
22883
+ cellSize: 32,
22884
+ className
22885
+ }
22886
+ );
22887
+ };
22888
+ IconPicker.displayName = "IconPicker";
22889
+ }
22890
+ });
22891
+ function iconForKind(kind) {
22892
+ if (kind === "audio") return "music";
22893
+ if (kind === "model") return "box";
22894
+ return "file";
22895
+ }
22896
+ var THUMB_PX, IMAGE_KINDS, AssetPicker;
22897
+ var init_AssetPicker = __esm({
22898
+ "components/core/molecules/AssetPicker.tsx"() {
22899
+ "use client";
22900
+ init_GridPicker();
22901
+ init_Icon();
22902
+ THUMB_PX = 32;
22903
+ IMAGE_KINDS = /* @__PURE__ */ new Set([
22904
+ "image",
22905
+ "spritesheet",
22906
+ "scene",
22907
+ "portrait"
22908
+ ]);
22909
+ AssetPicker = ({
22910
+ assets,
22911
+ value,
22912
+ onChange,
22913
+ className
22914
+ }) => {
22915
+ const byUrl = useMemo(() => {
22916
+ const map = /* @__PURE__ */ new Map();
22917
+ for (const entry of assets) map.set(entry.url, entry);
22918
+ return map;
22919
+ }, [assets]);
22920
+ const items = useMemo(
22921
+ () => assets.map((entry) => ({
22922
+ id: entry.url,
22923
+ label: entry.name,
22924
+ category: entry.category
22925
+ })),
22926
+ [assets]
22927
+ );
22928
+ const categories = useMemo(() => {
22929
+ const seen = [];
22930
+ for (const entry of assets) {
22931
+ if (!seen.includes(entry.category)) seen.push(entry.category);
22932
+ }
22933
+ return seen;
22934
+ }, [assets]);
22935
+ const renderThumbnail = useCallback(
22936
+ (item) => {
22937
+ const entry = byUrl.get(item.id);
22938
+ if (entry === void 0) return null;
22939
+ if (IMAGE_KINDS.has(entry.kind)) {
22940
+ return /* @__PURE__ */ jsx(
22941
+ "img",
22942
+ {
22943
+ src: entry.thumbnailUrl ?? entry.url,
22944
+ alt: entry.name,
22945
+ loading: "lazy",
22946
+ width: THUMB_PX,
22947
+ height: THUMB_PX,
22948
+ style: { width: THUMB_PX, height: THUMB_PX, objectFit: "cover" }
22949
+ }
22950
+ );
22951
+ }
22952
+ return /* @__PURE__ */ jsx(Icon, { name: iconForKind(entry.kind), size: "sm" });
22953
+ },
22954
+ [byUrl]
22955
+ );
22956
+ return /* @__PURE__ */ jsx(
22957
+ GridPicker,
22958
+ {
22959
+ items,
22960
+ value,
22961
+ onChange,
22962
+ categories,
22963
+ renderThumbnail,
22964
+ cellSize: THUMB_PX,
22965
+ className
22966
+ }
22967
+ );
22968
+ };
22969
+ AssetPicker.displayName = "AssetPicker";
22970
+ }
22971
+ });
22972
+ function currentValue(decl, override) {
22973
+ return override !== void 0 ? override : decl.default;
22974
+ }
22975
+ function TextLikeControl({
22976
+ field,
22977
+ numeric,
22978
+ value,
22979
+ onCommit
22980
+ }) {
22981
+ const initial = value === void 0 || value === null ? "" : String(value);
22982
+ const [draft, setDraft] = React74__default.useState(initial);
22983
+ React74__default.useEffect(() => setDraft(initial), [initial]);
22984
+ const commit = () => {
22985
+ if (numeric) {
22986
+ const n = draft.trim() === "" ? 0 : Number(draft);
22987
+ onCommit(field, Number.isNaN(n) ? 0 : n);
22988
+ } else {
22989
+ onCommit(field, draft);
22990
+ }
22991
+ };
22992
+ return /* @__PURE__ */ jsx(
22993
+ Input,
22994
+ {
22995
+ inputType: numeric ? "number" : "text",
22996
+ value: draft,
22997
+ onChange: (e) => setDraft(e.target.value),
22998
+ onBlur: commit,
22999
+ onKeyDown: (e) => {
23000
+ if (e.key === "Enter") commit();
23001
+ }
23002
+ }
23003
+ );
23004
+ }
23005
+ function isTraitConfigObject(v) {
23006
+ return v !== null && v !== void 0 && typeof v === "object" && !Array.isArray(v);
23007
+ }
23008
+ function FieldControl({
23009
+ name,
23010
+ decl,
23011
+ value,
23012
+ onChange,
23013
+ assets
23014
+ }) {
23015
+ let control;
23016
+ const stringValue = typeof value === "string" ? value : void 0;
23017
+ const effectiveValue = value ?? decl.default;
23018
+ if (decl.type === "icon") {
23019
+ control = /* @__PURE__ */ jsx(IconPicker, { value: stringValue, onChange: (icon) => onChange(name, icon) });
23020
+ } else if (decl.type === "asset") {
23021
+ control = /* @__PURE__ */ jsx(
23022
+ AssetPicker,
23023
+ {
23024
+ assets: assets ?? [],
23025
+ value: stringValue,
23026
+ onChange: (url) => onChange(name, url)
23027
+ }
23028
+ );
23029
+ } else if (decl.type === "boolean") {
23030
+ control = /* @__PURE__ */ jsx(Switch, { checked: value === true, onChange: (c) => onChange(name, c) });
23031
+ } else if (decl.type === "string" && decl.values !== void 0 && decl.values.length > 0) {
23032
+ control = /* @__PURE__ */ jsx(
23033
+ Select,
23034
+ {
23035
+ options: decl.values.map((v) => ({ value: v, label: v })),
23036
+ value: typeof value === "string" ? value : "",
23037
+ onChange: (e) => onChange(name, e.target.value)
23038
+ }
23039
+ );
23040
+ } else if (decl.type === "number") {
23041
+ control = /* @__PURE__ */ jsx(TextLikeControl, { field: name, numeric: true, value, onCommit: onChange });
23042
+ } else if (decl.type === "string") {
23043
+ control = /* @__PURE__ */ jsx(TextLikeControl, { field: name, numeric: false, value, onCommit: onChange });
23044
+ } else if (decl.type === "node") {
23045
+ control = /* @__PURE__ */ jsx(NodeSlotEditor, { value: effectiveValue, onChange: (next) => onChange(name, next) });
23046
+ } else if (decl.type.startsWith("[") || Array.isArray(effectiveValue)) {
23047
+ const arr = Array.isArray(effectiveValue) ? effectiveValue : [];
23048
+ control = /* @__PURE__ */ jsx(JsonTreeEditor, { value: arr, onChange: (next) => onChange(name, next) });
23049
+ } else if (decl.type === "object" || decl.type.startsWith("Map ") || !SCALAR_TYPES.has(decl.type) && isTraitConfigObject(effectiveValue)) {
23050
+ const obj = isTraitConfigObject(effectiveValue) ? effectiveValue : {};
23051
+ control = /* @__PURE__ */ jsx(JsonTreeEditor, { value: obj, onChange: (next) => onChange(name, next) });
23052
+ } else {
23053
+ control = /* @__PURE__ */ jsxs(Typography, { variant: "caption", color: "muted", children: [
23054
+ decl.type,
23055
+ " \u2014 edit in source"
23056
+ ] });
23057
+ }
23058
+ return /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
23059
+ /* @__PURE__ */ jsx(Typography, { variant: "label", children: decl.label ?? name }),
23060
+ control,
23061
+ decl.description !== void 0 && decl.description !== "" && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "muted", children: decl.description })
23062
+ ] });
23063
+ }
23064
+ var TIER_ORDER, SCALAR_TYPES, PropertyInspector;
23065
+ var init_PropertyInspector = __esm({
23066
+ "components/core/molecules/PropertyInspector.tsx"() {
23067
+ "use client";
23068
+ init_cn();
23069
+ init_Stack();
23070
+ init_Typography();
23071
+ init_Button();
23072
+ init_Switch();
23073
+ init_Select();
23074
+ init_Input();
23075
+ init_FormSection();
23076
+ init_IconPicker();
23077
+ init_AssetPicker();
23078
+ init_JsonTreeEditor();
23079
+ init_NodeSlotEditor();
23080
+ TIER_ORDER = ["presentation", "domain", "policy", "infra", "internal"];
23081
+ SCALAR_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "icon", "asset"]);
23082
+ PropertyInspector = ({
23083
+ config,
23084
+ values,
23085
+ onChange,
23086
+ onReset,
23087
+ title,
23088
+ className,
23089
+ assets
23090
+ }) => {
23091
+ const fields = Object.entries(config);
23092
+ const byTier = /* @__PURE__ */ new Map();
23093
+ for (const [name, decl] of fields) {
23094
+ const tier = decl.tier ?? "presentation";
23095
+ const arr = byTier.get(tier) ?? [];
23096
+ arr.push([name, decl]);
23097
+ byTier.set(tier, arr);
23098
+ }
23099
+ const tiers = [...byTier.keys()].sort((a, b) => {
23100
+ const ia = TIER_ORDER.indexOf(a);
23101
+ const ib = TIER_ORDER.indexOf(b);
23102
+ return (ia === -1 ? 99 : ia) - (ib === -1 ? 99 : ib);
23103
+ });
23104
+ return /* @__PURE__ */ jsxs(VStack, { gap: "sm", className: cn("w-full", className), children: [
23105
+ /* @__PURE__ */ jsxs(HStack, { justify: "between", align: "center", children: [
23106
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", weight: "bold", children: title ?? "Config" }),
23107
+ onReset !== void 0 && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", icon: "rotate-ccw", label: "Reset", onClick: onReset })
23108
+ ] }),
23109
+ fields.length === 0 && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "muted", children: "No configurable properties." }),
23110
+ tiers.map((tier) => /* @__PURE__ */ jsx(FormSection, { title: tier, collapsible: true, defaultCollapsed: tier !== "presentation", children: /* @__PURE__ */ jsx(VStack, { gap: "sm", children: byTier.get(tier)?.map(([name, decl]) => /* @__PURE__ */ jsx(
23111
+ FieldControl,
23112
+ {
23113
+ name,
23114
+ decl,
23115
+ value: currentValue(decl, values?.[name]),
23116
+ onChange,
23117
+ assets
23118
+ },
23119
+ name
23120
+ )) }) }, tier))
23121
+ ] });
23122
+ };
23123
+ }
23124
+ });
22479
23125
  function normalize(value) {
22480
23126
  const wasArray = Array.isArray(value);
22481
23127
  const pattern = wasArray ? value[0] : value;
@@ -22494,6 +23140,7 @@ var init_NodeSlotEditor = __esm({
22494
23140
  init_Select();
22495
23141
  init_Typography();
22496
23142
  init_JsonTreeEditor();
23143
+ init_PropertyInspector();
22497
23144
  init_pattern_resolver();
22498
23145
  init_cn();
22499
23146
  isObj2 = (v) => v !== null && v !== void 0 && typeof v === "object" && !Array.isArray(v);
@@ -22518,6 +23165,21 @@ var init_NodeSlotEditor = __esm({
22518
23165
  const pattern = { type: nextType, ...nextProps };
22519
23166
  onChange(wasArray || value === void 0 ? [pattern] : pattern);
22520
23167
  };
23168
+ const schemaEntries = React74__default.useMemo(() => {
23169
+ if (!type) return [];
23170
+ const def = getPatternDefinition(type);
23171
+ if (!def?.propsSchema) return [];
23172
+ return Object.entries(def.propsSchema).map(([propName, propDef]) => {
23173
+ const decl = {
23174
+ type: propDef.types?.[0] ?? "string",
23175
+ description: propDef.description,
23176
+ label: propName,
23177
+ ...propDef.enumValues && propDef.enumValues.length > 0 ? { values: propDef.enumValues } : {}
23178
+ };
23179
+ return [propName, decl];
23180
+ });
23181
+ }, [type]);
23182
+ const hasSchema = schemaEntries.length > 0;
22521
23183
  return /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: cn("w-full", className), children: [
22522
23184
  /* @__PURE__ */ jsx(
22523
23185
  Select,
@@ -22529,7 +23191,16 @@ var init_NodeSlotEditor = __esm({
22529
23191
  ),
22530
23192
  type !== "" && /* @__PURE__ */ jsxs(VStack, { gap: "none", className: "pl-1", children: [
22531
23193
  /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "muted", children: "props" }),
22532
- /* @__PURE__ */ jsx(JsonTreeEditor, { value: props, onChange: (next) => emit(type, isObj2(next) ? next : {}) })
23194
+ hasSchema ? /* @__PURE__ */ jsx(VStack, { gap: "xs", children: schemaEntries.map(([propName, decl]) => /* @__PURE__ */ jsx(
23195
+ FieldControl,
23196
+ {
23197
+ name: propName,
23198
+ decl,
23199
+ value: props[propName],
23200
+ onChange: (field, val) => emit(type, { ...props, [field]: val })
23201
+ },
23202
+ propName
23203
+ )) }) : /* @__PURE__ */ jsx(JsonTreeEditor, { value: props, onChange: (next) => emit(type, isObj2(next) ? next : {}) })
22533
23204
  ] })
22534
23205
  ] });
22535
23206
  };
@@ -25112,303 +25783,6 @@ var init_FlipCard = __esm({
25112
25783
  FlipCard.displayName = "FlipCard";
25113
25784
  }
25114
25785
  });
25115
- var ALL_CATEGORY, GridPicker;
25116
- var init_GridPicker = __esm({
25117
- "components/core/molecules/GridPicker.tsx"() {
25118
- "use client";
25119
- init_cn();
25120
- init_Input();
25121
- init_Badge();
25122
- init_Stack();
25123
- ALL_CATEGORY = "__all__";
25124
- GridPicker = ({
25125
- items,
25126
- value,
25127
- onChange,
25128
- categories,
25129
- searchPlaceholder,
25130
- renderThumbnail,
25131
- cellSize = 32,
25132
- className
25133
- }) => {
25134
- const [search, setSearch] = useState("");
25135
- const [activeCategory, setActiveCategory] = useState(ALL_CATEGORY);
25136
- const gridRef = useRef(null);
25137
- const categoryChips = useMemo(() => {
25138
- if (categories !== void 0) return categories;
25139
- const seen = [];
25140
- for (const item of items) {
25141
- if (!seen.includes(item.category)) seen.push(item.category);
25142
- }
25143
- return seen;
25144
- }, [categories, items]);
25145
- const filtered = useMemo(() => {
25146
- const needle = search.trim().toLowerCase();
25147
- return items.filter((item) => {
25148
- const matchesCategory = activeCategory === ALL_CATEGORY || item.category === activeCategory;
25149
- const matchesSearch = needle === "" || item.label.toLowerCase().includes(needle);
25150
- return matchesCategory && matchesSearch;
25151
- });
25152
- }, [items, search, activeCategory]);
25153
- const select = useCallback(
25154
- (item) => {
25155
- onChange(item.id);
25156
- },
25157
- [onChange]
25158
- );
25159
- const handleKeyDown = useCallback(
25160
- (e, index) => {
25161
- const cells = gridRef.current?.querySelectorAll(
25162
- "[data-gridpicker-cell]"
25163
- );
25164
- if (cells === void 0 || cells.length === 0) return;
25165
- const columns = (() => {
25166
- const grid = gridRef.current;
25167
- if (grid === null) return 1;
25168
- const style = window.getComputedStyle(grid);
25169
- const cols = style.gridTemplateColumns.split(" ").filter(Boolean).length;
25170
- return cols > 0 ? cols : 1;
25171
- })();
25172
- let next = -1;
25173
- if (e.key === "ArrowRight") next = index + 1;
25174
- else if (e.key === "ArrowLeft") next = index - 1;
25175
- else if (e.key === "ArrowDown") next = index + columns;
25176
- else if (e.key === "ArrowUp") next = index - columns;
25177
- else if (e.key === "Enter" || e.key === " ") {
25178
- e.preventDefault();
25179
- select(filtered[index]);
25180
- return;
25181
- } else {
25182
- return;
25183
- }
25184
- e.preventDefault();
25185
- if (next >= 0 && next < cells.length) {
25186
- cells[next].focus();
25187
- }
25188
- },
25189
- [filtered, select]
25190
- );
25191
- return /* @__PURE__ */ jsxs(VStack, { gap: "sm", className: cn("w-full", className), children: [
25192
- /* @__PURE__ */ jsx(
25193
- Input,
25194
- {
25195
- type: "search",
25196
- icon: "search",
25197
- value: search,
25198
- placeholder: searchPlaceholder,
25199
- clearable: true,
25200
- onClear: () => setSearch(""),
25201
- onChange: (e) => setSearch(e.target.value)
25202
- }
25203
- ),
25204
- categoryChips.length > 0 && /* @__PURE__ */ jsxs(HStack, { gap: "xs", wrap: true, children: [
25205
- /* @__PURE__ */ jsx(
25206
- Badge,
25207
- {
25208
- variant: activeCategory === ALL_CATEGORY ? "primary" : "neutral",
25209
- size: "sm",
25210
- role: "button",
25211
- tabIndex: 0,
25212
- "aria-pressed": activeCategory === ALL_CATEGORY,
25213
- className: "cursor-pointer",
25214
- onClick: () => setActiveCategory(ALL_CATEGORY),
25215
- onKeyDown: (e) => {
25216
- if (e.key === "Enter" || e.key === " ") {
25217
- e.preventDefault();
25218
- setActiveCategory(ALL_CATEGORY);
25219
- }
25220
- },
25221
- children: "All"
25222
- }
25223
- ),
25224
- categoryChips.map((category) => /* @__PURE__ */ jsx(
25225
- Badge,
25226
- {
25227
- variant: activeCategory === category ? "primary" : "neutral",
25228
- size: "sm",
25229
- role: "button",
25230
- tabIndex: 0,
25231
- "aria-pressed": activeCategory === category,
25232
- className: "cursor-pointer",
25233
- onClick: () => setActiveCategory(category),
25234
- onKeyDown: (e) => {
25235
- if (e.key === "Enter" || e.key === " ") {
25236
- e.preventDefault();
25237
- setActiveCategory(category);
25238
- }
25239
- },
25240
- children: category
25241
- },
25242
- category
25243
- ))
25244
- ] }),
25245
- /* @__PURE__ */ jsx(
25246
- "div",
25247
- {
25248
- ref: gridRef,
25249
- role: "listbox",
25250
- className: "grid gap-1 overflow-y-auto max-h-64 p-1",
25251
- style: {
25252
- gridTemplateColumns: `repeat(auto-fill, minmax(${cellSize}px, 1fr))`
25253
- },
25254
- children: filtered.map((item, index) => {
25255
- const selected = item.id === value;
25256
- return /* @__PURE__ */ jsx(
25257
- "button",
25258
- {
25259
- type: "button",
25260
- role: "option",
25261
- "aria-selected": selected,
25262
- "aria-label": item.label,
25263
- title: item.label,
25264
- "data-gridpicker-cell": true,
25265
- tabIndex: selected || value === void 0 && index === 0 ? 0 : -1,
25266
- onClick: () => select(item),
25267
- onKeyDown: (e) => handleKeyDown(e, index),
25268
- className: cn(
25269
- "flex items-center justify-center rounded-sm",
25270
- "transition-colors hover:bg-muted",
25271
- "focus:outline-none focus:ring-1 focus:ring-ring",
25272
- selected && "bg-primary/10 ring-1 ring-primary"
25273
- ),
25274
- style: { width: cellSize, height: cellSize },
25275
- children: renderThumbnail(item)
25276
- },
25277
- item.id
25278
- );
25279
- })
25280
- }
25281
- )
25282
- ] });
25283
- };
25284
- GridPicker.displayName = "GridPicker";
25285
- }
25286
- });
25287
- function iconForKind(kind) {
25288
- if (kind === "audio") return "music";
25289
- if (kind === "model") return "box";
25290
- return "file";
25291
- }
25292
- var THUMB_PX, IMAGE_KINDS, AssetPicker;
25293
- var init_AssetPicker = __esm({
25294
- "components/core/molecules/AssetPicker.tsx"() {
25295
- "use client";
25296
- init_GridPicker();
25297
- init_Icon();
25298
- THUMB_PX = 32;
25299
- IMAGE_KINDS = /* @__PURE__ */ new Set([
25300
- "image",
25301
- "spritesheet",
25302
- "scene",
25303
- "portrait"
25304
- ]);
25305
- AssetPicker = ({
25306
- assets,
25307
- value,
25308
- onChange,
25309
- className
25310
- }) => {
25311
- const byUrl = useMemo(() => {
25312
- const map = /* @__PURE__ */ new Map();
25313
- for (const entry of assets) map.set(entry.url, entry);
25314
- return map;
25315
- }, [assets]);
25316
- const items = useMemo(
25317
- () => assets.map((entry) => ({
25318
- id: entry.url,
25319
- label: entry.name,
25320
- category: entry.category
25321
- })),
25322
- [assets]
25323
- );
25324
- const categories = useMemo(() => {
25325
- const seen = [];
25326
- for (const entry of assets) {
25327
- if (!seen.includes(entry.category)) seen.push(entry.category);
25328
- }
25329
- return seen;
25330
- }, [assets]);
25331
- const renderThumbnail = useCallback(
25332
- (item) => {
25333
- const entry = byUrl.get(item.id);
25334
- if (entry === void 0) return null;
25335
- if (IMAGE_KINDS.has(entry.kind)) {
25336
- return /* @__PURE__ */ jsx(
25337
- "img",
25338
- {
25339
- src: entry.thumbnailUrl ?? entry.url,
25340
- alt: entry.name,
25341
- loading: "lazy",
25342
- width: THUMB_PX,
25343
- height: THUMB_PX,
25344
- style: { width: THUMB_PX, height: THUMB_PX, objectFit: "cover" }
25345
- }
25346
- );
25347
- }
25348
- return /* @__PURE__ */ jsx(Icon, { name: iconForKind(entry.kind), size: "sm" });
25349
- },
25350
- [byUrl]
25351
- );
25352
- return /* @__PURE__ */ jsx(
25353
- GridPicker,
25354
- {
25355
- items,
25356
- value,
25357
- onChange,
25358
- categories,
25359
- renderThumbnail,
25360
- cellSize: THUMB_PX,
25361
- className
25362
- }
25363
- );
25364
- };
25365
- AssetPicker.displayName = "AssetPicker";
25366
- }
25367
- });
25368
- function pascalToKebab(name) {
25369
- return name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([A-Z])([A-Z][a-z])/g, "$1-$2").toLowerCase();
25370
- }
25371
- function kebabToPascal3(name) {
25372
- return name.split("-").map((part) => /^\d+$/.test(part) ? part : part.charAt(0).toUpperCase() + part.slice(1)).join("");
25373
- }
25374
- var ICON_ITEMS, IconPicker;
25375
- var init_IconPicker = __esm({
25376
- "components/core/molecules/IconPicker.tsx"() {
25377
- "use client";
25378
- init_Icon();
25379
- init_GridPicker();
25380
- ICON_ITEMS = (() => {
25381
- const items = [];
25382
- for (const [exportName, candidate] of Object.entries(LucideIcons2)) {
25383
- if (!/^[A-Z]/.test(exportName)) continue;
25384
- if (exportName.endsWith("Icon")) continue;
25385
- if (exportName.startsWith("Lucide")) continue;
25386
- const isComponent = candidate !== null && (typeof candidate === "object" || typeof candidate === "function") && "$$typeof" in candidate;
25387
- if (!isComponent) continue;
25388
- const kebab = pascalToKebab(exportName);
25389
- if (kebabToPascal3(kebab) !== exportName) continue;
25390
- items.push({ id: kebab, label: kebab, category: "icons" });
25391
- }
25392
- return items;
25393
- })();
25394
- IconPicker = ({ value, onChange, className }) => {
25395
- const items = useMemo(() => ICON_ITEMS, []);
25396
- return /* @__PURE__ */ jsx(
25397
- GridPicker,
25398
- {
25399
- items,
25400
- value,
25401
- onChange,
25402
- searchPlaceholder: "Search icons\u2026",
25403
- renderThumbnail: (it) => /* @__PURE__ */ jsx(Icon, { name: it.id }),
25404
- cellSize: 32,
25405
- className
25406
- }
25407
- );
25408
- };
25409
- IconPicker.displayName = "IconPicker";
25410
- }
25411
- });
25412
25786
  function toISODate(d) {
25413
25787
  return d.toISOString().slice(0, 10);
25414
25788
  }
@@ -35099,286 +35473,6 @@ var init_PageHeader = __esm({
35099
35473
  PageHeader.displayName = "PageHeader";
35100
35474
  }
35101
35475
  });
35102
- var FormSection, FormLayout, FormActions;
35103
- var init_FormSection = __esm({
35104
- "components/core/molecules/FormSection.tsx"() {
35105
- "use client";
35106
- init_cn();
35107
- init_atoms2();
35108
- init_Box();
35109
- init_Typography();
35110
- init_Button();
35111
- init_Stack();
35112
- init_Icon();
35113
- init_useEventBus();
35114
- FormSection = ({
35115
- title,
35116
- description,
35117
- children,
35118
- collapsible = false,
35119
- defaultCollapsed = false,
35120
- card = false,
35121
- columns = 1,
35122
- className
35123
- }) => {
35124
- const [collapsed, setCollapsed] = React74__default.useState(defaultCollapsed);
35125
- const { t } = useTranslate();
35126
- const eventBus = useEventBus();
35127
- const gridClass = {
35128
- 1: "grid-cols-1",
35129
- 2: "grid-cols-1 md:grid-cols-2",
35130
- 3: "grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
35131
- }[columns];
35132
- React74__default.useCallback(() => {
35133
- if (collapsible) {
35134
- setCollapsed((prev) => !prev);
35135
- eventBus.emit("UI:TOGGLE_COLLAPSE", { collapsed: !collapsed });
35136
- }
35137
- }, [collapsible, collapsed, eventBus]);
35138
- const content = /* @__PURE__ */ jsxs(Fragment, { children: [
35139
- (title || description) && /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: "mb-4", children: [
35140
- title && /* @__PURE__ */ jsxs(
35141
- HStack,
35142
- {
35143
- justify: "between",
35144
- align: "center",
35145
- className: cn(collapsible && "cursor-pointer"),
35146
- action: collapsible ? "TOGGLE_COLLAPSE" : void 0,
35147
- children: [
35148
- /* @__PURE__ */ jsx(Typography, { variant: "h3", weight: "semibold", children: title }),
35149
- collapsible && /* @__PURE__ */ jsx(
35150
- Button,
35151
- {
35152
- variant: "ghost",
35153
- size: "sm",
35154
- action: "TOGGLE_COLLAPSE",
35155
- children: /* @__PURE__ */ jsx(
35156
- Icon,
35157
- {
35158
- icon: ChevronDown,
35159
- size: "sm",
35160
- className: cn(
35161
- "text-muted-foreground transition-transform",
35162
- collapsed && "rotate-180"
35163
- )
35164
- }
35165
- )
35166
- }
35167
- )
35168
- ]
35169
- }
35170
- ),
35171
- description && /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: description })
35172
- ] }),
35173
- (!collapsible || !collapsed) && /* @__PURE__ */ jsx(Box, { className: cn("grid gap-4", gridClass), children })
35174
- ] });
35175
- if (card) {
35176
- return /* @__PURE__ */ jsx(Card, { className: cn("p-6", className), children: content });
35177
- }
35178
- return /* @__PURE__ */ jsx(Box, { className, children: content });
35179
- };
35180
- FormSection.displayName = "FormSection";
35181
- FormLayout = ({
35182
- children,
35183
- dividers = true,
35184
- className
35185
- }) => {
35186
- return /* @__PURE__ */ jsx(
35187
- VStack,
35188
- {
35189
- gap: "lg",
35190
- className: cn(
35191
- dividers && "[&>*+*]:pt-8 [&>*+*]:border-t [&>*+*]:border-border",
35192
- className
35193
- ),
35194
- children
35195
- }
35196
- );
35197
- };
35198
- FormLayout.displayName = "FormLayout";
35199
- FormActions = ({
35200
- children,
35201
- sticky = false,
35202
- align = "right",
35203
- className
35204
- }) => {
35205
- const alignClass2 = {
35206
- left: "justify-start",
35207
- right: "justify-end",
35208
- between: "justify-between",
35209
- center: "justify-center"
35210
- }[align];
35211
- return /* @__PURE__ */ jsx(
35212
- HStack,
35213
- {
35214
- gap: "sm",
35215
- align: "center",
35216
- className: cn(
35217
- "pt-6 border-t border-border",
35218
- alignClass2,
35219
- sticky && "sticky bottom-0 bg-card py-4 -mx-6 px-6 shadow-[0_-4px_6px_-1px_rgb(0,0,0,0.05)]",
35220
- className
35221
- ),
35222
- children
35223
- }
35224
- );
35225
- };
35226
- FormActions.displayName = "FormActions";
35227
- }
35228
- });
35229
- function currentValue(decl, override) {
35230
- return override !== void 0 ? override : decl.default;
35231
- }
35232
- function TextLikeControl({
35233
- field,
35234
- numeric,
35235
- value,
35236
- onCommit
35237
- }) {
35238
- const initial = value === void 0 || value === null ? "" : String(value);
35239
- const [draft, setDraft] = React74__default.useState(initial);
35240
- React74__default.useEffect(() => setDraft(initial), [initial]);
35241
- const commit = () => {
35242
- if (numeric) {
35243
- const n = draft.trim() === "" ? 0 : Number(draft);
35244
- onCommit(field, Number.isNaN(n) ? 0 : n);
35245
- } else {
35246
- onCommit(field, draft);
35247
- }
35248
- };
35249
- return /* @__PURE__ */ jsx(
35250
- Input,
35251
- {
35252
- inputType: numeric ? "number" : "text",
35253
- value: draft,
35254
- onChange: (e) => setDraft(e.target.value),
35255
- onBlur: commit,
35256
- onKeyDown: (e) => {
35257
- if (e.key === "Enter") commit();
35258
- }
35259
- }
35260
- );
35261
- }
35262
- function isTraitConfigObject(v) {
35263
- return v !== null && v !== void 0 && typeof v === "object" && !Array.isArray(v);
35264
- }
35265
- function FieldControl({
35266
- name,
35267
- decl,
35268
- value,
35269
- onChange,
35270
- assets
35271
- }) {
35272
- let control;
35273
- const stringValue = typeof value === "string" ? value : void 0;
35274
- const effectiveValue = value ?? decl.default;
35275
- if (decl.type === "icon") {
35276
- control = /* @__PURE__ */ jsx(IconPicker, { value: stringValue, onChange: (icon) => onChange(name, icon) });
35277
- } else if (decl.type === "asset") {
35278
- control = /* @__PURE__ */ jsx(
35279
- AssetPicker,
35280
- {
35281
- assets: assets ?? [],
35282
- value: stringValue,
35283
- onChange: (url) => onChange(name, url)
35284
- }
35285
- );
35286
- } else if (decl.type === "boolean") {
35287
- control = /* @__PURE__ */ jsx(Switch, { checked: value === true, onChange: (c) => onChange(name, c) });
35288
- } else if (decl.type === "string" && decl.values !== void 0 && decl.values.length > 0) {
35289
- control = /* @__PURE__ */ jsx(
35290
- Select,
35291
- {
35292
- options: decl.values.map((v) => ({ value: v, label: v })),
35293
- value: typeof value === "string" ? value : "",
35294
- onChange: (e) => onChange(name, e.target.value)
35295
- }
35296
- );
35297
- } else if (decl.type === "number") {
35298
- control = /* @__PURE__ */ jsx(TextLikeControl, { field: name, numeric: true, value, onCommit: onChange });
35299
- } else if (decl.type === "string") {
35300
- control = /* @__PURE__ */ jsx(TextLikeControl, { field: name, numeric: false, value, onCommit: onChange });
35301
- } else if (decl.type === "node") {
35302
- control = /* @__PURE__ */ jsx(NodeSlotEditor, { value: effectiveValue, onChange: (next) => onChange(name, next) });
35303
- } else if (decl.type.startsWith("[") || Array.isArray(effectiveValue)) {
35304
- const arr = Array.isArray(effectiveValue) ? effectiveValue : [];
35305
- control = /* @__PURE__ */ jsx(JsonTreeEditor, { value: arr, onChange: (next) => onChange(name, next) });
35306
- } else if (decl.type === "object" || decl.type.startsWith("Map ") || !SCALAR_TYPES.has(decl.type) && isTraitConfigObject(effectiveValue)) {
35307
- const obj = isTraitConfigObject(effectiveValue) ? effectiveValue : {};
35308
- control = /* @__PURE__ */ jsx(JsonTreeEditor, { value: obj, onChange: (next) => onChange(name, next) });
35309
- } else {
35310
- control = /* @__PURE__ */ jsxs(Typography, { variant: "caption", color: "muted", children: [
35311
- decl.type,
35312
- " \u2014 edit in source"
35313
- ] });
35314
- }
35315
- return /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
35316
- /* @__PURE__ */ jsx(Typography, { variant: "label", children: decl.label ?? name }),
35317
- control,
35318
- decl.description !== void 0 && decl.description !== "" && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "muted", children: decl.description })
35319
- ] });
35320
- }
35321
- var TIER_ORDER, SCALAR_TYPES, PropertyInspector;
35322
- var init_PropertyInspector = __esm({
35323
- "components/core/molecules/PropertyInspector.tsx"() {
35324
- "use client";
35325
- init_cn();
35326
- init_Stack();
35327
- init_Typography();
35328
- init_Button();
35329
- init_Switch();
35330
- init_Select();
35331
- init_Input();
35332
- init_FormSection();
35333
- init_IconPicker();
35334
- init_AssetPicker();
35335
- init_JsonTreeEditor();
35336
- init_NodeSlotEditor();
35337
- TIER_ORDER = ["presentation", "domain", "policy", "infra", "internal"];
35338
- SCALAR_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "icon", "asset"]);
35339
- PropertyInspector = ({
35340
- config,
35341
- values,
35342
- onChange,
35343
- onReset,
35344
- title,
35345
- className,
35346
- assets
35347
- }) => {
35348
- const fields = Object.entries(config);
35349
- const byTier = /* @__PURE__ */ new Map();
35350
- for (const [name, decl] of fields) {
35351
- const tier = decl.tier ?? "presentation";
35352
- const arr = byTier.get(tier) ?? [];
35353
- arr.push([name, decl]);
35354
- byTier.set(tier, arr);
35355
- }
35356
- const tiers = [...byTier.keys()].sort((a, b) => {
35357
- const ia = TIER_ORDER.indexOf(a);
35358
- const ib = TIER_ORDER.indexOf(b);
35359
- return (ia === -1 ? 99 : ia) - (ib === -1 ? 99 : ib);
35360
- });
35361
- return /* @__PURE__ */ jsxs(VStack, { gap: "sm", className: cn("w-full", className), children: [
35362
- /* @__PURE__ */ jsxs(HStack, { justify: "between", align: "center", children: [
35363
- /* @__PURE__ */ jsx(Typography, { variant: "caption", weight: "bold", children: title ?? "Config" }),
35364
- onReset !== void 0 && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", icon: "rotate-ccw", label: "Reset", onClick: onReset })
35365
- ] }),
35366
- fields.length === 0 && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "muted", children: "No configurable properties." }),
35367
- tiers.map((tier) => /* @__PURE__ */ jsx(FormSection, { title: tier, collapsible: true, defaultCollapsed: tier !== "presentation", children: /* @__PURE__ */ jsx(VStack, { gap: "sm", children: byTier.get(tier)?.map(([name, decl]) => /* @__PURE__ */ jsx(
35368
- FieldControl,
35369
- {
35370
- name,
35371
- decl,
35372
- value: currentValue(decl, values?.[name]),
35373
- onChange,
35374
- assets
35375
- },
35376
- name
35377
- )) }) }, tier))
35378
- ] });
35379
- };
35380
- }
35381
- });
35382
35476
  var lookStyles8, Header;
35383
35477
  var init_Header = __esm({
35384
35478
  "components/core/molecules/Header.tsx"() {
@@ -36734,7 +36828,6 @@ var init_DocumentViewer = __esm({
36734
36828
  showPrint = false,
36735
36829
  actions,
36736
36830
  documents,
36737
- entity,
36738
36831
  isLoading = false,
36739
36832
  error,
36740
36833
  className
@@ -43904,7 +43997,7 @@ function TraitSlot({
43904
43997
  size = "md",
43905
43998
  showTooltip = true,
43906
43999
  categoryColors,
43907
- tooltipFrameUrl,
44000
+ tooltipFrameUrl = "",
43908
44001
  className,
43909
44002
  feedback,
43910
44003
  onItemDrop,
@@ -48402,7 +48495,7 @@ function SlotContentRenderer({
48402
48495
  const { children: _childrenConfig, ...restPropsNoChildren } = content.props;
48403
48496
  const restProps = childrenIsRenderFn ? { ...restPropsNoChildren, children: incomingChildren } : restPropsNoChildren;
48404
48497
  const renderedProps = renderPatternProps(restProps, onDismiss);
48405
- const patternDef = getPatternDefinition(content.pattern);
48498
+ const patternDef = getPatternDefinition$1(content.pattern);
48406
48499
  const propsSchema = patternDef?.propsSchema;
48407
48500
  if (propsSchema) {
48408
48501
  for (const [propKey, propValue] of Object.entries(renderedProps)) {