@almadar/ui 5.156.0 → 5.158.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.
@@ -3735,13 +3735,33 @@ interface FileTreeNode {
3735
3735
  /** Detected language for syntax highlighting */
3736
3736
  language?: string;
3737
3737
  }
3738
+ /** Flat tree row for `items` mode — nested at render time by `parentId`. */
3739
+ interface FileTreeItem {
3740
+ /** Unique node id */
3741
+ id: string;
3742
+ /** Display label */
3743
+ label: string;
3744
+ /** Id of this node's parent; empty/absent marks a root */
3745
+ parentId?: string;
3746
+ /** Icon name override (falls back to folder/file by presence of children) */
3747
+ icon?: string;
3748
+ }
3738
3749
  interface FileTreeProps {
3739
- /** The tree data */
3740
- tree: FileTreeNode[];
3750
+ /** The tree data (pre-nested). Ignored when `items` is provided. */
3751
+ tree?: FileTreeNode[];
3752
+ /** Flat node list, nested by `parentId` at render time — takes precedence
3753
+ * over `tree` when provided. A `parentId` with no matching row renders
3754
+ * that node as a root rather than dropping it. */
3755
+ items?: FileTreeItem[];
3741
3756
  /** Currently selected file path */
3742
3757
  selectedPath?: string;
3743
3758
  /** Called when a file is clicked */
3744
3759
  onFileSelect?: (path: string) => void;
3760
+ /** Called when a node is clicked, in flat `items` mode; carries the
3761
+ * selected node's id. Presence-gated: binding this prop is what turns on
3762
+ * the click-to-select affordance, mirroring Badge's `onRemove`.
3763
+ * @offByDefault */
3764
+ onNodeSelect?: (id: string) => void;
3745
3765
  /** CSS class */
3746
3766
  className?: string;
3747
3767
  /** Indent size per level in px (default: 16) */
@@ -4789,6 +4809,11 @@ declare const OnboardingSpotlight: React__default.FC<OnboardingSpotlightProps>;
4789
4809
  * Composed from: Box, HStack, VStack, Input, Button, Spinner, Typography atoms
4790
4810
  */
4791
4811
 
4812
+ /** Relation cardinality vocabulary — mirrors `@almadar/core`'s
4813
+ * `RelationCardinality` (spelled inline: the pattern extractor resolves
4814
+ * components-tree aliases to `enumValues`, but treats core imports as
4815
+ * opaque). `many`/`one-to-many`/`many-to-many` drive multi-value pickers. */
4816
+ type RelationFieldCardinality = "one" | "many" | "one-to-many" | "many-to-one" | "many-to-many";
4792
4817
  interface RelationOption {
4793
4818
  /** The value to store (typically the ID) */
4794
4819
  value: string;
@@ -6118,6 +6143,12 @@ interface MathCanvasProps {
6118
6143
  showAxes?: boolean;
6119
6144
  showGrid?: boolean;
6120
6145
  gridStep?: number;
6146
+ /** Canvas fill color; `var(--token, fallback)` strings resolve against the active theme. Default transparent. */
6147
+ backgroundColor?: string;
6148
+ /** Grid line color; resolves theme tokens (default `var(--color-border, #9ca3af)`). */
6149
+ gridColor?: string;
6150
+ /** Axis line color; resolves theme tokens (default `var(--color-muted-foreground, #374151)`). */
6151
+ axisColor?: string;
6121
6152
  /** Draw numeric labels on grid lines (default false). */
6122
6153
  showTickLabels?: boolean;
6123
6154
  /** Draw each curve's `label` at its last in-range sample (default false). */
@@ -10721,6 +10752,13 @@ declare const StatCard: React__default.FC<StatCardProps>;
10721
10752
  * Extends DisplayStateProps (see ./types.ts) and declares `entity?: EntityRow`.
10722
10753
  */
10723
10754
 
10755
+ /** Relation descriptor injected onto a display field — target entity +
10756
+ * cardinality, mirroring the `.lolo` `{type:"relation", relation:{...}}`
10757
+ * descriptor. */
10758
+ interface DetailRelationMeta {
10759
+ entity: string;
10760
+ cardinality?: RelationFieldCardinality;
10761
+ }
10724
10762
  interface DetailField {
10725
10763
  label: string;
10726
10764
  value: React__default.ReactNode;
@@ -10756,12 +10794,12 @@ type FieldDef$2 = string | {
10756
10794
  header?: string;
10757
10795
  type?: string;
10758
10796
  values?: readonly string[];
10759
- relation?: string;
10797
+ relation?: DetailRelationMeta;
10760
10798
  } | {
10761
10799
  name: string;
10762
10800
  type: string;
10763
10801
  values?: readonly string[];
10764
- relation?: string;
10802
+ relation?: DetailRelationMeta;
10765
10803
  };
10766
10804
  interface DetailPanelStatus {
10767
10805
  label: string;
@@ -10889,8 +10927,10 @@ interface RelationConfig {
10889
10927
  entity: string;
10890
10928
  /** Field on target entity to display (defaults to 'name') */
10891
10929
  displayField?: string;
10892
- /** Cardinality: one-to-one or one-to-many */
10893
- cardinality?: "one" | "many";
10930
+ /** Cardinality of the relation — many-valued spellings drive the
10931
+ * multi-select picker below; anything else (including absent) stays
10932
+ * single-select. */
10933
+ cardinality?: RelationFieldCardinality;
10894
10934
  }
10895
10935
  /**
10896
10936
  * Validation-rule bundle for a schema field. Only `enum` is actually read
@@ -12,7 +12,7 @@ import { useTranslate } from '@almadar/ui/hooks';
12
12
  import { useUISlots, useTheme } from '@almadar/ui/context';
13
13
  import { evaluate, createMinimalContext } from '@almadar/evaluator';
14
14
  import { createContextFromBindings, interpolateValue } from '@almadar/runtime';
15
- import { mergeEntityFrame, isFileValue, isInlineTrait, isRenderBindingMarker, ANIMATION_NAMES } from '@almadar/core';
15
+ import { mergeEntityFrame, isFileValue, isInlineTrait, isSecretConfigType, isRenderBindingMarker, ANIMATION_NAMES } from '@almadar/core';
16
16
  import { createPortal } from 'react-dom';
17
17
  import { wrapCallbackForEvent } from '@almadar/runtime/ui';
18
18
  import { Link, Outlet, useLocation } from 'react-router-dom';
@@ -6286,11 +6286,32 @@ function isPlainObject(value) {
6286
6286
  if (typeof value === "function") return false;
6287
6287
  return true;
6288
6288
  }
6289
+ function subtreeHasMarker(value) {
6290
+ const cached = markerPresenceCache.get(value);
6291
+ if (cached !== void 0) return cached;
6292
+ let found = false;
6293
+ const children = Array.isArray(value) ? value : Object.values(value);
6294
+ for (const child of children) {
6295
+ if (isRenderBindingMarker(child)) {
6296
+ found = true;
6297
+ break;
6298
+ }
6299
+ if (Array.isArray(child) || isPlainObject(child)) {
6300
+ if (subtreeHasMarker(child)) {
6301
+ found = true;
6302
+ break;
6303
+ }
6304
+ }
6305
+ }
6306
+ markerPresenceCache.set(value, found);
6307
+ return found;
6308
+ }
6289
6309
  function walkValue(value, scopeTrait, entity, config, state) {
6290
6310
  if (isRenderBindingMarker(value)) {
6291
6311
  return { resolved: resolveMarkerExpression(value.expression, entity, config, state), changed: true };
6292
6312
  }
6293
6313
  if (Array.isArray(value)) {
6314
+ if (!subtreeHasMarker(value)) return { resolved: value, changed: false };
6294
6315
  const out = [];
6295
6316
  let changed = false;
6296
6317
  for (const item of value) {
@@ -6308,6 +6329,7 @@ function walkValue(value, scopeTrait, entity, config, state) {
6308
6329
  return changed ? { resolved: out, changed: true } : { resolved: value, changed: false };
6309
6330
  }
6310
6331
  if (isPlainObject(value)) {
6332
+ if (!subtreeHasMarker(value)) return { resolved: value, changed: false };
6311
6333
  const sourceTrait = value._sourceTrait;
6312
6334
  if (typeof sourceTrait === "string" && sourceTrait !== scopeTrait) {
6313
6335
  return { resolved: value, changed: false };
@@ -6333,9 +6355,11 @@ function resolveRenderBindingMarkers(props, scopeTrait, entity, config, state) {
6333
6355
  }
6334
6356
  return changed ? out : props;
6335
6357
  }
6358
+ var markerPresenceCache;
6336
6359
  var init_resolve_render_bindings = __esm({
6337
6360
  "lib/resolve-render-bindings.ts"() {
6338
6361
  "use client";
6362
+ markerPresenceCache = /* @__PURE__ */ new WeakMap();
6339
6363
  }
6340
6364
  });
6341
6365
  var sizeClasses6, minWidthClasses, lookStyles2, Modal;
@@ -8614,6 +8638,8 @@ function drawShape(ctx, shape, width, height, allShapes) {
8614
8638
  case "path": {
8615
8639
  if (!shape.path) break;
8616
8640
  const p = new Path2D(shape.path);
8641
+ ctx.lineJoin = "round";
8642
+ ctx.lineCap = "round";
8617
8643
  if (fill) {
8618
8644
  ctx.fillStyle = fill;
8619
8645
  ctx.fill(p);
@@ -8822,7 +8848,7 @@ var init_LearningCanvas = __esm({
8822
8848
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
8823
8849
  ctx.clearRect(0, 0, width, height);
8824
8850
  if (backgroundColor) {
8825
- ctx.fillStyle = backgroundColor;
8851
+ ctx.fillStyle = resolveColor2(backgroundColor, ctx, backgroundColor);
8826
8852
  ctx.fillRect(0, 0, width, height);
8827
8853
  }
8828
8854
  for (const shape of derivedShapes) {
@@ -25435,6 +25461,7 @@ function currentValue(decl, override) {
25435
25461
  function TextLikeControl({
25436
25462
  field,
25437
25463
  numeric,
25464
+ secret,
25438
25465
  value,
25439
25466
  onCommit
25440
25467
  }) {
@@ -25452,7 +25479,7 @@ function TextLikeControl({
25452
25479
  return /* @__PURE__ */ jsx(
25453
25480
  Input,
25454
25481
  {
25455
- inputType: numeric ? "number" : "text",
25482
+ inputType: secret ? "password" : numeric ? "number" : "text",
25456
25483
  value: draft,
25457
25484
  onChange: (e) => setDraft(e.target.value),
25458
25485
  onBlur: commit,
@@ -25513,6 +25540,8 @@ function FieldControl({
25513
25540
  );
25514
25541
  } else if (decl.type === "number") {
25515
25542
  control = /* @__PURE__ */ jsx(TextLikeControl, { field: name, numeric: true, value, onCommit: onChange });
25543
+ } else if (isSecretConfigType(decl.type)) {
25544
+ control = /* @__PURE__ */ jsx(TextLikeControl, { field: name, numeric: false, secret: true, value, onCommit: onChange });
25516
25545
  } else if (decl.type === "string") {
25517
25546
  control = /* @__PURE__ */ jsx(TextLikeControl, { field: name, numeric: false, value, onCommit: onChange });
25518
25547
  } else if (decl.type === "node") {
@@ -25552,7 +25581,7 @@ var init_PropertyInspector = __esm({
25552
25581
  init_JsonTreeEditor();
25553
25582
  init_NodeSlotEditor();
25554
25583
  TIER_ORDER = ["presentation", "domain", "policy", "infra", "internal"];
25555
- SCALAR_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "icon", "asset", "Asset"]);
25584
+ SCALAR_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "icon", "asset", "Asset", "secret"]);
25556
25585
  PropertyInspector = ({
25557
25586
  config,
25558
25587
  values,
@@ -25710,7 +25739,7 @@ function fileIcon(name) {
25710
25739
  return "file";
25711
25740
  }
25712
25741
  }
25713
- var TreeNodeItem, FileTree;
25742
+ var TreeNodeItem, FlatTreeNodeItem, FileTree;
25714
25743
  var init_FileTree = __esm({
25715
25744
  "components/core/molecules/FileTree.tsx"() {
25716
25745
  "use client";
@@ -25795,14 +25824,101 @@ var init_FileTree = __esm({
25795
25824
  )) })
25796
25825
  ] });
25797
25826
  };
25827
+ FlatTreeNodeItem = ({
25828
+ item,
25829
+ depth,
25830
+ indent,
25831
+ childrenByParent,
25832
+ onNodeSelect,
25833
+ defaultExpanded = false
25834
+ }) => {
25835
+ const [expanded, setExpanded] = useState(defaultExpanded || depth < 1);
25836
+ const children = childrenByParent.get(item.id);
25837
+ const hasChildren = !!children && children.length > 0;
25838
+ const handleClick = useCallback(() => {
25839
+ if (hasChildren) setExpanded((prev) => !prev);
25840
+ onNodeSelect?.(item.id);
25841
+ }, [hasChildren, item.id, onNodeSelect]);
25842
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
25843
+ /* @__PURE__ */ jsxs(
25844
+ Box,
25845
+ {
25846
+ className: "flex items-center gap-1.5 py-0.5 px-2 cursor-pointer rounded-sm transition-colors hover:bg-muted",
25847
+ style: { paddingLeft: depth * indent + 8 },
25848
+ onClick: handleClick,
25849
+ role: "treeitem",
25850
+ "aria-expanded": hasChildren ? expanded : void 0,
25851
+ children: [
25852
+ hasChildren ? /* @__PURE__ */ jsx(
25853
+ Icon,
25854
+ {
25855
+ name: expanded ? "chevron-down" : "chevron-right",
25856
+ size: "xs",
25857
+ className: "text-[var(--color-muted-foreground)] flex-shrink-0"
25858
+ }
25859
+ ) : /* @__PURE__ */ jsx(Box, { style: { width: 12, flexShrink: 0 } }),
25860
+ /* @__PURE__ */ jsx(
25861
+ Icon,
25862
+ {
25863
+ name: item.icon ?? (hasChildren ? expanded ? "folder-open" : "folder" : "file"),
25864
+ size: "xs",
25865
+ className: hasChildren ? "text-[var(--color-warning)]" : "text-[var(--color-muted-foreground)]"
25866
+ }
25867
+ ),
25868
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "truncate font-mono text-xs", children: item.label })
25869
+ ]
25870
+ }
25871
+ ),
25872
+ hasChildren && expanded && /* @__PURE__ */ jsx(Box, { role: "group", children: children.map((child) => /* @__PURE__ */ jsx(
25873
+ FlatTreeNodeItem,
25874
+ {
25875
+ item: child,
25876
+ depth: depth + 1,
25877
+ indent,
25878
+ childrenByParent,
25879
+ onNodeSelect
25880
+ },
25881
+ child.id
25882
+ )) })
25883
+ ] });
25884
+ };
25798
25885
  FileTree = ({
25799
25886
  tree,
25887
+ items,
25800
25888
  selectedPath,
25801
25889
  onFileSelect,
25890
+ onNodeSelect,
25802
25891
  className,
25803
25892
  indent = 16
25804
25893
  }) => {
25805
- if (tree.length === 0) return null;
25894
+ if (items) {
25895
+ if (items.length === 0) return null;
25896
+ const ids = new Set(items.map((node) => node.id));
25897
+ const childrenByParent = /* @__PURE__ */ new Map();
25898
+ const roots = [];
25899
+ for (const item of items) {
25900
+ if (item.parentId && ids.has(item.parentId)) {
25901
+ const siblings = childrenByParent.get(item.parentId);
25902
+ if (siblings) siblings.push(item);
25903
+ else childrenByParent.set(item.parentId, [item]);
25904
+ } else {
25905
+ roots.push(item);
25906
+ }
25907
+ }
25908
+ return /* @__PURE__ */ jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: roots.map((item) => /* @__PURE__ */ jsx(
25909
+ FlatTreeNodeItem,
25910
+ {
25911
+ item,
25912
+ depth: 0,
25913
+ indent,
25914
+ childrenByParent,
25915
+ onNodeSelect,
25916
+ defaultExpanded: true
25917
+ },
25918
+ item.id
25919
+ )) });
25920
+ }
25921
+ if (!tree || tree.length === 0) return null;
25806
25922
  return /* @__PURE__ */ jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: tree.map((node) => /* @__PURE__ */ jsx(
25807
25923
  TreeNodeItem,
25808
25924
  {
@@ -27455,7 +27571,7 @@ var init_debug = __esm({
27455
27571
  createLogger("almadar:ui:debug:game-state");
27456
27572
  }
27457
27573
  });
27458
- var isRelationsDebugEnabled, RelationSelect;
27574
+ var isRelationsDebugEnabled, MANY_CARDINALITIES, RelationSelect;
27459
27575
  var init_RelationSelect = __esm({
27460
27576
  "components/core/molecules/RelationSelect.tsx"() {
27461
27577
  "use client";
@@ -27469,6 +27585,11 @@ var init_RelationSelect = __esm({
27469
27585
  init_Typography();
27470
27586
  init_debug();
27471
27587
  isRelationsDebugEnabled = () => isDebugEnabled();
27588
+ MANY_CARDINALITIES = [
27589
+ "many",
27590
+ "one-to-many",
27591
+ "many-to-many"
27592
+ ];
27472
27593
  RelationSelect = ({
27473
27594
  value,
27474
27595
  onChange,
@@ -31540,6 +31661,9 @@ var init_MathCanvas = __esm({
31540
31661
  showAxes = true,
31541
31662
  showGrid = true,
31542
31663
  gridStep = 1,
31664
+ backgroundColor,
31665
+ gridColor = "var(--color-border, #9ca3af)",
31666
+ axisColor = "var(--color-muted-foreground, #374151)",
31543
31667
  showTickLabels = false,
31544
31668
  showCurveLabels = false,
31545
31669
  curves = [],
@@ -31562,17 +31686,21 @@ var init_MathCanvas = __esm({
31562
31686
  error
31563
31687
  }) => {
31564
31688
  const eventBus = useEventBus();
31689
+ const keyMapKey = keyMap ? JSON.stringify(keyMap) : null;
31690
+ const keyUpMapKey = keyUpMap ? JSON.stringify(keyUpMap) : null;
31691
+ const stableKeyMap = useMemo(() => keyMap, [keyMapKey]);
31692
+ const stableKeyUpMap = useMemo(() => keyUpMap, [keyUpMapKey]);
31565
31693
  useEffect(() => {
31566
- if (!keyMap && !keyUpMap) return;
31694
+ if (!stableKeyMap && !stableKeyUpMap) return;
31567
31695
  const onDown = (e) => {
31568
- const ev = keyMap?.[e.code];
31696
+ const ev = stableKeyMap?.[e.code];
31569
31697
  if (ev) {
31570
31698
  eventBus.emit(`UI:${ev}`, {});
31571
31699
  e.preventDefault();
31572
31700
  }
31573
31701
  };
31574
31702
  const onUp = (e) => {
31575
- const ev = keyUpMap?.[e.code];
31703
+ const ev = stableKeyUpMap?.[e.code];
31576
31704
  if (ev) eventBus.emit(`UI:${ev}`, {});
31577
31705
  };
31578
31706
  window.addEventListener("keydown", onDown);
@@ -31581,7 +31709,7 @@ var init_MathCanvas = __esm({
31581
31709
  window.removeEventListener("keydown", onDown);
31582
31710
  window.removeEventListener("keyup", onUp);
31583
31711
  };
31584
- }, [keyMap, keyUpMap, eventBus]);
31712
+ }, [stableKeyMap, stableKeyUpMap, eventBus]);
31585
31713
  const derivedShapes = useMemo(() => {
31586
31714
  const out = [];
31587
31715
  const margin = 24;
@@ -31594,11 +31722,11 @@ var init_MathCanvas = __esm({
31594
31722
  if (showGrid) {
31595
31723
  for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep) {
31596
31724
  const px = mapX(x);
31597
- out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color: "#9ca3af", opacity: 0.35, lineWidth: 1 });
31725
+ out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color: gridColor, opacity: 0.35, lineWidth: 1 });
31598
31726
  }
31599
31727
  for (let y = Math.ceil(yMin / gridStep) * gridStep; y <= yMax; y += gridStep) {
31600
31728
  const py = mapY(y);
31601
- out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: "#9ca3af", opacity: 0.35, lineWidth: 1 });
31729
+ out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: gridColor, opacity: 0.35, lineWidth: 1 });
31602
31730
  }
31603
31731
  }
31604
31732
  if (showTickLabels) {
@@ -31669,8 +31797,8 @@ var init_MathCanvas = __esm({
31669
31797
  });
31670
31798
  }
31671
31799
  if (showAxes) {
31672
- out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: "#374151", lineWidth: 2 });
31673
- out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: "#374151", lineWidth: 2 });
31800
+ out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: axisColor, lineWidth: 2 });
31801
+ out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: axisColor, lineWidth: 2 });
31674
31802
  }
31675
31803
  for (const guide of guides) {
31676
31804
  const color = guide.color ?? "#9ca3af";
@@ -31693,23 +31821,36 @@ var init_MathCanvas = __esm({
31693
31821
  }
31694
31822
  for (const curve of curves) {
31695
31823
  if (!curve.samples || curve.samples.length < 2) continue;
31824
+ let d = "";
31825
+ let penDown = false;
31696
31826
  let lastInRange;
31697
31827
  for (let i = 1; i < curve.samples.length; i++) {
31698
31828
  const a = curve.samples[i - 1];
31699
31829
  const b = curve.samples[i];
31700
- if (a.x < xMin || a.x > xMax || b.x < xMin || b.x > xMax) continue;
31701
- out.push({
31702
- type: "line",
31703
- x1: mapX(a.x),
31704
- y1: mapY(a.y),
31705
- x2: mapX(b.x),
31706
- y2: mapY(b.y),
31707
- color: curve.color ?? "#2563eb",
31708
- lineWidth: 2,
31709
- dash: curve.dash
31710
- });
31711
- lastInRange = b;
31712
- }
31830
+ if (a.x < xMin && b.x < xMin || a.x > xMax && b.x > xMax) {
31831
+ penDown = false;
31832
+ continue;
31833
+ }
31834
+ const clip = (p, q, xLim) => {
31835
+ const t = q.x === p.x ? 0 : (xLim - p.x) / (q.x - p.x);
31836
+ return { x: xLim, y: p.y + t * (q.y - p.y) };
31837
+ };
31838
+ const ca = a.x < xMin ? clip(a, b, xMin) : a.x > xMax ? clip(a, b, xMax) : a;
31839
+ const cb = b.x < xMin ? clip(a, b, xMin) : b.x > xMax ? clip(a, b, xMax) : b;
31840
+ const pax = mapX(ca.x);
31841
+ const pay = mapY(ca.y);
31842
+ d += `${penDown ? "L" : "M"} ${pax} ${pay} L ${mapX(cb.x)} ${mapY(cb.y)} `;
31843
+ penDown = true;
31844
+ if (b.x >= xMin && b.x <= xMax) lastInRange = b;
31845
+ }
31846
+ if (!d) continue;
31847
+ out.push({
31848
+ type: "path",
31849
+ path: d,
31850
+ color: curve.color ?? "#2563eb",
31851
+ lineWidth: 2,
31852
+ dash: curve.dash
31853
+ });
31713
31854
  if (showCurveLabels && curve.label && lastInRange) {
31714
31855
  out.push({
31715
31856
  type: "text",
@@ -31824,6 +31965,8 @@ var init_MathCanvas = __esm({
31824
31965
  showAxes,
31825
31966
  showGrid,
31826
31967
  gridStep,
31968
+ gridColor,
31969
+ axisColor,
31827
31970
  showTickLabels,
31828
31971
  showCurveLabels,
31829
31972
  curves,
@@ -31843,6 +31986,7 @@ var init_MathCanvas = __esm({
31843
31986
  {
31844
31987
  width,
31845
31988
  height,
31989
+ backgroundColor,
31846
31990
  shapes: derivedShapes,
31847
31991
  readouts,
31848
31992
  traces,
@@ -44227,6 +44371,9 @@ function determineInputType(field) {
44227
44371
  if (field.type === "relation" || field.relation) {
44228
44372
  return "relation";
44229
44373
  }
44374
+ if (field.type === "array") {
44375
+ return "array";
44376
+ }
44230
44377
  if (field.type === "enum" || field.values || getEnumOptions(field).length > 0) {
44231
44378
  return "select";
44232
44379
  }
@@ -44302,6 +44449,7 @@ var init_Form = __esm({
44302
44449
  init_Typography();
44303
44450
  init_Icon();
44304
44451
  init_RelationSelect();
44452
+ init_TagInput();
44305
44453
  init_UploadDropZone();
44306
44454
  init_Alert();
44307
44455
  init_useEventBus();
@@ -44381,7 +44529,7 @@ var init_Form = __esm({
44381
44529
  values: "values" in f3 ? f3.values : void 0,
44382
44530
  min: f3.min,
44383
44531
  max: f3.max,
44384
- relation: "relation" in f3 ? { entity: f3.relation.entity } : void 0
44532
+ relation: "relation" in f3 ? { entity: f3.relation.entity, cardinality: f3.relation.cardinality } : void 0
44385
44533
  })
44386
44534
  );
44387
44535
  }, [entity, fields]);
@@ -44616,7 +44764,7 @@ var init_Form = __esm({
44616
44764
  values: "values" in entityField ? entityField.values : void 0,
44617
44765
  min: entityField.min,
44618
44766
  max: entityField.max,
44619
- relation: "relation" in entityField ? { entity: entityField.relation.entity } : void 0
44767
+ relation: "relation" in entityField ? { entity: entityField.relation.entity, cardinality: entityField.relation.cardinality } : void 0
44620
44768
  };
44621
44769
  }
44622
44770
  return { name: field, type: "string" };
@@ -44728,6 +44876,22 @@ var init_Form = __esm({
44728
44876
  case "relation": {
44729
44877
  const relationOptions = relationsData[fieldName] || [];
44730
44878
  const relationLoading = relationsLoading[fieldName] || false;
44879
+ if (field.relation?.cardinality !== void 0 && MANY_CARDINALITIES.includes(field.relation.cardinality)) {
44880
+ const selectedValues = Array.isArray(currentValue2) ? currentValue2.map((v) => String(v)) : [];
44881
+ return /* @__PURE__ */ jsx(
44882
+ Select,
44883
+ {
44884
+ ...commonProps,
44885
+ multiple: true,
44886
+ searchable: true,
44887
+ clearable: true,
44888
+ options: [...relationOptions],
44889
+ value: selectedValues,
44890
+ onValueChange: (value) => handleChange(fieldName, Array.isArray(value) ? value : [value]),
44891
+ placeholder: field.placeholder || `Select ${label}...`
44892
+ }
44893
+ );
44894
+ }
44731
44895
  return /* @__PURE__ */ jsx(
44732
44896
  RelationSelect,
44733
44897
  {
@@ -44742,6 +44906,18 @@ var init_Form = __esm({
44742
44906
  }
44743
44907
  );
44744
44908
  }
44909
+ case "array": {
44910
+ const arrayValue = Array.isArray(currentValue2) ? currentValue2.map((v) => String(v)) : currentValue2 != null && currentValue2 !== "" ? [String(currentValue2)] : [];
44911
+ return /* @__PURE__ */ jsx(
44912
+ TagInput,
44913
+ {
44914
+ placeholder: field.placeholder,
44915
+ disabled: isLoading,
44916
+ value: arrayValue,
44917
+ onChange: (next) => handleChange(fieldName, [...next])
44918
+ }
44919
+ );
44920
+ }
44745
44921
  case "number":
44746
44922
  return /* @__PURE__ */ jsx(
44747
44923
  Input,
@@ -50291,7 +50467,10 @@ function enrichFormFields(fields, entityDef) {
50291
50467
  enriched.values = entityField.enumValues;
50292
50468
  }
50293
50469
  if (entityField.relation) {
50294
- enriched.relation = entityField.relation.entity;
50470
+ enriched.relation = {
50471
+ entity: entityField.relation.entity,
50472
+ cardinality: entityField.relation.cardinality
50473
+ };
50295
50474
  }
50296
50475
  return enriched;
50297
50476
  }
@@ -50319,7 +50498,10 @@ function enrichFormFields(fields, entityDef) {
50319
50498
  }
50320
50499
  }
50321
50500
  if (!obj.relation && entityField.relation) {
50322
- enriched.relation = entityField.relation.entity;
50501
+ enriched.relation = {
50502
+ entity: entityField.relation.entity,
50503
+ cardinality: entityField.relation.cardinality
50504
+ };
50323
50505
  }
50324
50506
  return enriched;
50325
50507
  }
@@ -50334,7 +50516,12 @@ function enrichDetailFields(fields, entityDef) {
50334
50516
  const meta = { type: entityField.type };
50335
50517
  const values = entityField.values ?? entityField.enumValues;
50336
50518
  if (values && values.length > 0) meta.values = values;
50337
- if (entityField.relation) meta.relation = entityField.relation.entity;
50519
+ if (entityField.relation) {
50520
+ meta.relation = {
50521
+ entity: entityField.relation.entity,
50522
+ cardinality: entityField.relation.cardinality
50523
+ };
50524
+ }
50338
50525
  return meta;
50339
50526
  };
50340
50527
  return fields.map((field) => {
@@ -50888,6 +51075,32 @@ function isPlainConfigObject(value) {
50888
51075
  const proto = Object.getPrototypeOf(value);
50889
51076
  return proto === Object.prototype || proto === null;
50890
51077
  }
51078
+ function subtreeHasTraitRef(value) {
51079
+ const cached = traitRefPresenceCache.get(value);
51080
+ if (cached !== void 0) return cached;
51081
+ let found = false;
51082
+ const children = Array.isArray(value) ? value : Object.values(value);
51083
+ for (const child of children) {
51084
+ if (typeof child === "string" && TRAIT_BINDING_RE.test(child)) {
51085
+ found = true;
51086
+ break;
51087
+ }
51088
+ if (isRenderBindingMarker(child)) continue;
51089
+ if (Array.isArray(child)) {
51090
+ if (subtreeHasTraitRef(child)) {
51091
+ found = true;
51092
+ break;
51093
+ }
51094
+ } else if (child !== null && typeof child === "object" && isPlainConfigObject(child)) {
51095
+ if (subtreeHasTraitRef(child)) {
51096
+ found = true;
51097
+ break;
51098
+ }
51099
+ }
51100
+ }
51101
+ traitRefPresenceCache.set(value, found);
51102
+ return found;
51103
+ }
50891
51104
  function substituteTraitRefsDeep(value, pathKey) {
50892
51105
  if (isRenderBindingMarker(value)) return value;
50893
51106
  if (typeof value === "string") {
@@ -50902,11 +51115,13 @@ function substituteTraitRefsDeep(value, pathKey) {
50902
51115
  return value;
50903
51116
  }
50904
51117
  if (Array.isArray(value)) {
51118
+ if (!subtreeHasTraitRef(value)) return value;
50905
51119
  return value.map(
50906
51120
  (item, i) => substituteTraitRefsDeep(item, `${pathKey}[${i}]`)
50907
51121
  );
50908
51122
  }
50909
51123
  if (typeof value === "object" && isPlainConfigObject(value)) {
51124
+ if (!subtreeHasTraitRef(value)) return value;
50910
51125
  const out = {};
50911
51126
  for (const [k, v] of Object.entries(value)) {
50912
51127
  out[k] = substituteTraitRefsDeep(v, `${pathKey}.${k}`);
@@ -51195,7 +51410,7 @@ function UISlotRenderer({
51195
51410
  }
51196
51411
  return wrapped;
51197
51412
  }
51198
- var scopeWrapLog, TRAIT_BINDING_RE, SuspenseConfigContext, SlotContainedContext, SLOT_SKELETON_MAP, SELF_OVERLAY_PATTERNS, CONTENT_NODE_SLOTS, PATTERNS_WITH_CHILDREN;
51413
+ var scopeWrapLog, TRAIT_BINDING_RE, SuspenseConfigContext, SlotContainedContext, SLOT_SKELETON_MAP, SELF_OVERLAY_PATTERNS, CONTENT_NODE_SLOTS, PATTERNS_WITH_CHILDREN, traitRefPresenceCache;
51199
51414
  var init_UISlotRenderer = __esm({
51200
51415
  "components/core/organisms/UISlotRenderer.tsx"() {
51201
51416
  "use client";
@@ -51269,6 +51484,7 @@ var init_UISlotRenderer = __esm({
51269
51484
  "alert",
51270
51485
  "dialog"
51271
51486
  ]);
51487
+ traitRefPresenceCache = /* @__PURE__ */ new WeakMap();
51272
51488
  UISlotRenderer.displayName = "UISlotRenderer";
51273
51489
  }
51274
51490
  });