@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.
@@ -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;
@@ -25715,7 +25739,7 @@ function fileIcon(name) {
25715
25739
  return "file";
25716
25740
  }
25717
25741
  }
25718
- var TreeNodeItem, FileTree;
25742
+ var TreeNodeItem, FlatTreeNodeItem, FileTree;
25719
25743
  var init_FileTree = __esm({
25720
25744
  "components/core/molecules/FileTree.tsx"() {
25721
25745
  "use client";
@@ -25800,14 +25824,101 @@ var init_FileTree = __esm({
25800
25824
  )) })
25801
25825
  ] });
25802
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
+ };
25803
25885
  FileTree = ({
25804
25886
  tree,
25887
+ items,
25805
25888
  selectedPath,
25806
25889
  onFileSelect,
25890
+ onNodeSelect,
25807
25891
  className,
25808
25892
  indent = 16
25809
25893
  }) => {
25810
- 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;
25811
25922
  return /* @__PURE__ */ jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: tree.map((node) => /* @__PURE__ */ jsx(
25812
25923
  TreeNodeItem,
25813
25924
  {
@@ -27460,7 +27571,7 @@ var init_debug = __esm({
27460
27571
  createLogger("almadar:ui:debug:game-state");
27461
27572
  }
27462
27573
  });
27463
- var isRelationsDebugEnabled, RelationSelect;
27574
+ var isRelationsDebugEnabled, MANY_CARDINALITIES, RelationSelect;
27464
27575
  var init_RelationSelect = __esm({
27465
27576
  "components/core/molecules/RelationSelect.tsx"() {
27466
27577
  "use client";
@@ -27474,6 +27585,11 @@ var init_RelationSelect = __esm({
27474
27585
  init_Typography();
27475
27586
  init_debug();
27476
27587
  isRelationsDebugEnabled = () => isDebugEnabled();
27588
+ MANY_CARDINALITIES = [
27589
+ "many",
27590
+ "one-to-many",
27591
+ "many-to-many"
27592
+ ];
27477
27593
  RelationSelect = ({
27478
27594
  value,
27479
27595
  onChange,
@@ -31570,17 +31686,21 @@ var init_MathCanvas = __esm({
31570
31686
  error
31571
31687
  }) => {
31572
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]);
31573
31693
  useEffect(() => {
31574
- if (!keyMap && !keyUpMap) return;
31694
+ if (!stableKeyMap && !stableKeyUpMap) return;
31575
31695
  const onDown = (e) => {
31576
- const ev = keyMap?.[e.code];
31696
+ const ev = stableKeyMap?.[e.code];
31577
31697
  if (ev) {
31578
31698
  eventBus.emit(`UI:${ev}`, {});
31579
31699
  e.preventDefault();
31580
31700
  }
31581
31701
  };
31582
31702
  const onUp = (e) => {
31583
- const ev = keyUpMap?.[e.code];
31703
+ const ev = stableKeyUpMap?.[e.code];
31584
31704
  if (ev) eventBus.emit(`UI:${ev}`, {});
31585
31705
  };
31586
31706
  window.addEventListener("keydown", onDown);
@@ -31589,7 +31709,7 @@ var init_MathCanvas = __esm({
31589
31709
  window.removeEventListener("keydown", onDown);
31590
31710
  window.removeEventListener("keyup", onUp);
31591
31711
  };
31592
- }, [keyMap, keyUpMap, eventBus]);
31712
+ }, [stableKeyMap, stableKeyUpMap, eventBus]);
31593
31713
  const derivedShapes = useMemo(() => {
31594
31714
  const out = [];
31595
31715
  const margin = 24;
@@ -44251,6 +44371,9 @@ function determineInputType(field) {
44251
44371
  if (field.type === "relation" || field.relation) {
44252
44372
  return "relation";
44253
44373
  }
44374
+ if (field.type === "array") {
44375
+ return "array";
44376
+ }
44254
44377
  if (field.type === "enum" || field.values || getEnumOptions(field).length > 0) {
44255
44378
  return "select";
44256
44379
  }
@@ -44326,6 +44449,7 @@ var init_Form = __esm({
44326
44449
  init_Typography();
44327
44450
  init_Icon();
44328
44451
  init_RelationSelect();
44452
+ init_TagInput();
44329
44453
  init_UploadDropZone();
44330
44454
  init_Alert();
44331
44455
  init_useEventBus();
@@ -44405,7 +44529,7 @@ var init_Form = __esm({
44405
44529
  values: "values" in f3 ? f3.values : void 0,
44406
44530
  min: f3.min,
44407
44531
  max: f3.max,
44408
- 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
44409
44533
  })
44410
44534
  );
44411
44535
  }, [entity, fields]);
@@ -44640,7 +44764,7 @@ var init_Form = __esm({
44640
44764
  values: "values" in entityField ? entityField.values : void 0,
44641
44765
  min: entityField.min,
44642
44766
  max: entityField.max,
44643
- 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
44644
44768
  };
44645
44769
  }
44646
44770
  return { name: field, type: "string" };
@@ -44752,6 +44876,22 @@ var init_Form = __esm({
44752
44876
  case "relation": {
44753
44877
  const relationOptions = relationsData[fieldName] || [];
44754
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
+ }
44755
44895
  return /* @__PURE__ */ jsx(
44756
44896
  RelationSelect,
44757
44897
  {
@@ -44766,6 +44906,18 @@ var init_Form = __esm({
44766
44906
  }
44767
44907
  );
44768
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
+ }
44769
44921
  case "number":
44770
44922
  return /* @__PURE__ */ jsx(
44771
44923
  Input,
@@ -50315,7 +50467,10 @@ function enrichFormFields(fields, entityDef) {
50315
50467
  enriched.values = entityField.enumValues;
50316
50468
  }
50317
50469
  if (entityField.relation) {
50318
- enriched.relation = entityField.relation.entity;
50470
+ enriched.relation = {
50471
+ entity: entityField.relation.entity,
50472
+ cardinality: entityField.relation.cardinality
50473
+ };
50319
50474
  }
50320
50475
  return enriched;
50321
50476
  }
@@ -50343,7 +50498,10 @@ function enrichFormFields(fields, entityDef) {
50343
50498
  }
50344
50499
  }
50345
50500
  if (!obj.relation && entityField.relation) {
50346
- enriched.relation = entityField.relation.entity;
50501
+ enriched.relation = {
50502
+ entity: entityField.relation.entity,
50503
+ cardinality: entityField.relation.cardinality
50504
+ };
50347
50505
  }
50348
50506
  return enriched;
50349
50507
  }
@@ -50358,7 +50516,12 @@ function enrichDetailFields(fields, entityDef) {
50358
50516
  const meta = { type: entityField.type };
50359
50517
  const values = entityField.values ?? entityField.enumValues;
50360
50518
  if (values && values.length > 0) meta.values = values;
50361
- 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
+ }
50362
50525
  return meta;
50363
50526
  };
50364
50527
  return fields.map((field) => {
@@ -50912,6 +51075,32 @@ function isPlainConfigObject(value) {
50912
51075
  const proto = Object.getPrototypeOf(value);
50913
51076
  return proto === Object.prototype || proto === null;
50914
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
+ }
50915
51104
  function substituteTraitRefsDeep(value, pathKey) {
50916
51105
  if (isRenderBindingMarker(value)) return value;
50917
51106
  if (typeof value === "string") {
@@ -50926,11 +51115,13 @@ function substituteTraitRefsDeep(value, pathKey) {
50926
51115
  return value;
50927
51116
  }
50928
51117
  if (Array.isArray(value)) {
51118
+ if (!subtreeHasTraitRef(value)) return value;
50929
51119
  return value.map(
50930
51120
  (item, i) => substituteTraitRefsDeep(item, `${pathKey}[${i}]`)
50931
51121
  );
50932
51122
  }
50933
51123
  if (typeof value === "object" && isPlainConfigObject(value)) {
51124
+ if (!subtreeHasTraitRef(value)) return value;
50934
51125
  const out = {};
50935
51126
  for (const [k, v] of Object.entries(value)) {
50936
51127
  out[k] = substituteTraitRefsDeep(v, `${pathKey}.${k}`);
@@ -51219,7 +51410,7 @@ function UISlotRenderer({
51219
51410
  }
51220
51411
  return wrapped;
51221
51412
  }
51222
- 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;
51223
51414
  var init_UISlotRenderer = __esm({
51224
51415
  "components/core/organisms/UISlotRenderer.tsx"() {
51225
51416
  "use client";
@@ -51293,6 +51484,7 @@ var init_UISlotRenderer = __esm({
51293
51484
  "alert",
51294
51485
  "dialog"
51295
51486
  ]);
51487
+ traitRefPresenceCache = /* @__PURE__ */ new WeakMap();
51296
51488
  UISlotRenderer.displayName = "UISlotRenderer";
51297
51489
  }
51298
51490
  });