@almadar/ui 5.157.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.
@@ -6361,11 +6361,32 @@ function isPlainObject(value) {
6361
6361
  if (typeof value === "function") return false;
6362
6362
  return true;
6363
6363
  }
6364
+ function subtreeHasMarker(value) {
6365
+ const cached = markerPresenceCache.get(value);
6366
+ if (cached !== void 0) return cached;
6367
+ let found = false;
6368
+ const children = Array.isArray(value) ? value : Object.values(value);
6369
+ for (const child of children) {
6370
+ if (core.isRenderBindingMarker(child)) {
6371
+ found = true;
6372
+ break;
6373
+ }
6374
+ if (Array.isArray(child) || isPlainObject(child)) {
6375
+ if (subtreeHasMarker(child)) {
6376
+ found = true;
6377
+ break;
6378
+ }
6379
+ }
6380
+ }
6381
+ markerPresenceCache.set(value, found);
6382
+ return found;
6383
+ }
6364
6384
  function walkValue(value, scopeTrait, entity, config, state) {
6365
6385
  if (core.isRenderBindingMarker(value)) {
6366
6386
  return { resolved: resolveMarkerExpression(value.expression, entity, config, state), changed: true };
6367
6387
  }
6368
6388
  if (Array.isArray(value)) {
6389
+ if (!subtreeHasMarker(value)) return { resolved: value, changed: false };
6369
6390
  const out = [];
6370
6391
  let changed = false;
6371
6392
  for (const item of value) {
@@ -6383,6 +6404,7 @@ function walkValue(value, scopeTrait, entity, config, state) {
6383
6404
  return changed ? { resolved: out, changed: true } : { resolved: value, changed: false };
6384
6405
  }
6385
6406
  if (isPlainObject(value)) {
6407
+ if (!subtreeHasMarker(value)) return { resolved: value, changed: false };
6386
6408
  const sourceTrait = value._sourceTrait;
6387
6409
  if (typeof sourceTrait === "string" && sourceTrait !== scopeTrait) {
6388
6410
  return { resolved: value, changed: false };
@@ -6408,9 +6430,11 @@ function resolveRenderBindingMarkers(props, scopeTrait, entity, config, state) {
6408
6430
  }
6409
6431
  return changed ? out : props;
6410
6432
  }
6433
+ var markerPresenceCache;
6411
6434
  var init_resolve_render_bindings = __esm({
6412
6435
  "lib/resolve-render-bindings.ts"() {
6413
6436
  "use client";
6437
+ markerPresenceCache = /* @__PURE__ */ new WeakMap();
6414
6438
  }
6415
6439
  });
6416
6440
  var sizeClasses6, minWidthClasses, lookStyles2; exports.Modal = void 0;
@@ -25790,7 +25814,7 @@ function fileIcon(name) {
25790
25814
  return "file";
25791
25815
  }
25792
25816
  }
25793
- var TreeNodeItem; exports.FileTree = void 0;
25817
+ var TreeNodeItem, FlatTreeNodeItem; exports.FileTree = void 0;
25794
25818
  var init_FileTree = __esm({
25795
25819
  "components/core/molecules/FileTree.tsx"() {
25796
25820
  "use client";
@@ -25875,14 +25899,101 @@ var init_FileTree = __esm({
25875
25899
  )) })
25876
25900
  ] });
25877
25901
  };
