@almadar/ui 6.14.0 → 6.16.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.
@@ -6,8 +6,8 @@ import { LucideIcon } from 'lucide-react';
6
6
  import { C as ColorToken, U as UiError, D as DrawableNode, P as Point, a as Projector, F as FxOverlayItem, M as MeshShapeKind, b as MeshMaterial, c as DrawGroupProps, d as DrawTextProps, e as DrawMeshProps, L as LinkAction, I as ImageSource } from '../paintDispatch-Dh1pgljl.js';
7
7
  export { f as FxOverlayKind, R as Rect } from '../paintDispatch-Dh1pgljl.js';
8
8
  import { SExpr } from '@almadar/evaluator';
9
- import { U as UnitAnimationState, F as FacingDirection, S as SpriteSheetUrls, g as SpriteFrameDims, R as ResolvedFrame, h as CanvasLighting, i as CanvasPost, a as IsometricUnit, j as CameraState, k as FieldInfo, l as OrbitalTraitInfo, m as OrbitalPageInfo, T as TraitLevelData, E as ExternalLink } from '../avl-schema-parser-CM0pQo8E.js';
10
- export { B as BoardTile, G as GameAction, n as GamePhase, o as GameState, p as GameUnit, b as IsometricFeature, I as IsometricTile, P as Position, q as UnitTrait, r as calculateAttackTargets, s as calculateValidMoves, t as createInitialGameState } from '../avl-schema-parser-CM0pQo8E.js';
9
+ import { U as UnitAnimationState, F as FacingDirection, S as SpriteSheetUrls, g as SpriteFrameDims, R as ResolvedFrame, h as CanvasLighting, i as CanvasPost, a as IsometricUnit, j as CameraState, k as FieldInfo, l as OrbitalTraitInfo, m as OrbitalPageInfo, T as TraitLevelData, E as ExternalLink } from '../avl-schema-parser-tzl7qXDY.js';
10
+ export { B as BoardTile, G as GameAction, n as GamePhase, o as GameState, p as GameUnit, b as IsometricFeature, I as IsometricTile, P as Position, q as UnitTrait, r as calculateAttackTargets, s as calculateValidMoves, t as createInitialGameState } from '../avl-schema-parser-tzl7qXDY.js';
11
11
  export { G as GameAudioContext, a as GameAudioContextValue, e as GameAudioControls, b as GameAudioProvider, c as GameAudioProviderProps, U as UseGameAudioOptions, f as useGameAudio, u as useGameAudioContext } from '../GameAudioProvider-DIjFiy70.js';
12
12
  import { i as EditorMotion, j as EditorOperator, C as ContentSegment, L as LessonSegment, F as InteractiveOrbitalType, c as DomLayoutData, e as DomStateNode, V as VisualizerConfig, f as DomTransitionLabel } from '../cn-Do5gsPBm.js';
13
13
  export { G as BloomLevel, H as BloomQuizBlock, J as BloomQuizBlockProps, M as MixedSegment, o as cn, x as parseLessonSegments, K as parseMarkdownWithCodeBlocks } from '../cn-Do5gsPBm.js';
@@ -372,7 +372,7 @@ interface TextareaProps extends Omit<React__default.TextareaHTMLAttributes<HTMLT
372
372
  placeholder?: string;
373
373
  /** Number of visible rows */
374
374
  rows?: number;
375
- /** Declarative event name for trait dispatch */
375
+ /** Declarative event: fires on ⌘/Ctrl+Enter with `{ value }`. */
376
376
  action?: EventKey;
377
377
  /** Error message */
378
378
  error?: string;
@@ -2307,9 +2307,9 @@ interface ControlGridProps {
2307
2307
  }>;
2308
2308
  /** Per-direction PRESS → the board's SEMANTIC event (kind="dpad"), e.g. `{ left: "LEFT", right: "RIGHT", up: "JUMP" }`.
2309
2309
  * Lets the d-pad emit the SAME intent events as the keyboard so the FSM stays device-agnostic. */
2310
- directionEvents?: Partial<Record<DPadDirection, string>>;
2310
+ directionEvents?: Partial<Record<DPadDirection, EventKey>>;
2311
2311
  /** Per-direction RELEASE → semantic event, e.g. `{ left: "STOP", right: "STOP" }`. Omit a direction to ignore its release. */
2312
- directionReleaseEvents?: Partial<Record<DPadDirection, string>>;
2312
+ directionReleaseEvents?: Partial<Record<DPadDirection, EventKey>>;
2313
2313
  /** Per-direction sprite assets for kind="dpad" buttons (e.g. Kenny arrow PNGs). Falls back to arrow emoji. */
2314
2314
  directionAssets?: Partial<Record<DPadDirection, Asset>>;
2315
2315
  size?: 'sm' | 'md' | 'lg';
@@ -2593,10 +2593,10 @@ interface Canvas2DProps {
2593
2593
  }>;
2594
2594
  /** Emits UI:{tileLeaveEvent} with {} on pointer leave. */
2595
2595
  tileLeaveEvent?: EventEmit<Record<string, never>>;
2596
- /** Maps a keydown `e.code` the board's SEMANTIC event (device-agnostic input). */
2597
- keyMap?: Record<string, string>;
2598
- /** Maps a keyup `e.code` the board's SEMANTIC event. */
2599
- keyUpMap?: Record<string, string>;
2596
+ /** Maps a keydown `e.code` optionally prefixed `Mod+` (⌘/Ctrl), `Shift+`, `Alt+` in that order — to the board's SEMANTIC event (device-agnostic input), emitted as `UI:{event}`; keystrokes inside inputs/textareas never route. */
2597
+ keyMap?: Record<string, EventKey>;
2598
+ /** Maps a keyup `e.code` optionally prefixed `Mod+` (⌘/Ctrl), `Shift+`, `Alt+` in that order — to the board's SEMANTIC event, emitted as `UI:{event}`; keystrokes inside inputs/textareas never route. */
2599
+ keyUpMap?: Record<string, EventKey>;
2600
2600
  /** Enter scene-edit mode: click selects/deselects a drawable, drag moves it. */
