@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.
@@ -286,11 +286,32 @@ function isPlainObject(value) {
286
286
  if (typeof value === "function") return false;
287
287
  return true;
288
288
  }
289
+ function subtreeHasMarker(value) {
290
+ const cached = markerPresenceCache.get(value);
291
+ if (cached !== void 0) return cached;
292
+ let found = false;
293
+ const children = Array.isArray(value) ? value : Object.values(value);
294
+ for (const child of children) {
295
+ if (isRenderBindingMarker(child)) {
296
+ found = true;
297
+ break;
298
+ }
299
+ if (Array.isArray(child) || isPlainObject(child)) {
300
+ if (subtreeHasMarker(child)) {
301
+ found = true;
302
+ break;
303
+ }
304
+ }
305
+ }
306
+ markerPresenceCache.set(value, found);
307
+ return found;
308
+ }
289
309
  function walkValue(value, scopeTrait, entity, config, state) {
290
310
  if (isRenderBindingMarker(value)) {
291
311
  return { resolved: resolveMarkerExpression(value.expression, entity, config, state), changed: true };
292
312
  }
293
313
  if (Array.isArray(value)) {
314
+ if (!subtreeHasMarker(value)) return { resolved: value, changed: false };
294
315
  const out = [];
295
316
  let changed = false;
296
317
  for (const item of value) {
@@ -308,6 +329,7 @@ function walkValue(value, scopeTrait, entity, config, state) {
308
329
  return changed ? { resolved: out, changed: true } : { resolved: value, changed: false };
309
330
  }
310
331
  if (isPlainObject(value)) {
332
+ if (!subtreeHasMarker(value)) return { resolved: value, changed: false };
311
333
  const sourceTrait = value._sourceTrait;
312
334
  if (typeof sourceTrait === "string" && sourceTrait !== scopeTrait) {
313
335
  return { resolved: value, changed: false };
@@ -333,9 +355,11 @@ function resolveRenderBindingMarkers(props, scopeTrait, entity, config, state) {
333
355
  }
334
356
  return changed ? out : props;
335
357
  }
358
+ var markerPresenceCache;
336
359
  var init_resolve_render_bindings = __esm({
337
360
  "lib/resolve-render-bindings.ts"() {
338
361
  "use client";
362
+ markerPresenceCache = /* @__PURE__ */ new WeakMap();
339
363
  }
340
364
  });
341
365
  function cn(...inputs) {
@@ -27368,7 +27392,7 @@ function fileIcon(name) {
27368
27392
  return "file";
27369
27393
  }
27370
27394
  }
27371
- var TreeNodeItem, FileTree;
27395
+ var TreeNodeItem, FlatTreeNodeItem, FileTree;
27372
27396
  var init_FileTree = __esm({
27373
27397
  "components/core/molecules/FileTree.tsx"() {
27374
27398
  "use client";
@@ -27453,14 +27477,101 @@ var init_FileTree = __esm({
27453
27477
  )) })
27454
27478
  ] });
27455
27479
  };
27480
+ FlatTreeNodeItem = ({
27481
+ item,
27482
+ depth,
27483
+ indent,
27484
+ childrenByParent,
27485
+ onNodeSelect,
27486
+ defaultExpanded = false
27487
+ }) => {
27488
+ const [expanded, setExpanded] = useState(defaultExpanded || depth < 1);
27489
+ const children = childrenByParent.get(item.id);
27490
+ const hasChildren = !!children && children.length > 0;
27491
+ const handleClick = useCallback(() => {
27492
+ if (hasChildren) setExpanded((prev) => !prev);
27493
+ onNodeSelect?.(item.id);
27494
+ }, [hasChildren, item.id, onNodeSelect]);
27495
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
27496
+ /* @__PURE__ */ jsxs(
27497
+ Box,
27498
+ {
27499
+ className: "flex items-center gap-1.5 py-0.5 px-2 cursor-pointer rounded-sm transition-colors hover:bg-muted",
27500
+ style: { paddingLeft: depth * indent + 8 },
27501
+ onClick: handleClick,
27502
+ role: "treeitem",
27503
+ "aria-expanded": hasChildren ? expanded : void 0,
27504
+ children: [
27505
+ hasChildren ? /* @__PURE__ */ jsx(
27506
+ Icon,
27507
+ {
27508
+ name: expanded ? "chevron-down" : "chevron-right",
27509
+ size: "xs",
27510
+ className: "text-[var(--color-muted-foreground)] flex-shrink-0"
27511
+ }
27512
+ ) : /* @__PURE__ */ jsx(Box, { style: { width: 12, flexShrink: 0 } }),
27513
+ /* @__PURE__ */ jsx(
27514
+ Icon,
27515
+ {
27516
+ name: item.icon ?? (hasChildren ? expanded ? "folder-open" : "folder" : "file"),
27517
+ size: "xs",
27518
+ className: hasChildren ? "text-[var(--color-warning)]" : "text-[var(--color-muted-foreground)]"
27519
+ }
27520
+ ),
27521
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "truncate font-mono text-xs", children: item.label })
27522
+ ]
27523
+ }
27524
+ ),
27525
+ hasChildren && expanded && /* @__PURE__ */ jsx(Box, { role: "group", children: children.map((child) => /* @__PURE__ */ jsx(
27526
+ FlatTreeNodeItem,
27527
+ {
27528
+ item: child,
27529
+ depth: depth + 1,
27530
+ indent,
27531
+ childrenByParent,
27532
+ onNodeSelect
27533
+ },
27534
+ child.id
27535
+ )) })
27536
+ ] });
27537
+ };
27456
27538
  FileTree = ({
27457
27539
  tree,
27540
+ items,
27458
27541
  selectedPath,
27459
27542
  onFileSelect,
27543
+ onNodeSelect,
27460
27544
  className,
27461
27545
  indent = 16
27462
27546
  }) => {
27463
- if (tree.length === 0) return null;
27547
+ if (items) {
27548
+ if (items.length === 0) return null;
27549
+ const ids = new Set(items.map((node) => node.id));
27550
+ const childrenByParent = /* @__PURE__ */ new Map();
27551
+ const roots = [];
27552
+ for (const item of items) {
27553
+ if (item.parentId && ids.has(item.parentId)) {
27554
+ const siblings = childrenByParent.get(item.parentId);
27555
+ if (siblings) siblings.push(item);
27556
+ else childrenByParent.set(item.parentId, [item]);
27557
+ } else {
27558
+ roots.push(item);
27559
+ }
27560
+ }
27561
+ return /* @__PURE__ */ jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: roots.map((item) => /* @__PURE__ */ jsx(
27562
+ FlatTreeNodeItem,
27563
+ {
27564
+ item,
27565
+ depth: 0,
27566
+ indent,
27567
+ childrenByParent,
27568
+ onNodeSelect,
27569
+ defaultExpanded: true
27570
+ },
27571
+ item.id
27572
+ )) });
27573
+ }
27574
+ if (!tree || tree.length === 0) return null;
27464
27575
  return /* @__PURE__ */ jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: tree.map((node) => /* @__PURE__ */ jsx(
27465
27576
  TreeNodeItem,
27466
27577
  {
@@ -28855,7 +28966,7 @@ var init_debug = __esm({
28855
28966
  createLogger("almadar:ui:debug:game-state");
28856
28967
  }
28857
28968
  });
28858
- var isRelationsDebugEnabled, RelationSelect;
28969
+ var isRelationsDebugEnabled, MANY_CARDINALITIES, RelationSelect;
28859
28970
  var init_RelationSelect = __esm({
28860
28971
  "components/core/molecules/RelationSelect.tsx"() {
28861
28972
  "use client";
@@ -28869,6 +28980,11 @@ var init_RelationSelect = __esm({
28869
28980
  init_Typography();
28870
28981
  init_debug();
28871
28982
  isRelationsDebugEnabled = () => isDebugEnabled();
28983
+ MANY_CARDINALITIES = [
28984
+ "many",
28985
+ "one-to-many",
28986
+ "many-to-many"
28987
+ ];
28872
28988
  RelationSelect = ({
28873
28989
  value,
28874
28990
  onChange,
@@ -30533,17 +30649,21 @@ var init_MathCanvas = __esm({
30533
30649
  error
30534
30650
  }) => {
30535
30651
  const eventBus = useEventBus();
30652
+ const keyMapKey = keyMap ? JSON.stringify(keyMap) : null;
30653
+ const keyUpMapKey = keyUpMap ? JSON.stringify(keyUpMap) : null;
30654
+ const stableKeyMap = useMemo(() => keyMap, [keyMapKey]);
30655
+ const stableKeyUpMap = useMemo(() => keyUpMap, [keyUpMapKey]);
30536
30656
  useEffect(() => {
30537
- if (!keyMap && !keyUpMap) return;
30657
+ if (!stableKeyMap && !stableKeyUpMap) return;
30538
30658
  const onDown = (e) => {
30539
- const ev = keyMap?.[e.code];
30659
+ const ev = stableKeyMap?.[e.code];
30540
30660
  if (ev) {
30541
30661
  eventBus.emit(`UI:${ev}`, {});
30542
30662
  e.preventDefault();
30543
30663
  }
30544
30664
  };
30545
30665
  const onUp = (e) => {
30546
- const ev = keyUpMap?.[e.code];
30666
+ const ev = stableKeyUpMap?.[e.code];
30547
30667
  if (ev) eventBus.emit(`UI:${ev}`, {});
30548
30668
  };
30549
30669
  window.addEventListener("keydown", onDown);
@@ -30552,7 +30672,7 @@ var init_MathCanvas = __esm({
30552
30672
  window.removeEventListener("keydown", onDown);
30553
30673
  window.removeEventListener("keyup", onUp);
30554
30674
  };
30555
- }, [keyMap, keyUpMap, eventBus]);
30675
+ }, [stableKeyMap, stableKeyUpMap, eventBus]);
30556
30676
  const derivedShapes = useMemo(() => {
30557
30677
  const out = [];
30558
30678
  const margin = 24;
@@ -42989,6 +43109,9 @@ function determineInputType(field) {
42989
43109
  if (field.type === "relation" || field.relation) {
42990
43110
  return "relation";
42991
43111
  }
43112
+ if (field.type === "array") {
43113
+ return "array";
43114
+ }
42992
43115
  if (field.type === "enum" || field.values || getEnumOptions(field).length > 0) {
42993
43116
  return "select";
42994
43117
  }
@@ -43064,6 +43187,7 @@ var init_Form = __esm({
43064
43187
  init_Typography();
43065
43188
  init_Icon();
43066
43189
  init_RelationSelect();
43190
+ init_TagInput();
43067
43191
  init_UploadDropZone();
43068
43192
  init_Alert();
43069
43193
  init_useEventBus();
@@ -43143,7 +43267,7 @@ var init_Form = __esm({
43143
43267
  values: "values" in f3 ? f3.values : void 0,
43144
43268
  min: f3.min,
43145
43269
  max: f3.max,
43146
- relation: "relation" in f3 ? { entity: f3.relation.entity } : void 0
43270
+ relation: "relation" in f3 ? { entity: f3.relation.entity, cardinality: f3.relation.cardinality } : void 0
43147
43271
  })
43148
43272
  );
43149
43273
  }, [entity, fields]);
@@ -43378,7 +43502,7 @@ var init_Form = __esm({
43378
43502
  values: "values" in entityField ? entityField.values : void 0,
43379
43503
  min: entityField.min,
43380
43504
  max: entityField.max,
43381
- relation: "relation" in entityField ? { entity: entityField.relation.entity } : void 0
43505
+ relation: "relation" in entityField ? { entity: entityField.relation.entity, cardinality: entityField.relation.cardinality } : void 0
43382
43506
  };
43383
43507
  }
43384
43508
  return { name: field, type: "string" };
@@ -43490,6 +43614,22 @@ var init_Form = __esm({
43490
43614
  case "relation": {
43491
43615
  const relationOptions = relationsData[fieldName] || [];
43492
43616
  const relationLoading = relationsLoading[fieldName] || false;
43617
+ if (field.relation?.cardinality !== void 0 && MANY_CARDINALITIES.includes(field.relation.cardinality)) {
43618
+ const selectedValues = Array.isArray(currentValue) ? currentValue.map((v) => String(v)) : [];
43619
+ return /* @__PURE__ */ jsx(
43620
+ Select,
43621
+ {
43622
+ ...commonProps,
43623
+ multiple: true,
43624
+ searchable: true,
43625
+ clearable: true,
43626
+ options: [...relationOptions],
43627
+ value: selectedValues,
43628
+ onValueChange: (value) => handleChange(fieldName, Array.isArray(value) ? value : [value]),
43629
+ placeholder: field.placeholder || `Select ${label}...`
43630
+ }
43631
+ );
43632
+ }
43493
43633
  return /* @__PURE__ */ jsx(
43494
43634
  RelationSelect,
43495
43635
  {
@@ -43504,6 +43644,18 @@ var init_Form = __esm({
43504
43644
  }
43505
43645
  );
43506
43646
  }
43647
+ case "array": {
43648
+ const arrayValue = Array.isArray(currentValue) ? currentValue.map((v) => String(v)) : currentValue != null && currentValue !== "" ? [String(currentValue)] : [];
43649
+ return /* @__PURE__ */ jsx(
43650
+ TagInput,
43651
+ {
43652
+ placeholder: field.placeholder,
43653
+ disabled: isLoading,
43654
+ value: arrayValue,
43655
+ onChange: (next) => handleChange(fieldName, [...next])
43656
+ }
43657
+ );
43658
+ }
43507
43659
  case "number":
43508
43660
  return /* @__PURE__ */ jsx(
43509
43661
  Input,
@@ -49053,7 +49205,10 @@ function enrichFormFields(fields, entityDef) {
49053
49205
  enriched.values = entityField.enumValues;
49054
49206
  }
49055
49207
  if (entityField.relation) {
49056
- enriched.relation = entityField.relation.entity;
49208
+ enriched.relation = {
49209
+ entity: entityField.relation.entity,
49210
+ cardinality: entityField.relation.cardinality
49211
+ };
49057
49212
  }
49058
49213
  return enriched;
49059
49214
  }
@@ -49081,7 +49236,10 @@ function enrichFormFields(fields, entityDef) {
49081
49236
  }
49082
49237
  }
49083
49238
  if (!obj.relation && entityField.relation) {
49084
- enriched.relation = entityField.relation.entity;
49239
+ enriched.relation = {
49240
+ entity: entityField.relation.entity,
49241
+ cardinality: entityField.relation.cardinality
49242
+ };
49085
49243
  }
49086
49244
  return enriched;
49087
49245
  }
@@ -49096,7 +49254,12 @@ function enrichDetailFields(fields, entityDef) {
49096
49254
  const meta = { type: entityField.type };
49097
49255
  const values = entityField.values ?? entityField.enumValues;
49098
49256
  if (values && values.length > 0) meta.values = values;
49099
- if (entityField.relation) meta.relation = entityField.relation.entity;
49257
+ if (entityField.relation) {
49258
+ meta.relation = {
49259
+ entity: entityField.relation.entity,
49260
+ cardinality: entityField.relation.cardinality
49261
+ };
49262
+ }
49100
49263
  return meta;
49101
49264
  };
49102
49265
  return fields.map((field) => {
@@ -49650,6 +49813,32 @@ function isPlainConfigObject(value) {
49650
49813
  const proto = Object.getPrototypeOf(value);
49651
49814
  return proto === Object.prototype || proto === null;
49652
49815
  }
49816
+ function subtreeHasTraitRef(value) {
49817
+ const cached = traitRefPresenceCache.get(value);
49818
+ if (cached !== void 0) return cached;
49819
+ let found = false;
49820
+ const children = Array.isArray(value) ? value : Object.values(value);
49821
+ for (const child of children) {
49822
+ if (typeof child === "string" && TRAIT_BINDING_RE.test(child)) {
49823
+ found = true;
49824
+ break;
49825
+ }
49826
+ if (isRenderBindingMarker(child)) continue;
49827
+ if (Array.isArray(child)) {
49828
+ if (subtreeHasTraitRef(child)) {
49829
+ found = true;
49830
+ break;
49831
+ }
49832
+ } else if (child !== null && typeof child === "object" && isPlainConfigObject(child)) {
49833
+ if (subtreeHasTraitRef(child)) {
49834
+ found = true;
49835
+ break;
49836
+ }
49837
+ }
49838
+ }
49839
+ traitRefPresenceCache.set(value, found);
49840
+ return found;
49841
+ }
49653
49842
  function substituteTraitRefsDeep(value, pathKey) {
49654
49843
  if (isRenderBindingMarker(value)) return value;
49655
49844
  if (typeof value === "string") {
@@ -49664,11 +49853,13 @@ function substituteTraitRefsDeep(value, pathKey) {
49664
49853
  return value;
49665
49854
  }
49666
49855
  if (Array.isArray(value)) {
49856
+ if (!subtreeHasTraitRef(value)) return value;
49667
49857
  return value.map(
49668
49858
  (item, i) => substituteTraitRefsDeep(item, `${pathKey}[${i}]`)
49669
49859
  );
49670
49860
  }
49671
49861
  if (typeof value === "object" && isPlainConfigObject(value)) {
49862
+ if (!subtreeHasTraitRef(value)) return value;
49672
49863
  const out = {};
49673
49864
  for (const [k, v] of Object.entries(value)) {
49674
49865
  out[k] = substituteTraitRefsDeep(v, `${pathKey}.${k}`);
@@ -49957,7 +50148,7 @@ function UISlotRenderer({
49957
50148
  }
49958
50149
  return wrapped;
49959
50150
  }
49960
- var scopeWrapLog, TRAIT_BINDING_RE, SuspenseConfigContext, SlotContainedContext, SLOT_SKELETON_MAP, SELF_OVERLAY_PATTERNS, CONTENT_NODE_SLOTS, PATTERNS_WITH_CHILDREN;
50151
+ var scopeWrapLog, TRAIT_BINDING_RE, SuspenseConfigContext, SlotContainedContext, SLOT_SKELETON_MAP, SELF_OVERLAY_PATTERNS, CONTENT_NODE_SLOTS, PATTERNS_WITH_CHILDREN, traitRefPresenceCache;
49961
50152
  var init_UISlotRenderer = __esm({
49962
50153
  "components/core/organisms/UISlotRenderer.tsx"() {
49963
50154
  "use client";
@@ -50031,6 +50222,7 @@ var init_UISlotRenderer = __esm({
50031
50222
  "alert",
50032
50223
  "dialog"
50033
50224
  ]);
50225
+ traitRefPresenceCache = /* @__PURE__ */ new WeakMap();
50034
50226
  UISlotRenderer.displayName = "UISlotRenderer";
50035
50227
  }
50036
50228
  });
@@ -51223,8 +51415,8 @@ function createHttpTransport(serverUrl) {
51223
51415
  } catch {
51224
51416
  }
51225
51417
  },
51226
- sendEvent: async (orbitalName, event, payload, clientId) => {
51227
- const body = { event, payload, clientId };
51418
+ sendEvent: async (orbitalName, event, payload, clientId, tick, sourceTrait) => {
51419
+ const body = { event, payload, clientId, tick, sourceTrait };
51228
51420
  const res = await fetch(`${serverUrl}/${orbitalName}/events`, {
51229
51421
  method: "POST",
51230
51422
  headers: { "Content-Type": "application/json" },
@@ -51269,12 +51461,15 @@ function ServerBridgeProvider({
51269
51461
  async () => transport.unregister(),
51270
51462
  [transport]
51271
51463
  );
51272
- const sendEvent = useCallback(async (orbitalName, event, payload) => {
51464
+ const sendEvent = useCallback(async (orbitalName, event, payload, tick, sourceTrait) => {
51273
51465
  const emptyMeta = { success: false, clientEffects: 0, dataEntities: {}, emittedEvents: [] };
51274
51466
  if (!connected) return { effects: [], meta: emptyMeta };
51275
51467
  try {
51276
- const result = await transport.sendEvent(orbitalName, event, payload, getTabClientId());
51468
+ const result = await transport.sendEvent(orbitalName, event, payload, getTabClientId(), tick, sourceTrait);
51277
51469
  const effects = [];
51470
+ if (tick !== void 0) {
51471
+ return { effects, meta: { ...emptyMeta, success: !!result.success, error: result.error } };
51472
+ }
51278
51473
  const responseData = result.data || {};
51279
51474
  const dataEntities = {};
51280
51475
  for (const [entityName, records] of Object.entries(responseData)) {