25902
+ FlatTreeNodeItem = ({
25903
+ item,
25904
+ depth,
25905
+ indent,
25906
+ childrenByParent,
25907
+ onNodeSelect,
25908
+ defaultExpanded = false
25909
+ }) => {
25910
+ const [expanded, setExpanded] = React77.useState(defaultExpanded || depth < 1);
25911
+ const children = childrenByParent.get(item.id);
25912
+ const hasChildren = !!children && children.length > 0;
25913
+ const handleClick = React77.useCallback(() => {
25914
+ if (hasChildren) setExpanded((prev) => !prev);
25915
+ onNodeSelect?.(item.id);
25916
+ }, [hasChildren, item.id, onNodeSelect]);
25917
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
25918
+ /* @__PURE__ */ jsxRuntime.jsxs(
25919
+ exports.Box,
25920
+ {
25921
+ className: "flex items-center gap-1.5 py-0.5 px-2 cursor-pointer rounded-sm transition-colors hover:bg-muted",
25922
+ style: { paddingLeft: depth * indent + 8 },
25923
+ onClick: handleClick,
25924
+ role: "treeitem",
25925
+ "aria-expanded": hasChildren ? expanded : void 0,
25926
+ children: [
25927
+ hasChildren ? /* @__PURE__ */ jsxRuntime.jsx(
25928
+ exports.Icon,
25929
+ {
25930
+ name: expanded ? "chevron-down" : "chevron-right",
25931
+ size: "xs",
25932
+ className: "text-[var(--color-muted-foreground)] flex-shrink-0"
25933
+ }
25934
+ ) : /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { style: { width: 12, flexShrink: 0 } }),
25935
+ /* @__PURE__ */ jsxRuntime.jsx(
25936
+ exports.Icon,
25937
+ {
25938
+ name: item.icon ?? (hasChildren ? expanded ? "folder-open" : "folder" : "file"),
25939
+ size: "xs",
25940
+ className: hasChildren ? "text-[var(--color-warning)]" : "text-[var(--color-muted-foreground)]"
25941
+ }
25942
+ ),
25943
+ /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "caption", className: "truncate font-mono text-xs", children: item.label })
25944
+ ]
25945
+ }
25946
+ ),
25947
+ hasChildren && expanded && /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { role: "group", children: children.map((child) => /* @__PURE__ */ jsxRuntime.jsx(
25948
+ FlatTreeNodeItem,
25949
+ {
25950
+ item: child,
25951
+ depth: depth + 1,
25952
+ indent,
25953
+ childrenByParent,
25954
+ onNodeSelect
25955
+ },
25956
+ child.id
25957
+ )) })
25958
+ ] });
25959
+ };
25878
25960
  exports.FileTree = ({
25879
25961
  tree,
25962
+ items,
25880
25963
  selectedPath,
25881
25964
  onFileSelect,
25965
+ onNodeSelect,
25882
25966
  className,
25883
25967
  indent = 16
25884
25968
  }) => {
25885
- if (tree.length === 0) return null;
25969
+ if (items) {
25970
+ if (items.length === 0) return null;
25971
+ const ids = new Set(items.map((node) => node.id));
25972
+ const childrenByParent = /* @__PURE__ */ new Map();
25973
+ const roots = [];
25974
+ for (const item of items) {
25975
+ if (item.parentId && ids.has(item.parentId)) {
25976
+ const siblings = childrenByParent.get(item.parentId);
25977
+ if (siblings) siblings.push(item);
25978
+ else childrenByParent.set(item.parentId, [item]);
25979
+ } else {
25980
+ roots.push(item);
25981
+ }
25982
+ }
25983
+ return /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: roots.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
25984
+ FlatTreeNodeItem,
25985
+ {
25986
+ item,
25987
+ depth: 0,
25988
+ indent,
25989
+ childrenByParent,
25990
+ onNodeSelect,
25991
+ defaultExpanded: true
25992
+ },
25993
+ item.id
25994
+ )) });
25995
+ }
25996
+ if (!tree || tree.length === 0) return null;
25886
25997
  return /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: tree.map((node) => /* @__PURE__ */ jsxRuntime.jsx(
25887
25998
  TreeNodeItem,
25888
25999
  {
@@ -27535,7 +27646,7 @@ var init_debug = __esm({
27535
27646
  logger.createLogger("almadar:ui:debug:game-state");
27536
27647
  }
27537
27648
  });
27538
- var isRelationsDebugEnabled; exports.RelationSelect = void 0;
27649
+ var isRelationsDebugEnabled, MANY_CARDINALITIES; exports.RelationSelect = void 0;
27539
27650
  var init_RelationSelect = __esm({
27540
27651
  "components/core/molecules/RelationSelect.tsx"() {
27541
27652
  "use client";
@@ -27549,6 +27660,11 @@ var init_RelationSelect = __esm({
27549
27660
  init_Typography();
27550
27661
  init_debug();
27551
27662
  isRelationsDebugEnabled = () => isDebugEnabled();
27663
+ MANY_CARDINALITIES = [
27664
+ "many",
27665
+ "one-to-many",
27666
+ "many-to-many"
27667
+ ];
27552
27668
  exports.RelationSelect = ({
27553
27669
  value,
27554
27670
  onChange,
@@ -31645,17 +31761,21 @@ var init_MathCanvas = __esm({
31645
31761
  error
31646
31762
  }) => {
31647
31763
  const eventBus = useEventBus();
31764
+ const keyMapKey = keyMap ? JSON.stringify(keyMap) : null;
31765
+ const keyUpMapKey = keyUpMap ? JSON.stringify(keyUpMap) : null;
31766
+ const stableKeyMap = React77.useMemo(() => keyMap, [keyMapKey]);
31767
+ const stableKeyUpMap = React77.useMemo(() => keyUpMap, [keyUpMapKey]);
31648
31768
  React77.useEffect(() => {
31649
- if (!keyMap && !keyUpMap) return;
31769
+ if (!stableKeyMap && !stableKeyUpMap) return;
31650
31770
  const onDown = (e) => {
31651
- const ev = keyMap?.[e.code];
31771
+ const ev = stableKeyMap?.[e.code];
31652
31772
  if (ev) {
31653
31773
  eventBus.emit(`UI:${ev}`, {});
31654
31774
  e.preventDefault();
31655
31775
  }
31656
31776
  };
31657
31777
  const onUp = (e) => {
31658
- const ev = keyUpMap?.[e.code];
31778
+ const ev = stableKeyUpMap?.[e.code];
31659
31779
  if (ev) eventBus.emit(`UI:${ev}`, {});
31660
31780
  };
31661
31781
  window.addEventListener("keydown", onDown);
@@ -31664,7 +31784,7 @@ var init_MathCanvas = __esm({
31664
31784
  window.removeEventListener("keydown", onDown);
31665
31785
  window.removeEventListener("keyup", onUp);
31666
31786
  };
31667
- }, [keyMap, keyUpMap, eventBus]);
31787
+ }, [stableKeyMap, stableKeyUpMap, eventBus]);
31668
31788
  const derivedShapes = React77.useMemo(() => {
31669
31789
  const out = [];
31670
31790
  const margin = 24;
@@ -44326,6 +44446,9 @@ function determineInputType(field) {
44326
44446
  if (field.type === "relation" || field.relation) {
44327
44447
  return "relation";
44328
44448
  }
44449
+ if (field.type === "array") {
44450
+ return "array";
44451
+ }
44329
44452
  if (field.type === "enum" || field.values || getEnumOptions(field).length > 0) {
44330
44453
  return "select";
44331
44454
  }
@@ -44401,6 +44524,7 @@ var init_Form = __esm({
44401
44524
  init_Typography();
44402
44525
  init_Icon();
44403
44526
  init_RelationSelect();
44527
+ init_TagInput();
44404
44528
  init_UploadDropZone();
44405
44529
  init_Alert();
44406
44530
  init_useEventBus();
@@ -44480,7 +44604,7 @@ var init_Form = __esm({
44480
44604
  values: "values" in f3 ? f3.values : void 0,
44481
44605
  min: f3.min,
44482
44606
  max: f3.max,
44483
- relation: "relation" in f3 ? { entity: f3.relation.entity } : void 0
44607
+ relation: "relation" in f3 ? { entity: f3.relation.entity, cardinality: f3.relation.cardinality } : void 0
44484
44608
  })
44485
44609
  );
44486
44610
  }, [entity, fields]);
@@ -44715,7 +44839,7 @@ var init_Form = __esm({
44715
44839
  values: "values" in entityField ? entityField.values : void 0,
44716
44840
  min: entityField.min,
44717
44841
  max: entityField.max,
44718
- relation: "relation" in entityField ? { entity: entityField.relation.entity } : void 0
44842
+ relation: "relation" in entityField ? { entity: entityField.relation.entity, cardinality: entityField.relation.cardinality } : void 0
44719
44843
  };
44720
44844
  }
44721
44845
  return { name: field, type: "string" };
@@ -44827,6 +44951,22 @@ var init_Form = __esm({
44827
44951
  case "relation": {
44828
44952
  const relationOptions = relationsData[fieldName] || [];
44829
44953
  const relationLoading = relationsLoading[fieldName] || false;
44954
+ if (field.relation?.cardinality !== void 0 && MANY_CARDINALITIES.includes(field.relation.cardinality)) {
44955
+ const selectedValues = Array.isArray(currentValue2) ? currentValue2.map((v) => String(v)) : [];
44956
+ return /* @__PURE__ */ jsxRuntime.jsx(
44957
+ exports.Select,
44958
+ {
44959
+ ...commonProps,
44960
+ multiple: true,
44961
+ searchable: true,
44962
+ clearable: true,
44963
+ options: [...relationOptions],
44964
+ value: selectedValues,
44965
+ onValueChange: (value) => handleChange(fieldName, Array.isArray(value) ? value : [value]),
44966
+ placeholder: field.placeholder || `Select ${label}...`
44967
+ }
44968
+ );
44969
+ }
44830
44970
  return /* @__PURE__ */ jsxRuntime.jsx(
44831
44971
  exports.RelationSelect,
44832
44972
  {
@@ -44841,6 +44981,18 @@ var init_Form = __esm({
44841
44981
  }
44842
44982
  );
44843
44983
  }
44984
+ case "array": {
44985
+ const arrayValue = Array.isArray(currentValue2) ? currentValue2.map((v) => String(v)) : currentValue2 != null && currentValue2 !== "" ? [String(currentValue2)] : [];
44986
+ return /* @__PURE__ */ jsxRuntime.jsx(
44987
+ exports.TagInput,
44988
+ {
44989
+ placeholder: field.placeholder,
44990
+ disabled: isLoading,
44991
+ value: arrayValue,
44992
+ onChange: (next) => handleChange(fieldName, [...next])
44993
+ }
44994
+ );
44995
+ }
44844
44996
  case "number":
44845
44997
  return /* @__PURE__ */ jsxRuntime.jsx(
44846
44998
  exports.Input,
@@ -50390,7 +50542,10 @@ function enrichFormFields(fields, entityDef) {
50390
50542
  enriched.values = entityField.enumValues;
50391
50543
  }
50392
50544
  if (entityField.relation) {
50393
- enriched.relation = entityField.relation.entity;
50545
+ enriched.relation = {
50546
+ entity: entityField.relation.entity,
50547
+ cardinality: entityField.relation.cardinality
50548
+ };
50394
50549
  }
50395
50550
  return enriched;
50396
50551
  }
@@ -50418,7 +50573,10 @@ function enrichFormFields(fields, entityDef) {
50418
50573
  }
50419
50574
  }
50420
50575
  if (!obj.relation && entityField.relation) {
50421
- enriched.relation = entityField.relation.entity;
50576
+ enriched.relation = {
50577
+ entity: entityField.relation.entity,
50578
+ cardinality: entityField.relation.cardinality
50579
+ };
50422
50580
  }
50423
50581
  return enriched;
50424
50582
  }
@@ -50433,7 +50591,12 @@ function enrichDetailFields(fields, entityDef) {
50433
50591
  const meta = { type: entityField.type };
50434
50592
  const values = entityField.values ?? entityField.enumValues;
50435
50593
  if (values && values.length > 0) meta.values = values;
50436
- if (entityField.relation) meta.relation = entityField.relation.entity;
50594
+ if (entityField.relation) {
50595
+ meta.relation = {
50596
+ entity: entityField.relation.entity,
50597
+ cardinality: entityField.relation.cardinality
50598
+ };
50599
+ }
50437
50600
  return meta;
50438
50601
  };
50439
50602
  return fields.map((field) => {
@@ -50987,6 +51150,32 @@ function isPlainConfigObject(value) {
50987
51150
  const proto = Object.getPrototypeOf(value);
50988
51151
  return proto === Object.prototype || proto === null;
50989
51152
  }
51153
+ function subtreeHasTraitRef(value) {
51154
+ const cached = traitRefPresenceCache.get(value);
51155
+ if (cached !== void 0) return cached;
51156
+ let found = false;
51157
+ const children = Array.isArray(value) ? value : Object.values(value);
51158
+ for (const child of children) {
51159
+ if (typeof child === "string" && TRAIT_BINDING_RE.test(child)) {
51160
+ found = true;
51161
+ break;
51162
+ }
51163
+ if (core.isRenderBindingMarker(child)) continue;
51164
+ if (Array.isArray(child)) {
51165
+ if (subtreeHasTraitRef(child)) {
51166
+ found = true;
51167
+ break;
51168
+ }
51169
+ } else if (child !== null && typeof child === "object" && isPlainConfigObject(child)) {
51170
+ if (subtreeHasTraitRef(child)) {
51171
+ found = true;
51172
+ break;
51173
+ }
51174
+ }
51175
+ }
51176
+ traitRefPresenceCache.set(value, found);
51177
+ return found;
51178
+ }
50990
51179
  function substituteTraitRefsDeep(value, pathKey) {
50991
51180
  if (core.isRenderBindingMarker(value)) return value;
50992
51181
  if (typeof value === "string") {
@@ -51001,11 +51190,13 @@ function substituteTraitRefsDeep(value, pathKey) {
51001
51190
  return value;
51002
51191
  }
51003
51192
  if (Array.isArray(value)) {
51193
+ if (!subtreeHasTraitRef(value)) return value;
51004
51194
  return value.map(
51005
51195
  (item, i) => substituteTraitRefsDeep(item, `${pathKey}[${i}]`)
51006
51196
  );
51007
51197
  }
51008
51198
  if (typeof value === "object" && isPlainConfigObject(value)) {
51199
+ if (!subtreeHasTraitRef(value)) return value;
51009
51200
  const out = {};
51010
51201
  for (const [k, v] of Object.entries(value)) {
51011
51202
  out[k] = substituteTraitRefsDeep(v, `${pathKey}.${k}`);
@@ -51294,7 +51485,7 @@ function UISlotRenderer({
51294
51485
  }
51295
51486
  return wrapped;
51296
51487
  }
51297
- var scopeWrapLog, TRAIT_BINDING_RE, SuspenseConfigContext, SlotContainedContext, SLOT_SKELETON_MAP, SELF_OVERLAY_PATTERNS, CONTENT_NODE_SLOTS, PATTERNS_WITH_CHILDREN;
51488
+ var scopeWrapLog, TRAIT_BINDING_RE, SuspenseConfigContext, SlotContainedContext, SLOT_SKELETON_MAP, SELF_OVERLAY_PATTERNS, CONTENT_NODE_SLOTS, PATTERNS_WITH_CHILDREN, traitRefPresenceCache;
51298
51489
  var init_UISlotRenderer = __esm({
51299
51490
  "components/core/organisms/UISlotRenderer.tsx"() {
51300
51491
  "use client";
@@ -51368,6 +51559,7 @@ var init_UISlotRenderer = __esm({
51368
51559
  "alert",
51369
51560
  "dialog"
51370
51561
  ]);
51562
+ traitRefPresenceCache = /* @__PURE__ */ new WeakMap();
51371
51563
  UISlotRenderer.displayName = "UISlotRenderer";
51372
51564
  }
51373
51565
  });
@@ -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;
@@ -10727,6 +10752,13 @@ declare const StatCard: React__default.FC<StatCardProps>;
10727
10752
  * Extends DisplayStateProps (see ./types.ts) and declares `entity?: EntityRow`.
10728
10753
  */
10729
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
+ }
10730
10762
  interface DetailField {
10731
10763
  label: string;
10732
10764
  value: React__default.ReactNode;
@@ -10762,12 +10794,12 @@ type FieldDef$2 = string | {
10762
10794
  header?: string;
10763
10795
  type?: string;
10764
10796
  values?: readonly string[];
10765
- relation?: string;
10797
+ relation?: DetailRelationMeta;
10766
10798
  } | {
10767
10799
  name: string;
10768
10800
  type: string;
10769
10801
  values?: readonly string[];
10770
- relation?: string;
10802
+ relation?: DetailRelationMeta;
10771
10803
  };
10772
10804
  interface DetailPanelStatus {
10773
10805
  label: string;
@@ -10895,8 +10927,10 @@ interface RelationConfig {
10895
10927
  entity: string;
10896
10928
  /** Field on target entity to display (defaults to 'name') */
10897
10929
  displayField?: string;
10898
- /** Cardinality: one-to-one or one-to-many */
10899
- 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;
10900
10934
  }
10901
10935
  /**
10902
10936
  * Validation-rule bundle for a schema field. Only `enum` is actually read
@@ -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;
@@ -10727,6 +10752,13 @@ declare const StatCard: React__default.FC<StatCardProps>;
10727
10752
  * Extends DisplayStateProps (see ./types.ts) and declares `entity?: EntityRow`.
10728
10753
  */
10729
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
+ }
10730
10762
  interface DetailField {
10731
10763
  label: string;
10732
10764
  value: React__default.ReactNode;
@@ -10762,12 +10794,12 @@ type FieldDef$2 = string | {
10762
10794
  header?: string;
10763
10795
  type?: string;
10764
10796
  values?: readonly string[];
10765
- relation?: string;
10797
+ relation?: DetailRelationMeta;
10766
10798
  } | {
10767
10799
  name: string;
10768
10800
  type: string;
10769
10801
  values?: readonly string[];
10770
- relation?: string;
10802
+ relation?: DetailRelationMeta;
10771
10803
  };
10772
10804
  interface DetailPanelStatus {
10773
10805
  label: string;
@@ -10895,8 +10927,10 @@ interface RelationConfig {
10895
10927
  entity: string;
10896
10928
  /** Field on target entity to display (defaults to 'name') */
10897
10929
  displayField?: string;
10898
- /** Cardinality: one-to-one or one-to-many */
10899
- 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;
10900
10934
  }
10901
10935
  /**
10902
10936
  * Validation-rule bundle for a schema field. Only `enum` is actually read