2601
2601
  editable?: boolean;
2602
2602
  /** The currently-selected drawable id (controlled); `null`/undefined = none. */
@@ -2754,8 +2754,10 @@ interface CanvasProps {
2754
2754
  type?: string;
2755
2755
  elevation?: number;
2756
2756
  }>;
2757
- keyMap?: Record<string, string>;
2758
- keyUpMap?: Record<string, string>;
2757
+ /** Maps a keydown `e.code` — optionally prefixed `Mod+` (⌘/Ctrl), `Shift+`, `Alt+` in that order — to the board's SEMANTIC event (device-agnostic input), emitted as `UI:{event}`; keystrokes inside inputs/textareas never route. */
2758
+ keyMap?: Record<string, EventKey>;
2759
+ /** Maps a keyup `e.code` — optionally prefixed `Mod+` (⌘/Ctrl), `Shift+`, `Alt+` in that order — to the board's SEMANTIC event, emitted as `UI:{event}`; keystrokes inside inputs/textareas never route. */
2760
+ keyUpMap?: Record<string, EventKey>;
2759
2761
  /** Enter scene-edit mode: click selects/deselects a drawable, drag moves it. */
2760
2762
  editable?: boolean;
2761
2763
  /** The currently-selected drawable id (controlled); `null`/undefined = none. */
@@ -6516,10 +6518,10 @@ interface MathCanvasProps {
6516
6518
  type?: string;
6517
6519
  index: number;
6518
6520
  }) => void;
6519
- /** Maps a keydown `e.code` the board's SEMANTIC event (device-agnostic input), emitted as `UI:{event}` — same contract as the game canvas keyMap. */
6520
- keyMap?: Record<string, string>;
6521
- /** Maps a keyup `e.code` the board's SEMANTIC event. */
6522
- keyUpMap?: Record<string, string>;
6521
+ /** Maps a keydown `e.code` optionally prefixed `Mod+` (⌘/Ctrl), `Shift+`, `Alt+` in that order — to the board's SEMANTIC event (device-agnostic input), emitted as `UI:{event}` — same contract as the game canvas keyMap; keystrokes inside inputs/textareas never route. */
6522
+ keyMap?: Record<string, EventKey>;
6523
+ /** Maps a keyup `e.code` optionally prefixed `Mod+` (⌘/Ctrl), `Shift+`, `Alt+` in that order — to the board's SEMANTIC event, emitted as `UI:{event}`; keystrokes inside inputs/textareas never route. */
6524
+ keyUpMap?: Record<string, EventKey>;
6523
6525
  isLoading?: boolean;
6524
6526
  error?: UiError | null;
6525
6527
  }
@@ -7869,6 +7871,7 @@ interface DataGridItemAction {
7869
7871
  * with sort, select, and drag-reorder.
7870
7872
  *
7871
7873
  * @capabilities admin table, records grid, user list, CRUD list, manage-records view, spreadsheet-style data grid, sortable columns
7874
+ * @fieldsContract display
7872
7875
  */
7873
7876
  interface DataGridProps extends DataDndProps {
7874
7877
  /**
@@ -7933,8 +7936,13 @@ interface DataGridProps extends DataDndProps {
7933
7936
  * data-grid / data-list / entity-table share one knob name from authors.
7934
7937
  */
7935
7938
  look?: "dense" | "spacious" | "striped" | "borderless" | "card-rows";
7939
+ /** Relation display data: { fieldName: [{value, label}] } — injected
7940
+ * server-side by the runtime (relation-option injection) or bound by
7941
+ * compiled codegen; resolves stored foreign ids to display names for a
7942
+ * field whose type is relation. Same contract DetailPanel takes. */
7943
+ relationsData?: Record<string, readonly RelationOption[]>;
7936
7944
  }
7937
- declare function DataGrid({ entity, fields, columns, itemActions, maxInlineActions, scrollX, cols, gap, minCardWidth, className, isLoading, error, imageField, selectable, selectionEvent, infiniteScroll, loadMoreEvent, hasMore, children, pageSize, renderItem: schemaRenderItem, dragGroup, accepts, sortable, dropEvent, reorderEvent, positionEvent, dndItemIdField, dndRoot, look, }: DataGridProps): string | number | bigint | boolean | React__default.JSX.Element | Iterable<React__default.ReactNode> | Promise<string | number | bigint | boolean | React__default.ReactPortal | React__default.ReactElement<unknown, string | React__default.JSXElementConstructor<any>> | Iterable<React__default.ReactNode> | null | undefined> | null | undefined;
7945
+ declare function DataGrid({ entity, fields, columns, itemActions, maxInlineActions, scrollX, cols, gap, minCardWidth, className, isLoading, error, imageField, selectable, selectionEvent, infiniteScroll, loadMoreEvent, hasMore, children, pageSize, renderItem: schemaRenderItem, dragGroup, accepts, sortable, dropEvent, reorderEvent, positionEvent, dndItemIdField, dndRoot, look, relationsData, }: DataGridProps): string | number | bigint | boolean | React__default.JSX.Element | Iterable<React__default.ReactNode> | Promise<string | number | bigint | boolean | React__default.ReactPortal | React__default.ReactElement<unknown, string | React__default.JSXElementConstructor<any>> | Iterable<React__default.ReactNode> | null | undefined> | null | undefined;
7938
7946
  declare namespace DataGrid {
7939
7947
  var displayName: string;
7940
7948
  }
@@ -7985,6 +7993,9 @@ interface DataListSwipeAction {
7985
7993
  /** Button variant */
7986
7994
  variant?: 'primary' | 'secondary' | 'danger' | 'ghost';
7987
7995
  }
7996
+ /**
7997
+ * @fieldsContract display
7998
+ */
7988
7999
  interface DataListProps extends DataDndProps {
7989
8000
  /**
7990
8001
  * Schema entity data — the collection of rows to render.
@@ -8087,8 +8098,13 @@ interface DataListProps extends DataDndProps {
8087
8098
  * data-grid / data-list / entity-table share one knob name from authors.
8088
8099
  */
8089
8100
  look?: "dense" | "spacious" | "striped" | "borderless" | "card-rows";
8101
+ /** Relation display data: { fieldName: [{value, label}] } — injected
8102
+ * server-side by the runtime (relation-option injection) or bound by
8103
+ * compiled codegen; resolves stored foreign ids to display names for a
8104
+ * field whose type is relation. Same contract DetailPanel takes. */
8105
+ relationsData?: Record<string, readonly RelationOption[]>;
8090
8106
  }
8091
- declare function DataList({ entity, fields, columns, itemActions, maxInlineActions, itemClickEvent, gap, variant, groupBy, senderField, senderLabelField, currentUser, emptyMessage, className, isLoading, error, reorderable: _reorderable, reorderEvent: _reorderEvent, swipeLeftEvent: _swipeLeftEvent, swipeLeftActions: _swipeLeftActions, swipeRightEvent: _swipeRightEvent, swipeRightActions: _swipeRightActions, longPressEvent: _longPressEvent, infiniteScroll, loadMoreEvent, hasMore, children, pageSize, sortBy, sortDirection, renderItem: schemaRenderItem, dragGroup, accepts, sortable: sortableProp, dropEvent, reorderEvent: dndReorderEvent, positionEvent, dndItemIdField, dndRoot, look, }: DataListProps): string | number | bigint | boolean | React__default.JSX.Element | Iterable<React__default.ReactNode> | Promise<string | number | bigint | boolean | React__default.ReactPortal | React__default.ReactElement<unknown, string | React__default.JSXElementConstructor<any>> | Iterable<React__default.ReactNode> | null | undefined> | null | undefined;
8107
+ declare function DataList({ entity, fields, columns, itemActions, maxInlineActions, itemClickEvent, gap, variant, groupBy, senderField, senderLabelField, currentUser, emptyMessage, className, isLoading, error, reorderable: _reorderable, reorderEvent: _reorderEvent, swipeLeftEvent: _swipeLeftEvent, swipeLeftActions: _swipeLeftActions, swipeRightEvent: _swipeRightEvent, swipeRightActions: _swipeRightActions, longPressEvent: _longPressEvent, infiniteScroll, loadMoreEvent, hasMore, children, pageSize, sortBy, sortDirection, renderItem: schemaRenderItem, dragGroup, accepts, sortable: sortableProp, dropEvent, reorderEvent: dndReorderEvent, positionEvent, dndItemIdField, dndRoot, look, relationsData, }: DataListProps): string | number | bigint | boolean | React__default.JSX.Element | Iterable<React__default.ReactNode> | Promise<string | number | bigint | boolean | React__default.ReactPortal | React__default.ReactElement<unknown, string | React__default.JSXElementConstructor<any>> | Iterable<React__default.ReactNode> | null | undefined> | null | undefined;
8092
8108
  declare namespace DataList {
8093
8109
  var displayName: string;
8094
8110
  }
@@ -8148,6 +8164,7 @@ interface TableViewItemAction {
8148
8164
  * columns, with inline row actions and grouping.
8149
8165
  *
8150
8166
  * @capabilities admin console table, records list, CRUD data table, user list, manage-users grid, sortable columns, row selection with bulk actions, grouped list view
8167
+ * @fieldsContract display
8151
8168
  */
8152
8169
  interface TableViewProps extends DataDndProps {
8153
8170
  /** Schema entity data — the collection of rows to render. */
@@ -8212,8 +8229,13 @@ interface TableViewProps extends DataDndProps {
8212
8229
  * so authors share one knob name across row renderers.
8213
8230
  */
8214
8231
  look?: 'dense' | 'spacious' | 'striped' | 'borderless' | 'bordered';
8232
+ /** Relation display data: { fieldName: [{value, label}] } — injected
8233
+ * server-side by the runtime (relation-option injection) or bound by
8234
+ * compiled codegen; resolves stored foreign ids to display names for a
8235
+ * column whose field is relation-typed. Same contract DetailPanel takes. */
8236
+ relationsData?: Record<string, readonly RelationOption[]>;
8215
8237
  }
8216
- declare function TableView({ entity, columns, fields, itemActions, maxInlineActions, itemClickEvent, selectable, selectEvent, selectedIds, sortEvent, sortColumn, sortDirection, className, emptyMessage, isLoading, error, groupBy, pageSize, children, renderItem: _schemaRenderItem, look, dragGroup, accepts, sortable, dropEvent, reorderEvent, positionEvent, dndItemIdField, dndRoot, }: TableViewProps): React__default.JSX.Element;
8238
+ declare function TableView({ entity, columns, fields, itemActions, maxInlineActions, itemClickEvent, selectable, selectEvent, selectedIds, sortEvent, sortColumn, sortDirection, className, emptyMessage, isLoading, error, groupBy, pageSize, children, renderItem: _schemaRenderItem, look, dragGroup, accepts, sortable, dropEvent, reorderEvent, positionEvent, dndItemIdField, dndRoot, relationsData, }: TableViewProps): React__default.JSX.Element;
8217
8239
  declare namespace TableView {
8218
8240
  var displayName: string;
8219
8241
  }
@@ -11228,6 +11250,9 @@ interface DataTableEmptyAction {
11228
11250
  label: string;
11229
11251
  event?: EventKey;
11230
11252
  }
11253
+ /**
11254
+ * @fieldsContract display
11255
+ */
11231
11256
  interface DataTableProps<T extends EntityRow & {
11232
11257
  id: string | number;
11233
11258
  }> extends DisplayStateProps {
@@ -11268,10 +11293,15 @@ interface DataTableProps<T extends EntityRow & {
11268
11293
  showTotal?: boolean;
11269
11294
  /** Layer 2 visual treatment — orthogonal to the semantic variant. */
11270
11295
  look?: EntityTableLook;
11296
+ /** Relation display data: { fieldName: [{value, label}] } — injected
11297
+ * server-side by the runtime (relation-option injection) or bound by
11298
+ * compiled codegen; resolves stored foreign ids to display names for a
11299
+ * column whose field is relation-typed. Same contract DetailPanel takes. */
11300
+ relationsData?: Record<string, readonly RelationOption[]>;
11271
11301
  }
11272
11302
  declare function DataTable<T extends EntityRow & {
11273
11303
  id: string | number;
11274
- }>({ fields, columns, entity, itemActions, isLoading, error, emptyIcon, emptyTitle, emptyDescription, emptyAction, selectable, selectedIds, sortBy, sortDirection, searchable, searchValue, searchPlaceholder, page, pageSize, totalCount, rowActions: externalRowActions, bulkActions, headerActions, showTotal, className, look, }: DataTableProps<T>): React__default.JSX.Element;
11304
+ }>({ fields, columns, entity, itemActions, isLoading, error, emptyIcon, emptyTitle, emptyDescription, emptyAction, selectable, selectedIds, sortBy, sortDirection, searchable, searchValue, searchPlaceholder, page, pageSize, totalCount, rowActions: externalRowActions, bulkActions, headerActions, showTotal, className, look, relationsData, }: DataTableProps<T>): React__default.JSX.Element;
11275
11305
  declare namespace DataTable {
11276
11306
  var displayName: string;
11277
11307
  }
@@ -2042,7 +2042,7 @@ var init_Textarea = __esm({
2042
2042
  init_cn();
2043
2043
  init_useEventBus();
2044
2044
  Textarea = React79__default.forwardRef(
2045
- ({ className, error, onChange, ...props }, ref) => {
2045
+ ({ className, error, onChange, action, onKeyDown, ...props }, ref) => {
2046
2046
  const eventBus = useEventBus();
2047
2047
  const handleChange = (e) => {
2048
2048
  if (typeof onChange === "string") {
@@ -2051,11 +2051,18 @@ var init_Textarea = __esm({
2051
2051
  onChange?.(e);
2052
2052
  }
2053
2053
  };
2054
+ const handleKeyDown = (e) => {
2055
+ onKeyDown?.(e);
2056
+ if (!action || e.key !== "Enter" || !(e.metaKey || e.ctrlKey)) return;
2057
+ e.preventDefault();
2058
+ eventBus.emit(`UI:${action}`, { value: e.currentTarget.value });
2059
+ };
2054
2060
  return /* @__PURE__ */ jsx(
2055
2061
  "textarea",
2056
2062
  {
2057
2063
  ref,
2058
2064
  onChange: handleChange,
2065
+ onKeyDown: handleKeyDown,
2059
2066
  className: cn(
2060
2067
  "block w-full border-[length:var(--border-width)] shadow-sm",
2061
2068
  "px-3 py-2 text-sm text-foreground",
@@ -19981,6 +19988,29 @@ var init_useCanvasGestures = __esm({
19981
19988
  }
19982
19989
  });
19983
19990
 
19991
+ // lib/keyMapEvent.ts
19992
+ function isEditableTarget(target) {
19993
+ if (!(target instanceof HTMLElement)) return false;
19994
+ const tag = target.tagName;
19995
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
19996
+ return target.isContentEditable || target.contentEditable === "true";
19997
+ }
19998
+ function keyMapCode(e) {
19999
+ let code = e.code;
20000
+ if (e.altKey) code = `Alt+${code}`;
20001
+ if (e.shiftKey) code = `Shift+${code}`;
20002
+ if (e.metaKey || e.ctrlKey) code = `Mod+${code}`;
20003
+ return code;
20004
+ }
20005
+ function resolveKeyMapEvent(map, e) {
20006
+ if (!map || isEditableTarget(e.target)) return void 0;
20007
+ return map[keyMapCode(e)] ?? map[e.code];
20008
+ }
20009
+ var init_keyMapEvent = __esm({
20010
+ "lib/keyMapEvent.ts"() {
20011
+ }
20012
+ });
20013
+
19984
20014
  // lib/isometric.ts
19985
20015
  function isoToScreen(tileX, tileY, cellWidth, baseOffsetX, layout = "isometric") {
19986
20016
  const w = cellWidth;
@@ -20699,14 +20729,14 @@ function Canvas2D({
20699
20729
  useEffect(() => {
20700
20730
  if (!keyMap && !keyUpMap) return;
20701
20731
  const onDown = (e) => {
20702
- const ev = keyMap?.[e.code];
20732
+ const ev = resolveKeyMapEvent(keyMap, e);
20703
20733
  if (ev) {
20704
20734
  eventBus.emit(`UI:${ev}`, {});
20705
20735
  e.preventDefault();
20706
20736
  }
20707
20737
  };
20708
20738
  const onUp = (e) => {
20709
- const ev = keyUpMap?.[e.code];
20739
+ const ev = resolveKeyMapEvent(keyUpMap, e);
20710
20740
  if (ev) eventBus.emit(`UI:${ev}`, {});
20711
20741
  };
20712
20742
  window.addEventListener("keydown", onDown);
@@ -20830,6 +20860,7 @@ var init_Canvas2D = __esm({
20830
20860
  init_useCamera();
20831
20861
  init_useCanvasGestures();
20832
20862
  init_verificationRegistry();
20863
+ init_keyMapEvent();
20833
20864
  init_webPainter2d();
20834
20865
  init_projector();
20835
20866
  init_paintDispatch();
@@ -25062,6 +25093,40 @@ var init_DashboardLayout = __esm({
25062
25093
  NavLinkBottom.displayName = "NavLinkBottom";
25063
25094
  }
25064
25095
  });
25096
+
25097
+ // lib/relationLabel.ts
25098
+ function relationLabel(value) {
25099
+ if (value === null || typeof value !== "object" || Array.isArray(value) || value instanceof Date)
25100
+ return null;
25101
+ for (const key of ["name", "title", "label"]) {
25102
+ const candidate = value[key];
25103
+ if (typeof candidate === "string" && candidate !== "") return candidate;
25104
+ }
25105
+ const id = value.id;
25106
+ return id !== void 0 && id !== null ? String(id) : null;
25107
+ }
25108
+ function relationDisplayLabels(value, options) {
25109
+ if (value === void 0 || value === null || value === "") return [];
25110
+ if (Array.isArray(value)) {
25111
+ return value.flatMap((item) => relationDisplayLabels(item, options));
25112
+ }
25113
+ const hydrated = relationLabel(value);
25114
+ if (hydrated !== null) return [hydrated];
25115
+ const raw = String(value);
25116
+ const match = options?.find((opt) => opt.value === raw);
25117
+ return [match ? match.label : raw];
25118
+ }
25119
+ function resolveRelationCellDisplay(value, options) {
25120
+ if (value === null || value === void 0 || value === "") return void 0;
25121
+ const isObjectShaped = typeof value === "object" && !(value instanceof Date) || Array.isArray(value) && value.some((v) => v !== null && typeof v === "object" && !(v instanceof Date));
25122
+ if (!isObjectShaped && !options) return void 0;
25123
+ const labels = relationDisplayLabels(value, options);
25124
+ return labels.length > 0 ? labels.join(", ") : void 0;
25125
+ }
25126
+ var init_relationLabel = __esm({
25127
+ "lib/relationLabel.ts"() {
25128
+ }
25129
+ });
25065
25130
  function downloadItemUrl(url, label) {
25066
25131
  const a = document.createElement("a");
25067
25132
  a.href = url;
@@ -25893,7 +25958,8 @@ function DataGrid({
25893
25958
  positionEvent,
25894
25959
  dndItemIdField,
25895
25960
  dndRoot,
25896
- look = "dense"
25961
+ look = "dense",
25962
+ relationsData
25897
25963
  }) {
25898
25964
  const eventBus = useEventBus();
25899
25965
  const { t } = useTranslate();
@@ -26083,6 +26149,10 @@ function DataGrid({
26083
26149
  );
26084
26150
  }
26085
26151
  const titleValue = getNestedValue(itemData, titleField?.name ?? "");
26152
+ const titleDisplay = resolveRelationCellDisplay(
26153
+ titleValue,
26154
+ titleField ? relationsData?.[titleField.name] : void 0
26155
+ ) ?? (titleValue !== void 0 && titleValue !== null ? String(titleValue) : void 0);
26086
26156
  return wrapDnd(
26087
26157
  /* @__PURE__ */ jsxs(
26088
26158
  Box,
@@ -26107,7 +26177,7 @@ function DataGrid({
26107
26177
  "img",
26108
26178
  {
26109
26179
  src: imgUrl,
26110
- alt: titleValue !== void 0 ? String(titleValue) : "",
26180
+ alt: titleDisplay ?? "",
26111
26181
  className: "w-full h-full object-cover",
26112
26182
  loading: "lazy"
26113
26183
  }
@@ -26122,18 +26192,18 @@ function DataGrid({
26122
26192
  onChange: () => toggleSelection(id),
26123
26193
  onClick: (e) => e.stopPropagation(),
26124
26194
  className: "w-4 h-4 mt-1 flex-shrink-0 accent-primary",
26125
- "aria-label": t("card.selectItem", { item: titleValue !== void 0 ? String(titleValue) : t("card.itemFallback") })
26195
+ "aria-label": t("card.selectItem", { item: titleDisplay ?? t("card.itemFallback") })
26126
26196
  }
26127
26197
  ),
26128
26198
  /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: "flex-1 min-w-0", children: [
26129
- titleValue !== void 0 && titleValue !== null && /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "items-center min-w-0", children: [
26199
+ titleDisplay !== void 0 && /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "items-center min-w-0", children: [
26130
26200
  titleField?.icon && renderIconInput(titleField.icon, { size: "sm", className: "text-primary flex-shrink-0" }),
26131
26201
  /* @__PURE__ */ jsx(
26132
26202
  Typography,
26133
26203
  {
26134
26204
  variant: titleField?.variant === "h3" ? "h3" : "h4",
26135
26205
  className: "font-semibold truncate min-w-0",
26136
- children: String(titleValue)
26206
+ children: titleDisplay
26137
26207
  }
26138
26208
  )
26139
26209
  ] }),
@@ -26142,7 +26212,7 @@ function DataGrid({
26142
26212
  if (val === void 0 || val === null || val === "") return null;
26143
26213
  return /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "items-center", children: [
26144
26214
  field.icon && renderIconInput(field.icon, { size: "xs" }),
26145
- /* @__PURE__ */ jsx(Badge, { variant: resolveBadgeVariant(field, String(val)), children: humanizeEnumValue(formatValue(val, field.format)) })
26215
+ /* @__PURE__ */ jsx(Badge, { variant: resolveBadgeVariant(field, String(val)), children: resolveRelationCellDisplay(val, relationsData?.[field.name]) ?? humanizeEnumValue(formatValue(val, field.format)) })
26146
26216
  ] }, field.name);
26147
26217
  }) })
26148
26218
  ] }),
@@ -26181,7 +26251,7 @@ function DataGrid({
26181
26251
  bodyFields.filter((f3) => f3.variant === "caption" && f3.format !== "boolean").map((field) => {
26182
26252
  const value = getNestedValue(itemData, field.name);
26183
26253
  if (value === void 0 || value === null || value === "") return null;
26184
- return /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", className: "line-clamp-2", children: formatValue(value, field.format) }, field.name);
26254
+ return /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", className: "line-clamp-2", children: resolveRelationCellDisplay(value, relationsData?.[field.name]) ?? formatValue(value, field.format) }, field.name);
26185
26255
  }),
26186
26256
  /* @__PURE__ */ jsx(HStack, { gap: "md", className: "flex-wrap gap-y-1", children: bodyFields.filter((f3) => f3.variant !== "caption" || f3.format === "boolean").map((field) => {
26187
26257
  const value = getNestedValue(itemData, field.name);
@@ -26196,7 +26266,7 @@ function DataGrid({
26196
26266
  return /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "items-center", children: [
26197
26267
  field.icon && renderIconInput(field.icon, { size: "xs", className: "text-muted-foreground" }),
26198
26268
  /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", className: "sr-only", children: (field.label ?? fieldLabel2(field.name)) + ":" }),
26199
- /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: formatValue(value, field.format) })
26269
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: resolveRelationCellDisplay(value, relationsData?.[field.name]) ?? formatValue(value, field.format) })
26200
26270
  ] }, field.name);
26201
26271
  }) })
26202
26272
  ] }) })
@@ -26240,6 +26310,7 @@ var init_DataGrid = __esm({
26240
26310
  "use client";
26241
26311
  init_cn();
26242
26312
  init_format();
26313
+ init_relationLabel();
26243
26314
  init_getNestedValue();
26244
26315
  init_useEventBus();
26245
26316
  init_Box();
@@ -26292,7 +26363,9 @@ function statusVariant3(value) {
26292
26363
  if (["new", "created", "scheduled", "queued", "info"].includes(v)) return "info";
26293
26364
  return "default";
26294
26365
  }
26295
- function formatValue2(value, format, boolLabels) {
26366
+ function formatValue2(value, format, boolLabels, relationOptions) {
26367
+ const relationDisplay = resolveRelationCellDisplay(value, relationOptions);
26368
+ if (relationDisplay !== void 0) return relationDisplay;
26296
26369
  if (value !== void 0 && value !== null && (format === "boolean" || typeof value === "boolean")) {
26297
26370
  const isNo = value === false || value === 0 || String(value) === "false";
26298
26371
  return isNo ? boolLabels?.no ?? "No" : boolLabels?.yes ?? "Yes";
@@ -26352,7 +26425,8 @@ function DataList({
26352
26425
  positionEvent,
26353
26426
  dndItemIdField,
26354
26427
  dndRoot,
26355
- look = "dense"
26428
+ look = "dense",
26429
+ relationsData
26356
26430
  }) {
26357
26431
  const eventBus = useEventBus();
26358
26432
  const { t } = useTranslate();
@@ -26540,13 +26614,13 @@ function DataList({
26540
26614
  return f3.variant === "badge" ? (
26541
26615
  // `format` applies here too — a boolean field badged
26542
26616
  // without it renders the raw "false" instead of "No".
26543
- /* @__PURE__ */ jsx(Badge, { variant: statusVariant3(String(v)), children: formatValue2(v, f3.format) }, f3.name)
26617
+ /* @__PURE__ */ jsx(Badge, { variant: statusVariant3(String(v)), children: formatValue2(v, f3.format, void 0, relationsData?.[f3.name]) }, f3.name)
26544
26618
  ) : /* @__PURE__ */ jsx(
26545
26619
  Typography,
26546
26620
  {
26547
26621
  variant: "caption",
26548
26622
  className: cn("text-xs", isSent ? "opacity-70" : "text-muted-foreground"),
26549
- children: formatValue2(v, f3.format)
26623
+ children: formatValue2(v, f3.format, void 0, relationsData?.[f3.name])
26550
26624
  },
26551
26625
  f3.name
26552
26626
  );
@@ -26624,6 +26698,10 @@ function DataList({
26624
26698
  }
26625
26699
  const id = itemData.id || String(index);
26626
26700
  const titleValue = getNestedValue(itemData, titleField?.name ?? "");
26701
+ const titleDisplay = resolveRelationCellDisplay(
26702
+ titleValue,
26703
+ titleField ? relationsData?.[titleField.name] : void 0
26704
+ ) ?? (titleValue !== void 0 && titleValue !== null ? String(titleValue) : void 0);
26627
26705
  return wrapDnd(
26628
26706
  /* @__PURE__ */ jsxs(Box, { "data-entity-row": true, "data-entity-id": id, onClick: rowClickEvent ? handleRowClick(itemData) : void 0, className: cn(rowClickEvent && "cursor-pointer"), children: [
26629
26707
  /* @__PURE__ */ jsxs(
@@ -26648,7 +26726,7 @@ function DataList({
26648
26726
  {
26649
26727
  variant: titleField?.variant === "h3" ? "h3" : "h4",
26650
26728
  className: cn("font-semibold truncate flex-1", isCompact && "text-sm"),
26651
- children: String(titleValue)
26729
+ children: titleDisplay
26652
26730
  }
26653
26731
  ),
26654
26732
  badgeFields.map((field) => {
@@ -26656,7 +26734,7 @@ function DataList({
26656
26734
  if (val === void 0 || val === null) return null;
26657
26735
  return /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "items-center flex-shrink-0", children: [
26658
26736
  field.icon && renderIconInput2(field.icon, { size: "xs" }),
26659
- /* @__PURE__ */ jsx(Badge, { variant: statusVariant3(String(val)), children: formatValue2(val, field.format) })
26737
+ /* @__PURE__ */ jsx(Badge, { variant: statusVariant3(String(val)), children: formatValue2(val, field.format, void 0, relationsData?.[field.name]) })
26660
26738
  ] }, field.name);
26661
26739
  })
26662
26740
  ] }),
@@ -26677,7 +26755,7 @@ function DataList({
26677
26755
  ]
26678
26756
  }
26679
26757
  ),
26680
- /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: formatValue2(value, field.format, { yes: t("common.yes"), no: t("common.no") }) })
26758
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: formatValue2(value, field.format, { yes: t("common.yes"), no: t("common.no") }, relationsData?.[field.name]) })
26681
26759
  ] }, field.name);
26682
26760
  }) }),
26683
26761
  progressFields.map((field) => {
@@ -26757,6 +26835,7 @@ var init_DataList = __esm({
26757
26835
  "use client";
26758
26836
  init_cn();
26759
26837
  init_format();
26838
+ init_relationLabel();
26760
26839
  init_getNestedValue();
26761
26840
  init_useEventBus();
26762
26841
  init_Box();
@@ -34019,6 +34098,7 @@ var init_MathCanvas = __esm({
34019
34098
  "use client";
34020
34099
  init_useEventBus();
34021
34100
  init_perf();
34101
+ init_keyMapEvent();
34022
34102
  init_atoms();
34023
34103
  init_Stack();
34024
34104
  init_gameFonts();
@@ -34072,14 +34152,14 @@ var init_MathCanvas = __esm({
34072
34152
  useEffect(() => {
34073
34153
  if (!stableKeyMap && !stableKeyUpMap) return;
34074
34154
  const onDown = (e) => {
34075
- const ev = stableKeyMap?.[e.code];
34155
+ const ev = resolveKeyMapEvent(stableKeyMap, e);
34076
34156
  if (ev) {
34077
34157
  eventBus.emit(`UI:${ev}`, {});
34078
34158
  e.preventDefault();
34079
34159
  }
34080
34160
  };
34081
34161
  const onUp = (e) => {
34082
- const ev = stableKeyUpMap?.[e.code];
34162
+ const ev = resolveKeyMapEvent(stableKeyUpMap, e);
34083
34163
  if (ev) eventBus.emit(`UI:${ev}`, {});
34084
34164
  };
34085
34165
  window.addEventListener("keydown", onDown);
@@ -36495,33 +36575,6 @@ var init_Lightbox = __esm({
36495
36575
  Lightbox.displayName = "Lightbox";
36496
36576
  }
36497
36577
  });
36498
-
36499
- // lib/relationLabel.ts
36500
- function relationLabel(value) {
36501
- if (value === null || typeof value !== "object" || Array.isArray(value) || value instanceof Date)
36502
- return null;
36503
- for (const key of ["name", "title", "label"]) {
36504
- const candidate = value[key];
36505
- if (typeof candidate === "string" && candidate !== "") return candidate;
36506
- }
36507
- const id = value.id;
36508
- return id !== void 0 && id !== null ? String(id) : null;
36509
- }
36510
- function relationDisplayLabels(value, options) {
36511
- if (value === void 0 || value === null || value === "") return [];
36512
- if (Array.isArray(value)) {
36513
- return value.flatMap((item) => relationDisplayLabels(item, options));
36514
- }
36515
- const hydrated = relationLabel(value);
36516
- if (hydrated !== null) return [hydrated];
36517
- const raw = String(value);
36518
- const match = options?.find((opt) => opt.value === raw);
36519
- return [match ? match.label : raw];
36520
- }
36521
- var init_relationLabel = __esm({
36522
- "lib/relationLabel.ts"() {
36523
- }
36524
- });
36525
36578
  function renderIconInput3(icon, props) {
36526
36579
  return typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsx(Icon, { icon, ...props });
36527
36580
  }
@@ -36585,7 +36638,8 @@ function TableView({
36585
36638
  reorderEvent,
36586
36639
  positionEvent,
36587
36640
  dndItemIdField,
36588
- dndRoot
36641
+ dndRoot,
36642
+ relationsData
36589
36643
  }) {
36590
36644
  const eventBus = useEventBus();
36591
36645
  const { t } = useTranslate();
@@ -36670,7 +36724,7 @@ function TableView({
36670
36724
  const colFloors = React79__default.useMemo(
36671
36725
  () => colDefs.map((col) => {
36672
36726
  const longest = data.reduce((widest, row) => {
36673
- const cell = formatCell(asFieldValue(getNestedValue(row, col.field ?? col.key)), col.format);
36727
+ const cell = formatCell(asFieldValue(getNestedValue(row, col.field ?? col.key)), col.format, relationsData?.[col.field ?? col.key]);
36674
36728
  return Math.max(widest, cell.length);
36675
36729
  }, columnLabel(col).length);
36676
36730
  const chrome = col.format === "badge" ? BADGE_CHROME_CH : 0;
@@ -36770,9 +36824,11 @@ function TableView({
36770
36824
  col.className
36771
36825
  );
36772
36826
  if (col.format === "badge" && raw != null && raw !== "") {
36773
- return /* @__PURE__ */ jsx(Box, { role: "cell", className: cellBase, children: /* @__PURE__ */ jsx(Badge, { variant: statusVariant4(String(raw)), size: "sm", className: "whitespace-nowrap", children: humanizeEnumValue(String(raw)) }) }, col.key);
36827
+ const relationDisplay = resolveRelationCellDisplay(raw, relationsData?.[col.field ?? col.key]);
36828
+ const label = relationDisplay ?? humanizeEnumValue(String(raw));
36829
+ return /* @__PURE__ */ jsx(Box, { role: "cell", className: cellBase, children: /* @__PURE__ */ jsx(Badge, { variant: statusVariant4(String(raw)), size: "sm", className: "whitespace-nowrap", children: label }) }, col.key);
36774
36830
  }
36775
- return /* @__PURE__ */ jsx(Box, { role: "cell", className: cellBase, children: /* @__PURE__ */ jsx("span", { className: "truncate text-foreground", children: formatCell(raw, col.format) }) }, col.key);
36831
+ return /* @__PURE__ */ jsx(Box, { role: "cell", className: cellBase, children: /* @__PURE__ */ jsx("span", { className: "truncate text-foreground", children: formatCell(raw, col.format, relationsData?.[col.field ?? col.key]) }) }, col.key);
36776
36832
  }),
36777
36833
  hasActions && /* @__PURE__ */ jsxs(
36778
36834
  HStack,
@@ -36874,11 +36930,9 @@ var init_TableView = __esm({
36874
36930
  init_Menu();
36875
36931
  init_useDataDnd();
36876
36932
  tableViewLog = createLogger("almadar:ui:table-view");
36877
- formatCell = (value, format) => {
36878
- if (value !== null && value !== void 0 && typeof value === "object" && !(value instanceof Date)) {
36879
- const labels = relationDisplayLabels(value);
36880
- if (labels.length > 0) return labels.join(", ");
36881
- }
36933
+ formatCell = (value, format, relationOptions) => {
36934
+ const relationDisplay = resolveRelationCellDisplay(value, relationOptions);
36935
+ if (relationDisplay !== void 0) return relationDisplay;
36882
36936
  return formatValue(value, format);
36883
36937
  };
36884
36938
  MAX_MEASURED_COL_CH = 32;
@@ -45817,7 +45871,8 @@ function DataTable({
45817
45871
  headerActions,
45818
45872
  showTotal = true,
45819
45873
  className,
45820
- look = "dense"
45874
+ look = "dense",
45875
+ relationsData
45821
45876
  }) {
45822
45877
  const [openActionMenu, setOpenActionMenu] = useState(
45823
45878
  null
@@ -46112,6 +46167,11 @@ function DataTable({
46112
46167
  "data-column": String(col.key),
46113
46168
  className: "px-4 py-3 text-sm text-foreground whitespace-nowrap sm:whitespace-normal",
46114
46169
  children: col.render ? col.render(cellValue, row, rowIndex) : (() => {
46170
+ const relationDisplay = resolveRelationCellDisplay(
46171
+ cellValue,
46172
+ relationsData?.[String(col.key)]
46173
+ );
46174
+ if (relationDisplay !== void 0) return relationDisplay;
46115
46175
  const boolVal = asBooleanValue2(cellValue);
46116
46176
  if (boolVal !== null) {
46117
46177
  return boolVal ? /* @__PURE__ */ jsx(Badge, { variant: "success", children: t("common.yes") }) : /* @__PURE__ */ jsx(Badge, { variant: "neutral", children: t("common.no") });
@@ -46200,6 +46260,7 @@ var init_DataTable = __esm({
46200
46260
  init_cn();
46201
46261
  init_format();
46202
46262
  init_getNestedValue();
46263
+ init_relationLabel();
46203
46264
  init_atoms();
46204
46265
  init_Box();
46205
46266
  init_Stack();
@@ -174,6 +174,25 @@ function useEmitEvent() {
174
174
  );
175
175
  }
176
176
 
177
+ // lib/keyMapEvent.ts
178
+ function isEditableTarget(target) {
179
+ if (!(target instanceof HTMLElement)) return false;
180
+ const tag = target.tagName;
181
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
182
+ return target.isContentEditable || target.contentEditable === "true";
183
+ }
184
+ function keyMapCode(e) {
185
+ let code = e.code;
186
+ if (e.altKey) code = `Alt+${code}`;
187
+ if (e.shiftKey) code = `Shift+${code}`;
188
+ if (e.metaKey || e.ctrlKey) code = `Mod+${code}`;
189
+ return code;
190
+ }
191
+ function resolveKeyMapEvent(map, e) {
192
+ if (!map || isEditableTarget(e.target)) return void 0;
193
+ return map[keyMapCode(e)] ?? map[e.code];
194
+ }
195
+
177
196
  // lib/drawable/contract.ts
178
197
  function isValidScenePos(pos) {
179
198
  return Number.isFinite(pos?.x) && Number.isFinite(pos?.y);
@@ -2087,17 +2106,18 @@ var Canvas3DHost = React3.forwardRef(
2087
2106
  React3.useEffect(() => {
2088
2107
  if (!keyMap && !keyUpMap) return;
2089
2108
  const down = (e) => {
2090
- if (keysRef.current.has(e.code)) return;
2091
- keysRef.current.add(e.code);
2092
- const ev = keyMap?.[e.code];
2109
+ const code = keyMapCode(e);
2110
+ if (keysRef.current.has(code)) return;
2111
+ keysRef.current.add(code);
2112
+ const ev = resolveKeyMapEvent(keyMap, e);
2093
2113
  if (ev) {
2094
2114
  eventBus.emit(`UI:${ev}`, {});
2095
2115
  e.preventDefault();
2096
2116
  }
2097
2117
  };
2098
2118
  const up = (e) => {
2099
- keysRef.current.delete(e.code);
2100
- const ev = keyUpMap?.[e.code];
2119
+ keysRef.current.delete(keyMapCode(e));
2120
+ const ev = resolveKeyMapEvent(keyUpMap, e);
2101
2121
  if (ev) eventBus.emit(`UI:${ev}`, {});
2102
2122
  };
2103
2123
  window.addEventListener("keydown", down);