@almadar/ui 5.134.0 → 5.136.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.
Files changed (31) hide show
  1. package/dist/{TraitProvider-Ch79cUcb.d.cts → EntityBindingContext-CD9ZoXb4.d.cts} +30 -2
  2. package/dist/{TraitProvider-Ch79cUcb.d.ts → EntityBindingContext-CD9ZoXb4.d.ts} +30 -2
  3. package/dist/avl/index.cjs +819 -429
  4. package/dist/avl/index.js +820 -430
  5. package/dist/{cn-Dm0VrLRG.d.ts → cn-CCaph5o9.d.ts} +1 -1
  6. package/dist/{cn-D3H9UzCW.d.cts → cn-CL0kdshO.d.cts} +1 -1
  7. package/dist/components/index.cjs +1649 -1482
  8. package/dist/components/index.d.cts +105 -202
  9. package/dist/components/index.d.ts +105 -202
  10. package/dist/components/index.js +705 -533
  11. package/dist/lib/drawable/three/index.cjs +16 -0
  12. package/dist/lib/drawable/three/index.d.cts +1 -1
  13. package/dist/lib/drawable/three/index.d.ts +1 -1
  14. package/dist/lib/drawable/three/index.js +16 -0
  15. package/dist/lib/index.cjs +28 -0
  16. package/dist/lib/index.d.cts +21 -3
  17. package/dist/lib/index.d.ts +21 -3
  18. package/dist/lib/index.js +27 -1
  19. package/dist/marketing/index.cjs +1 -0
  20. package/dist/marketing/index.js +1 -0
  21. package/dist/{paintDispatch-BXJgISot.d.cts → paintDispatch-DXygiK7M.d.cts} +39 -10
  22. package/dist/{paintDispatch-BXJgISot.d.ts → paintDispatch-DXygiK7M.d.ts} +39 -10
  23. package/dist/providers/index.cjs +701 -323
  24. package/dist/providers/index.d.cts +3 -3
  25. package/dist/providers/index.d.ts +3 -3
  26. package/dist/providers/index.js +698 -322
  27. package/dist/runtime/index.cjs +819 -429
  28. package/dist/runtime/index.d.cts +5 -2
  29. package/dist/runtime/index.d.ts +5 -2
  30. package/dist/runtime/index.js +820 -430
  31. package/package.json +6 -5
@@ -3,6 +3,8 @@
3
3
  var React85 = require('react');
4
4
  var providers = require('@almadar/ui/providers');
5
5
  var logger = require('@almadar/logger');
6
+ var runtime = require('@almadar/runtime');
7
+ var core = require('@almadar/core');
6
8
  var clsx = require('clsx');
7
9
  var tailwindMerge = require('tailwind-merge');
8
10
  var LucideIcons2 = require('lucide-react');
@@ -67,10 +69,10 @@ var ReactMarkdown = require('react-markdown');
67
69
  var remarkGfm = require('remark-gfm');
68
70
  var remarkMath = require('remark-math');
69
71
  var rehypeKatex = require('rehype-katex');
70
- var core = require('@almadar/core');
71
72
  var core$1 = require('@dnd-kit/core');
72
73
  var sortable = require('@dnd-kit/sortable');
73
74
  var utilities = require('@dnd-kit/utilities');
75
+ var emojilib = require('emojilib');
74
76
  var react = require('@xyflow/react');
75
77
  var d3Force = require('d3-force');
76
78
  var patterns = require('@almadar/core/patterns');
@@ -341,6 +343,75 @@ var init_useEventBus = __esm({
341
343
  useEventBus_default = useEventBus;
342
344
  }
343
345
  });
346
+ function resolveMarkerExpression(expression, entity, config, state) {
347
+ const ctx = runtime.createContextFromBindings({
348
+ entity,
349
+ payload: {},
350
+ state,
351
+ ...config !== void 0 ? { config } : {}
352
+ });
353
+ return runtime.interpolateValue(expression, ctx);
354
+ }
355
+ function isPlainObject(value) {
356
+ if (value === null || value === void 0 || typeof value !== "object") return false;
357
+ if (Array.isArray(value)) return false;
358
+ if (React85__namespace.default.isValidElement(value)) return false;
359
+ if (value instanceof Date) return false;
360
+ if (typeof value === "function") return false;
361
+ return true;
362
+ }
363
+ function walkValue(value, scopeTrait, entity, config, state) {
364
+ if (core.isRenderBindingMarker(value)) {
365
+ return { resolved: resolveMarkerExpression(value.expression, entity, config, state), changed: true };
366
+ }
367
+ if (Array.isArray(value)) {
368
+ const out = [];
369
+ let changed = false;
370
+ for (const item of value) {
371
+ const element = item;
372
+ const wasMarker = core.isRenderBindingMarker(element);
373
+ const { resolved, changed: itemChanged } = walkValue(element, scopeTrait, entity, config, state);
374
+ if (wasMarker && Array.isArray(resolved)) {
375
+ out.push(...resolved);
376
+ changed = true;
377
+ continue;
378
+ }
379
+ out.push(resolved);
380
+ if (itemChanged) changed = true;
381
+ }
382
+ return changed ? { resolved: out, changed: true } : { resolved: value, changed: false };
383
+ }
384
+ if (isPlainObject(value)) {
385
+ const sourceTrait = value._sourceTrait;
386
+ if (typeof sourceTrait === "string" && sourceTrait !== scopeTrait) {
387
+ return { resolved: value, changed: false };
388
+ }
389
+ const out = {};
390
+ let changed = false;
391
+ for (const [key, item] of Object.entries(value)) {
392
+ const { resolved, changed: itemChanged } = walkValue(item, scopeTrait, entity, config, state);
393
+ out[key] = resolved;
394
+ if (itemChanged) changed = true;
395
+ }
396
+ return changed ? { resolved: out, changed: true } : { resolved: value, changed: false };
397
+ }
398
+ return { resolved: value, changed: false };
399
+ }
400
+ function resolveRenderBindingMarkers(props, scopeTrait, entity, config, state) {
401
+ const out = {};
402
+ let changed = false;
403
+ for (const [key, value] of Object.entries(props)) {
404
+ const { resolved, changed: propChanged } = walkValue(value, scopeTrait, entity, config, state);
405
+ out[key] = resolved;
406
+ if (propChanged) changed = true;
407
+ }
408
+ return changed ? out : props;
409
+ }
410
+ var init_resolve_render_bindings = __esm({
411
+ "lib/resolve-render-bindings.ts"() {
412
+ "use client";
413
+ }
414
+ });
344
415
  function cn(...inputs) {
345
416
  return tailwindMerge.twMerge(clsx.clsx(inputs));
346
417
  }
@@ -848,9 +919,13 @@ function getAtlas(url, onReady) {
848
919
  onReady();
849
920
  }).catch(() => {
850
921
  atlasCache.set(url, null);
922
+ onReady();
851
923
  });
852
924
  return void 0;
853
925
  }
926
+ function atlasFailed(url) {
927
+ return atlasCache.get(url) === null;
928
+ }
854
929
  function subRectFor(atlas, sprite) {
855
930
  if (isTilesheet(atlas)) {
856
931
  let col;
@@ -1271,6 +1346,32 @@ function formatValue(value, format) {
1271
1346
  return String(value);
1272
1347
  }
1273
1348
  }
1349
+ function compareCellValues(a, b) {
1350
+ const aEmpty = a === null || a === void 0 || a === "";
1351
+ const bEmpty = b === null || b === void 0 || b === "";
1352
+ if (aEmpty || bEmpty) return aEmpty && bEmpty ? 0 : aEmpty ? 1 : -1;
1353
+ if (typeof a === "number" && typeof b === "number") return a - b;
1354
+ if (typeof a === "boolean" && typeof b === "boolean") return Number(a) - Number(b);
1355
+ const aNum = Number(a);
1356
+ const bNum = Number(b);
1357
+ if (Number.isFinite(aNum) && Number.isFinite(bNum)) return aNum - bNum;
1358
+ const aTime = dateLikeTime(a);
1359
+ const bTime = dateLikeTime(b);
1360
+ if (aTime !== null && bTime !== null) return aTime - bTime;
1361
+ return String(a).localeCompare(String(b));
1362
+ }
1363
+ function dateLikeTime(value) {
1364
+ if (value instanceof Date) return value.getTime();
1365
+ const text = String(value);
1366
+ if (!/\d/.test(text) || !/[-/:T]/.test(text)) return null;
1367
+ const time = Date.parse(text);
1368
+ return Number.isNaN(time) ? null : time;
1369
+ }
1370
+ function sortRows(rows, field, direction = "asc") {
1371
+ if (!field) return rows;
1372
+ const dir = direction === "desc" ? -1 : 1;
1373
+ return [...rows].sort((a, b) => dir * compareCellValues(a?.[field], b?.[field]));
1374
+ }
1274
1375
  var init_format = __esm({
1275
1376
  "lib/format.ts"() {
1276
1377
  }
@@ -3093,6 +3194,20 @@ var init_Input = __esm({
3093
3194
  const { t } = hooks.useTranslate();
3094
3195
  const eventBus = useEventBus();
3095
3196
  const type = inputType || htmlType || "text";
3197
+ const isDeclarative = typeof onChange === "string";
3198
+ const [localValue, setLocalValue] = React85__namespace.default.useState(value);
3199
+ const pendingEchoRef = React85__namespace.default.useRef(/* @__PURE__ */ new Set());
3200
+ React85__namespace.default.useEffect(() => {
3201
+ if (!isDeclarative) return;
3202
+ const incoming = value == null ? "" : String(value);
3203
+ if (pendingEchoRef.current.has(incoming)) {
3204
+ pendingEchoRef.current.delete(incoming);
3205
+ return;
3206
+ }
3207
+ pendingEchoRef.current.clear();
3208
+ setLocalValue(value);
3209
+ }, [value, isDeclarative]);
3210
+ const displayValue = isDeclarative ? localValue : value;
3096
3211
  const resolveIconNode = (i, cls) => {
3097
3212
  if (!i) return null;
3098
3213
  if (typeof i === "string") return /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: i, className: cls });
@@ -3102,7 +3217,7 @@ var init_Input = __esm({
3102
3217
  const iconCls = "h-icon-default w-icon-default";
3103
3218
  const IconComponent = typeof iconProp === "string" ? resolveIcon(iconProp) : iconProp;
3104
3219
  const resolvedLeftIcon = (leftIcon ? resolveIconNode(leftIcon, iconCls) : null) || IconComponent && /* @__PURE__ */ jsxRuntime.jsx(IconComponent, { className: iconCls });
3105
- const showClearButton = clearable && value && String(value).length > 0;
3220
+ const showClearButton = clearable && displayValue && String(displayValue).length > 0;
3106
3221
  const isMultiline = type === "textarea";
3107
3222
  const baseClassName = cn(
3108
3223
  "block w-full rounded-sm transition-all duration-fast",
@@ -3121,6 +3236,10 @@ var init_Input = __esm({
3121
3236
  if (typeof onChange === "string") {
3122
3237
  const target = e.target;
3123
3238
  const payload = type === "checkbox" ? { checked: target.checked } : { value: target.value };
3239
+ if (type !== "checkbox") {
3240
+ pendingEchoRef.current.add(target.value);
3241
+ setLocalValue(target.value);
3242
+ }
3124
3243
  eventBus.emit(`UI:${onChange}`, payload);
3125
3244
  } else {
3126
3245
  onChange?.(e);
@@ -3151,7 +3270,7 @@ var init_Input = __esm({
3151
3270
  "select",
3152
3271
  {
3153
3272
  ref,
3154
- value,
3273
+ value: displayValue,
3155
3274
  onChange: handleChange,
3156
3275
  className: cn(baseClassName, "appearance-none pr-10", className),
3157
3276
  ...props,
@@ -3171,7 +3290,7 @@ var init_Input = __esm({
3171
3290
  "textarea",
3172
3291
  {
3173
3292
  ref,
3174
- value,
3293
+ value: displayValue,
3175
3294
  onChange: handleChange,
3176
3295
  rows,
3177
3296
  className: baseClassName,
@@ -3210,7 +3329,7 @@ var init_Input = __esm({
3210
3329
  {
3211
3330
  ref,
3212
3331
  type,
3213
- value,
3332
+ value: displayValue,
3214
3333
  onChange: handleChange,
3215
3334
  onKeyDown: handleKeyDown,
3216
3335
  className: baseClassName,
@@ -7133,46 +7252,45 @@ var init_useImageCache = __esm({
7133
7252
  });
7134
7253
 
7135
7254
  // lib/isometric.ts
7136
- function isoToScreen(tileX, tileY, scale, baseOffsetX, layout = "isometric") {
7137
- const scaledTileWidth = TILE_WIDTH * scale;
7138
- const scaledFloorHeight = FLOOR_HEIGHT * scale;
7255
+ function isoToScreen(tileX, tileY, cellWidth, baseOffsetX, layout = "isometric") {
7256
+ const w = cellWidth;
7257
+ const fh = cellWidth / 2;
7139
7258
  if (layout === "hex") {
7140
- const screenX2 = tileX * scaledTileWidth + (tileY & 1) * (scaledTileWidth / 2) + baseOffsetX;
7141
- const screenY2 = tileY * (scaledFloorHeight * 0.75);
7259
+ const screenX2 = tileX * w + (tileY & 1) * (w / 2) + baseOffsetX;
7260
+ const screenY2 = tileY * (fh * 0.75);
7142
7261
  return { x: screenX2, y: screenY2 };
7143
7262
  }
7144
7263
  if (layout === "flat") {
7145
- const screenX2 = tileX * scaledTileWidth + baseOffsetX;
7146
- const screenY2 = tileY * scaledTileWidth;
7264
+ const screenX2 = tileX * w + baseOffsetX;
7265
+ const screenY2 = tileY * w;
7147
7266
  return { x: screenX2, y: screenY2 };
7148
7267
  }
7149
- const screenX = (tileX - tileY) * (scaledTileWidth / 2) + baseOffsetX;
7150
- const screenY = (tileX + tileY) * (scaledFloorHeight / 2);
7268
+ const screenX = (tileX - tileY) * (w / 2) + baseOffsetX;
7269
+ const screenY = (tileX + tileY) * (fh / 2);
7151
7270
  return { x: screenX, y: screenY };
7152
7271
  }
7153
- function screenToIso(screenX, screenY, scale, baseOffsetX, layout = "isometric") {
7154
- const scaledTileWidth = TILE_WIDTH * scale;
7155
- const scaledFloorHeight = FLOOR_HEIGHT * scale;
7272
+ function screenToIso(screenX, screenY, cellWidth, baseOffsetX, layout = "isometric") {
7273
+ const w = cellWidth;
7274
+ const fh = cellWidth / 2;
7156
7275
  if (layout === "hex") {
7157
- const row = Math.round(screenY / (scaledFloorHeight * 0.75));
7158
- const col = Math.round((screenX - (row & 1) * (scaledTileWidth / 2) - baseOffsetX) / scaledTileWidth);
7276
+ const row = Math.round(screenY / (fh * 0.75));
7277
+ const col = Math.round((screenX - (row & 1) * (w / 2) - baseOffsetX) / w);
7159
7278
  return { x: col, y: row };
7160
7279
  }
7161
7280
  if (layout === "flat") {
7162
- const col = Math.round((screenX - baseOffsetX) / scaledTileWidth);
7163
- const row = Math.round(screenY / scaledTileWidth);
7281
+ const col = Math.round((screenX - baseOffsetX) / w);
7282
+ const row = Math.round(screenY / w);
7164
7283
  return { x: col, y: row };
7165
7284
  }
7166
7285
  const adjustedX = screenX - baseOffsetX;
7167
- const tileX = (adjustedX / (scaledTileWidth / 2) + screenY / (scaledFloorHeight / 2)) / 2;
7168
- const tileY = (screenY / (scaledFloorHeight / 2) - adjustedX / (scaledTileWidth / 2)) / 2;
7286
+ const tileX = (adjustedX / (w / 2) + screenY / (fh / 2)) / 2;
7287
+ const tileY = (screenY / (fh / 2) - adjustedX / (w / 2)) / 2;
7169
7288
  return { x: Math.round(tileX), y: Math.round(tileY) };
7170
7289
  }
7171
- var TILE_WIDTH, FLOOR_HEIGHT, DIAMOND_TOP_Y, BACKGROUND_FALLBACK_COLOR, MINIMAP_TERRAIN_COLORS;
7290
+ var TILE_WIDTH, DIAMOND_TOP_Y, BACKGROUND_FALLBACK_COLOR, MINIMAP_TERRAIN_COLORS;
7172
7291
  var init_isometric = __esm({
7173
7292
  "lib/isometric.ts"() {
7174
7293
  TILE_WIDTH = 256;
7175
- FLOOR_HEIGHT = 128;
7176
7294
  DIAMOND_TOP_Y = 374;
7177
7295
  BACKGROUND_FALLBACK_COLOR = "#1a1a2e";
7178
7296
  MINIMAP_TERRAIN_COLORS = {
@@ -7657,210 +7775,6 @@ var init_ChoiceButton = __esm({
7657
7775
  ChoiceButton.displayName = "ChoiceButton";
7658
7776
  }
7659
7777
  });
7660
- function SvgStage({
7661
- cols,
7662
- rows,
7663
- tileSize = 32,
7664
- background = "var(--color-background)",
7665
- tileClickEvent,
7666
- tileHoverEvent,
7667
- tileLeaveEvent,
7668
- keyMap,
7669
- keyUpMap,
7670
- className,
7671
- children
7672
- }) {
7673
- const eventBus = useEventBus();
7674
- const svgRef = React85.useRef(null);
7675
- const pointerDownRef = React85.useRef(null);
7676
- const cellFromClient = React85.useCallback((clientX, clientY) => {
7677
- const svg = svgRef.current;
7678
- if (!svg) return null;
7679
- const rect = svg.getBoundingClientRect();
7680
- if (rect.width === 0 || rect.height === 0) return null;
7681
- const vbW = cols * tileSize;
7682
- const vbH = rows * tileSize;
7683
- const meet = Math.min(rect.width / vbW, rect.height / vbH);
7684
- const offsetX = (rect.width - vbW * meet) / 2;
7685
- const offsetY = (rect.height - vbH * meet) / 2;
7686
- const svgX = (clientX - rect.left - offsetX) / meet;
7687
- const svgY = (clientY - rect.top - offsetY) / meet;
7688
- return {
7689
- x: Math.min(Math.max(Math.floor(svgX / tileSize), 0), cols - 1),
7690
- y: Math.min(Math.max(Math.floor(svgY / tileSize), 0), rows - 1)
7691
- };
7692
- }, [cols, rows, tileSize]);
7693
- const handlePointerDown = React85.useCallback((e) => {
7694
- pointerDownRef.current = { clientX: e.clientX, clientY: e.clientY };
7695
- }, []);
7696
- const handlePointerUp = React85.useCallback((e) => {
7697
- const down = pointerDownRef.current;
7698
- pointerDownRef.current = null;
7699
- if (!tileClickEvent) return;
7700
- if (down && Math.hypot(e.clientX - down.clientX, e.clientY - down.clientY) > 5) return;
7701
- const cell = cellFromClient(e.clientX, e.clientY);
7702
- if (cell) eventBus.emit(`UI:${tileClickEvent}`, cell);
7703
- }, [cellFromClient, tileClickEvent, eventBus]);
7704
- const handlePointerMove = React85.useCallback((e) => {
7705
- if (!tileHoverEvent) return;
7706
- const cell = cellFromClient(e.clientX, e.clientY);
7707
- if (cell) eventBus.emit(`UI:${tileHoverEvent}`, cell);
7708
- }, [cellFromClient, tileHoverEvent, eventBus]);
7709
- const handlePointerLeave = React85.useCallback(() => {
7710
- pointerDownRef.current = null;
7711
- if (tileLeaveEvent) eventBus.emit(`UI:${tileLeaveEvent}`, {});
7712
- }, [tileLeaveEvent, eventBus]);
7713
- React85.useEffect(() => {
7714
- if (!keyMap && !keyUpMap) return;
7715
- const onDown = (e) => {
7716
- const ev = keyMap?.[e.code];
7717
- if (ev) {
7718
- eventBus.emit(`UI:${ev}`, {});
7719
- e.preventDefault();
7720
- }
7721
- };
7722
- const onUp = (e) => {
7723
- const ev = keyUpMap?.[e.code];
7724
- if (ev) eventBus.emit(`UI:${ev}`, {});
7725
- };
7726
- window.addEventListener("keydown", onDown);
7727
- window.addEventListener("keyup", onUp);
7728
- return () => {
7729
- window.removeEventListener("keydown", onDown);
7730
- window.removeEventListener("keyup", onUp);
7731
- };
7732
- }, [keyMap, keyUpMap, eventBus]);
7733
- React85.useEffect(() => {
7734
- if (!keyMap && !keyUpMap) return;
7735
- svgRef.current?.focus();
7736
- }, [keyMap, keyUpMap]);
7737
- const stageContext = React85.useMemo(() => ({ tileSize }), [tileSize]);
7738
- return /* @__PURE__ */ jsxRuntime.jsxs(
7739
- "svg",
7740
- {
7741
- ref: svgRef,
7742
- "data-testid": "svg-stage",
7743
- viewBox: `0 0 ${cols * tileSize} ${rows * tileSize}`,
7744
- preserveAspectRatio: "xMidYMid meet",
7745
- className: cn("block h-full w-full", className),
7746
- tabIndex: keyMap || keyUpMap ? 0 : void 0,
7747
- onPointerDown: handlePointerDown,
7748
- onPointerMove: handlePointerMove,
7749
- onPointerUp: handlePointerUp,
7750
- onPointerLeave: handlePointerLeave,
7751
- children: [
7752
- /* @__PURE__ */ jsxRuntime.jsx("rect", { width: cols * tileSize, height: rows * tileSize, fill: background }),
7753
- /* @__PURE__ */ jsxRuntime.jsx(SvgStageContext.Provider, { value: stageContext, children })
7754
- ]
7755
- }
7756
- );
7757
- }
7758
- var SvgStageContext;
7759
- var init_SvgStage = __esm({
7760
- "components/game/molecules/SvgStage.tsx"() {
7761
- "use client";
7762
- init_cn();
7763
- init_useEventBus();
7764
- SvgStageContext = React85__namespace.createContext({ tileSize: 1 });
7765
- SvgStage.displayName = "SvgStage";
7766
- }
7767
- });
7768
- function SvgDrawShape({
7769
- shape,
7770
- x,
7771
- y,
7772
- width,
7773
- height,
7774
- radius,
7775
- radiusY,
7776
- points,
7777
- d,
7778
- x2,
7779
- y2,
7780
- fill,
7781
- stroke,
7782
- strokeWidth,
7783
- opacity,
7784
- className
7785
- }) {
7786
- const { tileSize } = React85.useContext(SvgStageContext);
7787
- const cell = (v) => v === void 0 ? void 0 : v * tileSize;
7788
- const paint = {
7789
- fill: fill ?? (stroke === void 0 ? "var(--color-primary)" : "none"),
7790
- stroke,
7791
- strokeWidth,
7792
- opacity,
7793
- className
7794
- };
7795
- return /* @__PURE__ */ jsxRuntime.jsxs("g", { transform: `translate(${x * tileSize} ${y * tileSize})`, children: [
7796
- shape === "rect" && /* @__PURE__ */ jsxRuntime.jsx("rect", { width: cell(width), height: cell(height), ...paint }),
7797
- shape === "circle" && /* @__PURE__ */ jsxRuntime.jsx("circle", { r: cell(radius), ...paint }),
7798
- shape === "ellipse" && /* @__PURE__ */ jsxRuntime.jsx("ellipse", { rx: cell(radius), ry: cell(radiusY ?? radius), ...paint }),
7799
- shape === "polygon" && /* @__PURE__ */ jsxRuntime.jsx("polygon", { points, ...paint }),
7800
- shape === "polyline" && /* @__PURE__ */ jsxRuntime.jsx("polyline", { points, ...paint }),
7801
- shape === "path" && /* @__PURE__ */ jsxRuntime.jsx("path", { d, ...paint }),
7802
- shape === "line" && /* @__PURE__ */ jsxRuntime.jsx("line", { x2: cell(x2), y2: cell(y2), ...paint })
7803
- ] });
7804
- }
7805
- var init_SvgDrawShape = __esm({
7806
- "components/game/atoms/SvgDrawShape.tsx"() {
7807
- "use client";
7808
- init_SvgStage();
7809
- SvgDrawShape.displayName = "SvgDrawShape";
7810
- }
7811
- });
7812
- function SvgDrawGroup({
7813
- x = 0,
7814
- y = 0,
7815
- scale,
7816
- rotate,
7817
- opacity,
7818
- className,
7819
- children
7820
- }) {
7821
- const { tileSize } = React85.useContext(SvgStageContext);
7822
- const transforms = [`translate(${x * tileSize} ${y * tileSize})`];
7823
- if (rotate !== void 0) transforms.push(`rotate(${rotate})`);
7824
- if (scale !== void 0) transforms.push(`scale(${scale})`);
7825
- return /* @__PURE__ */ jsxRuntime.jsx("g", { transform: transforms.join(" "), opacity, className, children });
7826
- }
7827
- var init_SvgDrawGroup = __esm({
7828
- "components/game/atoms/SvgDrawGroup.tsx"() {
7829
- "use client";
7830
- init_SvgStage();
7831
- SvgDrawGroup.displayName = "SvgDrawGroup";
7832
- }
7833
- });
7834
- function SvgDrawText({
7835
- x,
7836
- y,
7837
- text,
7838
- size = 12,
7839
- fill = "var(--color-foreground)",
7840
- anchor = "middle",
7841
- className
7842
- }) {
7843
- const { tileSize } = React85.useContext(SvgStageContext);
7844
- return /* @__PURE__ */ jsxRuntime.jsx(
7845
- "text",
7846
- {
7847
- x: x * tileSize,
7848
- y: y * tileSize,
7849
- fontSize: size,
7850
- fill,
7851
- textAnchor: anchor,
7852
- className,
7853
- children: text
7854
- }
7855
- );
7856
- }
7857
- var init_SvgDrawText = __esm({
7858
- "components/game/atoms/SvgDrawText.tsx"() {
7859
- "use client";
7860
- init_SvgStage();
7861
- SvgDrawText.displayName = "SvgDrawText";
7862
- }
7863
- });
7864
7778
  function ControlGrid({
7865
7779
  kind,
7866
7780
  buttons = DEFAULT_BUTTONS,
@@ -8564,6 +8478,7 @@ function MiniMap({
8564
8478
  const cached = imgCacheRef.current.get(url);
8565
8479
  if (cached) return cached.complete ? cached : null;
8566
8480
  const img = new Image();
8481
+ img.crossOrigin = "anonymous";
8567
8482
  img.src = url;
8568
8483
  img.onload = () => {
8569
8484
  const canvas = canvasRef.current;
@@ -8731,7 +8646,7 @@ function useCamera(initial) {
8731
8646
  const handleWheel = React85.useCallback((e, drawFn) => {
8732
8647
  e.preventDefault();
8733
8648
  const zoomDelta = e.deltaY > 0 ? 0.9 : 1.1;
8734
- cameraRef.current.zoom = Math.max(0.5, Math.min(3, cameraRef.current.zoom * zoomDelta));
8649
+ cameraRef.current.zoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, cameraRef.current.zoom * zoomDelta));
8735
8650
  drawFn?.();
8736
8651
  }, []);
8737
8652
  const handlePointerDown = React85.useCallback((e) => {
@@ -8752,7 +8667,7 @@ function useCamera(initial) {
8752
8667
  const zoomAtPoint = React85.useCallback((factor, centerX, centerY, viewportSize, drawFn) => {
8753
8668
  const cam = cameraRef.current;
8754
8669
  const oldZoom = cam.zoom;
8755
- const newZoom = Math.max(0.5, Math.min(3, oldZoom * factor));
8670
+ const newZoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, oldZoom * factor));
8756
8671
  if (newZoom === oldZoom) {
8757
8672
  drawFn?.();
8758
8673
  return;
@@ -8804,9 +8719,12 @@ function useCamera(initial) {
8804
8719
  lerpToTarget
8805
8720
  };
8806
8721
  }
8722
+ var MIN_ZOOM, MAX_ZOOM;
8807
8723
  var init_useCamera = __esm({
8808
8724
  "hooks/useCamera.ts"() {
8809
8725
  "use client";
8726
+ MIN_ZOOM = 0.05;
8727
+ MAX_ZOOM = 10;
8810
8728
  }
8811
8729
  });
8812
8730
  function localPoint(canvas, clientX, clientY) {
@@ -8927,6 +8845,7 @@ function getOrLoadImage(url, onReady) {
8927
8845
  return null;
8928
8846
  }
8929
8847
  const img = new Image();
8848
+ img.crossOrigin = "anonymous";
8930
8849
  const entry = { img, status: "pending", onReady };
8931
8850
  cache.set(url, entry);
8932
8851
  updateAssetStatus(url, "pending");
@@ -8938,10 +8857,14 @@ function getOrLoadImage(url, onReady) {
8938
8857
  img.onerror = () => {
8939
8858
  entry.status = "failed";
8940
8859
  updateAssetStatus(url, "failed");
8860
+ entry.onReady?.();
8941
8861
  };
8942
8862
  img.src = url;
8943
8863
  return null;
8944
8864
  }
8865
+ function getImageStatus(url) {
8866
+ return cache.get(url)?.status;
8867
+ }
8945
8868
  var cache;
8946
8869
  var init_imageCache = __esm({
8947
8870
  "lib/imageCache.ts"() {
@@ -9050,6 +8973,15 @@ function createWebPainter(ctx, onAssetLoad) {
9050
8973
  ctx.lineWidth = lineWidth;
9051
8974
  ctx.stroke();
9052
8975
  },
8976
+ fillPath(d, color) {
8977
+ ctx.fillStyle = color;
8978
+ ctx.fill(new Path2D(d));
8979
+ },
8980
+ strokePath(d, color, lineWidth = 1) {
8981
+ ctx.strokeStyle = color;
8982
+ ctx.lineWidth = lineWidth;
8983
+ ctx.stroke(new Path2D(d));
8984
+ },
9053
8985
  text(str, x, y, style) {
9054
8986
  if (style.font) ctx.font = style.font;
9055
8987
  ctx.fillStyle = style.color;
@@ -9071,12 +9003,13 @@ var init_webPainter2d = __esm({
9071
9003
 
9072
9004
  // lib/drawable/projector.ts
9073
9005
  function create2DProjector(opts) {
9074
- const { scale, baseOffsetX, layout } = opts;
9075
- const tileWidth = layout === "free" ? 1 : TILE_WIDTH * scale;
9076
- const floorHeight = layout === "free" ? 1 : FLOOR_HEIGHT * scale;
9077
- const diamondTopY = layout === "free" ? 0 : (opts.diamondTopY ?? DIAMOND_TOP_Y) * scale;
9006
+ const { baseOffsetX, layout } = opts;
9007
+ const tw = opts.tileWidth ?? TILE_WIDTH;
9008
+ const tileWidth = layout === "free" ? 1 : tw;
9009
+ const floorHeight = layout === "free" ? 1 : tw / 2;
9010
+ const diamondTopY = layout === "free" ? 0 : opts.diamondTopY ?? tw * (DIAMOND_TOP_Y / TILE_WIDTH);
9078
9011
  const squareGrid = layout === "flat" || layout === "free";
9079
- const project = (pos) => layout === "free" ? { x: pos.x, y: pos.y } : isoToScreen(pos.x, pos.y, scale, baseOffsetX, layout);
9012
+ const project = (pos) => layout === "free" ? { x: pos.x, y: pos.y } : isoToScreen(pos.x, pos.y, tw, baseOffsetX, layout);
9080
9013
  const anchorPoint = (pos, anchor) => {
9081
9014
  const base = project(pos);
9082
9015
  if (anchor === "top-left") return base;
@@ -9107,7 +9040,7 @@ function create2DProjector(opts) {
9107
9040
  { x: base.x, y: topY + floorHeight / 2 }
9108
9041
  ];
9109
9042
  };
9110
- return { project, anchorPoint, cellPath, tileWidth, floorHeight, diamondTopY, scale, squareGrid };
9043
+ return { project, anchorPoint, cellPath, tileWidth, floorHeight, diamondTopY, squareGrid, worldPixelDirect: layout === "free" };
9111
9044
  }
9112
9045
  var init_projector = __esm({
9113
9046
  "lib/drawable/projector.ts"() {
@@ -9123,32 +9056,67 @@ var init_contract = __esm({
9123
9056
  "lib/drawable/contract.ts"() {
9124
9057
  }
9125
9058
  });
9126
-
9127
- // components/game/atoms/DrawSprite.tsx
9059
+ function warnMissingOnce(reason, node) {
9060
+ const key = `${reason}:${node.asset.url}:${String(node.asset.atlas)}:${String(node.asset.sprite)}`;
9061
+ if (loggedMissing.has(key)) return;
9062
+ loggedMissing.add(key);
9063
+ spriteLog.warn("draw-sprite asset unresolvable \u2014 painting fallback square", { reason, url: node.asset.url, atlas: node.asset.atlas, sprite: node.asset.sprite });
9064
+ }
9065
+ function paintFallbackSquare(painter, node, dctx, reason) {
9066
+ warnMissingOnce(reason, node);
9067
+ const tw = dctx.projector.tileWidth;
9068
+ const natural = dctx.projector.worldPixelDirect ? FALLBACK_WORLD_PX : tw;
9069
+ const w = node.width !== void 0 ? node.width * tw : natural;
9070
+ const h = node.height !== void 0 ? node.height * tw : natural;
9071
+ const anchor = node.anchor ?? "top-left";
9072
+ const p = dctx.projector.anchorPoint(node.position, anchor);
9073
+ const dx = anchor === "top-left" ? p.x : p.x - w / 2;
9074
+ const dy = anchor === "ground" ? p.y - h : anchor === "center" ? p.y - h / 2 : p.y;
9075
+ painter.save();
9076
+ if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
9077
+ painter.fillRect(dx, dy, w, h, "#9b8f7f");
9078
+ painter.strokeRect(dx, dy, w, h, "#5e564b", Math.max(1, tw / 32));
9079
+ painter.restore();
9080
+ }
9128
9081
  function DrawSprite(_props) {
9129
9082
  return null;
9130
9083
  }
9131
- var paintSprite;
9084
+ var spriteLog, loggedMissing, FALLBACK_WORLD_PX, paintSprite;
9132
9085
  var init_DrawSprite = __esm({
9133
9086
  "components/game/atoms/DrawSprite.tsx"() {
9134
9087
  "use client";
9135
9088
  init_atlasSlice();
9089
+ init_imageCache();
9136
9090
  init_contract();
9091
+ spriteLog = logger.createLogger("almadar:ui:draw-sprite");
9092
+ loggedMissing = /* @__PURE__ */ new Set();
9093
+ FALLBACK_WORLD_PX = 32;
9137
9094
  paintSprite = (painter, node, dctx) => {
9138
9095
  if (!node.asset?.url || !isValidScenePos(node.position)) return;
9139
9096
  const tex = painter.resolveTexture(node.asset.url);
9140
- if (!tex) return;
9097
+ if (!tex) {
9098
+ if (getImageStatus(node.asset.url) === "failed") paintFallbackSquare(painter, node, dctx, "texture-failed");
9099
+ return;
9100
+ }
9141
9101
  let src = typeof node.frame === "object" ? node.frame : void 0;
9142
9102
  if (!src && isAtlasAsset(node.asset)) {
9143
9103
  const atlas = getAtlas(node.asset.atlas, dctx.invalidate);
9144
- if (!atlas) return;
9104
+ if (!atlas) {
9105
+ if (atlasFailed(node.asset.atlas)) paintFallbackSquare(painter, node, dctx, "atlas-failed");
9106
+ return;
9107
+ }
9145
9108
  const r = subRectFor(atlas, node.asset.sprite);
9146
- if (!r) return;
9109
+ if (!r) {
9110
+ paintFallbackSquare(painter, node, dctx, "sprite-missing");
9111
+ return;
9112
+ }
9147
9113
  src = { x: r.sx, y: r.sy, w: r.sw, h: r.sh };
9148
9114
  }
9149
9115
  const tw = dctx.projector.tileWidth;
9150
- const w = node.width !== void 0 ? node.width * tw : src ? src.w : tex.width;
9151
- const h = node.height !== void 0 ? node.height * tw : src ? src.h : tex.height;
9116
+ const fallbackW = dctx.projector.worldPixelDirect ? src ? src.w : tex.width : tw;
9117
+ const fallbackH = dctx.projector.worldPixelDirect ? src ? src.h : tex.height : tw;
9118
+ const w = node.width !== void 0 ? node.width * tw : fallbackW;
9119
+ const h = node.height !== void 0 ? node.height * tw : fallbackH;
9152
9120
  const anchor = node.anchor ?? "top-left";
9153
9121
  const p = dctx.projector.anchorPoint(node.position, anchor);
9154
9122
  const dx = anchor === "top-left" ? p.x : p.x - w / 2;
@@ -9225,6 +9193,16 @@ var init_DrawShape = __esm({
9225
9193
  if (node.stroke) painter.strokePoly(pts, node.stroke, node.strokeWidth ?? 1, true);
9226
9194
  break;
9227
9195
  }
9196
+ case "path": {
9197
+ if (!node.d) break;
9198
+ const base = dctx.projector.project(node.position);
9199
+ const tw = dctx.projector.tileWidth;
9200
+ painter.translate(base.x, base.y);
9201
+ painter.scale(tw, tw);
9202
+ if (node.fill) painter.fillPath(node.d, node.fill);
9203
+ if (node.stroke) painter.strokePath(node.d, node.stroke, (node.strokeWidth ?? 1) / tw);
9204
+ break;
9205
+ }
9228
9206
  }
9229
9207
  painter.restore();
9230
9208
  };
@@ -9316,6 +9294,19 @@ function paintDrawable(painter, node, dctx) {
9316
9294
  case "draw-text":
9317
9295
  paintText(painter, node, dctx);
9318
9296
  break;
9297
+ case "draw-group": {
9298
+ if (!isValidScenePos(node.position)) break;
9299
+ if (!Array.isArray(node.items)) break;
9300
+ const p = dctx.projector.project(node.position);
9301
+ painter.save();
9302
+ painter.translate(p.x, p.y);
9303
+ if (node.scale !== void 0) painter.scale(node.scale, node.scale);
9304
+ if (node.rotate !== void 0) painter.rotate(node.rotate);
9305
+ if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
9306
+ for (const item of node.items) paintDrawable(painter, item, dctx);
9307
+ painter.restore();
9308
+ break;
9309
+ }
9319
9310
  case "draw-sprite-layer":
9320
9311
  paintSpriteLayer(painter, node, dctx);
9321
9312
  break;
@@ -9329,6 +9320,7 @@ function paintDrawable(painter, node, dctx) {
9329
9320
  }
9330
9321
  var init_paintDispatch = __esm({
9331
9322
  "lib/drawable/paintDispatch.ts"() {
9323
+ init_contract();
9332
9324
  init_DrawSprite();
9333
9325
  init_DrawShape();
9334
9326
  init_DrawText();
@@ -9346,6 +9338,7 @@ function collectDrawnItems(nodes) {
9346
9338
  case "draw-sprite":
9347
9339
  case "draw-shape":
9348
9340
  case "draw-text":
9341
+ case "draw-group":
9349
9342
  if (isValidScenePos(n.position)) out.push({ pos: n.position, id: n.id });
9350
9343
  break;
9351
9344
  case "draw-sprite-layer":
@@ -9389,6 +9382,8 @@ function Canvas2D({
9389
9382
  keyUpMap,
9390
9383
  camera = "pan-zoom",
9391
9384
  scale = 0.4,
9385
+ tileWidth,
9386
+ fit = false,
9392
9387
  showMinimap = true,
9393
9388
  followTarget,
9394
9389
  cameraPos,
@@ -9431,9 +9426,32 @@ function Canvas2D({
9431
9426
  observer2.observe(el);
9432
9427
  return () => observer2.disconnect();
9433
9428
  }, []);
9434
- const scaledTileWidth = TILE_WIDTH * scale;
9435
- const scaledFloorHeight = FLOOR_HEIGHT * scale;
9436
- const scaledDiamondTopY = DIAMOND_TOP_Y * scale;
9429
+ const [atlasVersion, setAtlasVersion] = React85.useState(0);
9430
+ const bumpAtlas = React85.useCallback(() => setAtlasVersion((v) => v + 1), []);
9431
+ const detectedTileWidth = React85.useMemo(() => {
9432
+ for (const n of drawables ?? []) {
9433
+ const refs = [];
9434
+ if (n.type === "draw-sprite") refs.push(n.asset);
9435
+ else if (n.type === "draw-sprite-layer") for (const it of n.items) refs.push(it.asset);
9436
+ for (const a of refs) {
9437
+ if (a && isAtlasAsset(a) && a.atlas) {
9438
+ const atlas = getAtlas(a.atlas, bumpAtlas);
9439
+ if (atlas) {
9440
+ if ("tileWidth" in atlas) return atlas.tileWidth;
9441
+ if ("subTextures" in atlas) {
9442
+ const first = Object.values(atlas.subTextures)[0];
9443
+ if (first && typeof first.width === "number") return first.width;
9444
+ }
9445
+ }
9446
+ }
9447
+ }
9448
+ }
9449
+ return void 0;
9450
+ }, [drawables, atlasVersion]);
9451
+ const nativeTileW = tileWidth ?? detectedTileWidth ?? TILE_WIDTH;
9452
+ const scaledTileWidth = nativeTileW;
9453
+ const scaledFloorHeight = nativeTileW / 2;
9454
+ const scaledDiamondTopY = nativeTileW * (DIAMOND_TOP_Y / TILE_WIDTH);
9437
9455
  const drawnItems = React85.useMemo(() => collectDrawnItems(drawables ?? []), [drawables]);
9438
9456
  const scenePositions = React85.useMemo(() => drawnItems.map((i) => i.pos), [drawnItems]);
9439
9457
  const hitIndex = React85.useMemo(() => buildHitIndex(drawnItems), [drawnItems]);
@@ -9451,14 +9469,36 @@ function Canvas2D({
9451
9469
  if (isFree || projection === "flat" || projection === "side") return 0;
9452
9470
  return (gridExtent.height - 1) * (scaledTileWidth / 2);
9453
9471
  }, [isFree, projection, gridExtent.height, scaledTileWidth]);
9472
+ const effectiveZoom = React85.useMemo(() => {
9473
+ if (isFree || projection === "side") return scale;
9474
+ if (!fit) {
9475
+ const z2 = TILE_WIDTH * scale * scale / nativeTileW;
9476
+ return Number.isFinite(z2) && z2 > 0 ? z2 : scale;
9477
+ }
9478
+ if (!viewportSize.width || gridExtent.width < 2 || gridExtent.height < 2) return scale;
9479
+ let boardW;
9480
+ let boardH;
9481
+ if (projection === "flat") {
9482
+ boardW = gridExtent.width * nativeTileW;
9483
+ boardH = gridExtent.height * nativeTileW;
9484
+ } else if (projection === "hex") {
9485
+ boardW = (gridExtent.width + 0.5) * nativeTileW;
9486
+ boardH = gridExtent.height * (nativeTileW / 2) * 0.75 + nativeTileW / 2;
9487
+ } else {
9488
+ boardW = (gridExtent.width + gridExtent.height) * (nativeTileW / 2);
9489
+ boardH = (gridExtent.width + gridExtent.height) * (nativeTileW / 4);
9490
+ }
9491
+ const z = Math.min(viewportSize.width * 0.85 / boardW, viewportSize.height * 0.85 / boardH);
9492
+ return Number.isFinite(z) && z > 0 ? z : scale;
9493
+ }, [isFree, projection, fit, viewportSize, gridExtent, nativeTileW, scale]);
9454
9494
  const projector = React85.useMemo(
9455
- () => create2DProjector({ scale, baseOffsetX, layout }),
9456
- [scale, baseOffsetX, layout]
9495
+ () => create2DProjector({ tileWidth: nativeTileW, baseOffsetX, layout }),
9496
+ [nativeTileW, baseOffsetX, layout]
9457
9497
  );
9458
9498
  const unproject = React85.useCallback((screenX, screenY) => {
9459
9499
  if (projection === "free" || projection === "side") return { x: Math.round(screenX), y: Math.round(screenY) };
9460
- return screenToIso(screenX, screenY, scale, baseOffsetX, projection);
9461
- }, [projection, scale, baseOffsetX]);
9500
+ return screenToIso(screenX, screenY, nativeTileW, baseOffsetX, projection);
9501
+ }, [projection, nativeTileW, baseOffsetX]);
9462
9502
  const bgUrls = React85.useMemo(() => backgroundImage ? [backgroundImage.url] : [], [backgroundImage]);
9463
9503
  const { getImage, pendingCount: _imagePendingCount } = useImageCache(bgUrls);
9464
9504
  React85.useEffect(() => {
@@ -9485,9 +9525,7 @@ function Canvas2D({
9485
9525
  zoomAtPoint,
9486
9526
  screenToWorld,
9487
9527
  lerpToTarget
9488
- } = useCamera({ zoom: scale });
9489
- const [atlasVersion, setAtlasVersion] = React85.useState(0);
9490
- const bumpAtlas = React85.useCallback(() => setAtlasVersion((v) => v + 1), []);
9528
+ } = useCamera({ zoom: effectiveZoom });
9491
9529
  const miniMapTiles = React85.useMemo(() => {
9492
9530
  if (!showMinimap) return [];
9493
9531
  const color = MINIMAP_TERRAIN_COLORS.default;
@@ -9562,6 +9600,13 @@ function Canvas2D({
9562
9600
  React85.useEffect(() => {
9563
9601
  draw();
9564
9602
  }, [_imagePendingCount, draw]);
9603
+ const userZoomedRef = React85.useRef(false);
9604
+ React85.useEffect(() => {
9605
+ if (userZoomedRef.current) return;
9606
+ if (cameraRef.current.zoom === effectiveZoom) return;
9607
+ cameraRef.current.zoom = effectiveZoom;
9608
+ draw();
9609
+ }, [effectiveZoom, cameraRef, draw]);
9565
9610
  React85.useEffect(() => {
9566
9611
  draw();
9567
9612
  }, [atlasVersion, draw]);
@@ -9618,7 +9663,9 @@ function Canvas2D({
9618
9663
  if (tileLeaveEvent) eventBus.emit(`UI:${tileLeaveEvent}`, {});
9619
9664
  }, [handleMouseLeave, tileLeaveEvent, eventBus]);
9620
9665
  const applyZoom = React85.useCallback((factor, centerX, centerY) => {
9621
- if (enableCamera) zoomAtPoint(factor, centerX, centerY, viewportSize, () => draw());
9666
+ if (!enableCamera) return;
9667
+ userZoomedRef.current = true;
9668
+ zoomAtPoint(factor, centerX, centerY, viewportSize, () => draw());
9622
9669
  }, [enableCamera, zoomAtPoint, viewportSize, draw]);
9623
9670
  const applyPanDelta = React85.useCallback((dx, dy) => {
9624
9671
  if (enableCamera) panBy(dx, dy, () => draw());
@@ -9803,6 +9850,8 @@ function Canvas({
9803
9850
  isLoading,
9804
9851
  unitScale,
9805
9852
  showMinimap,
9853
+ fit,
9854
+ tileWidth,
9806
9855
  backgroundImage,
9807
9856
  backgroundColor,
9808
9857
  worldWidth,
@@ -9860,6 +9909,8 @@ function Canvas({
9860
9909
  projection,
9861
9910
  camera: to2DCamera(camera?.mode),
9862
9911
  ...zoom !== void 0 ? { scale: zoom } : {},
9912
+ ...fit !== void 0 ? { fit } : {},
9913
+ ...tileWidth !== void 0 ? { tileWidth } : {},
9863
9914
  ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
9864
9915
  ...camera?.pos !== void 0 ? { cameraPos: camera.pos } : {},
9865
9916
  showMinimap,
@@ -9886,32 +9937,6 @@ var init_Canvas = __esm({
9886
9937
  Canvas.displayName = "Canvas";
9887
9938
  }
9888
9939
  });
9889
- function SvgDrawShapeLayer({
9890
- items,
9891
- fill,
9892
- stroke,
9893
- strokeWidth,
9894
- opacity
9895
- }) {
9896
- return /* @__PURE__ */ jsxRuntime.jsx("g", { children: items.map(({ id, ...shape }) => /* @__PURE__ */ jsxRuntime.jsx(
9897
- SvgDrawShape,
9898
- {
9899
- ...shape,
9900
- fill: shape.fill ?? fill,
9901
- stroke: shape.stroke ?? stroke,
9902
- strokeWidth: shape.strokeWidth ?? strokeWidth,
9903
- opacity: shape.opacity ?? opacity
9904
- },
9905
- id
9906
- )) });
9907
- }
9908
- var init_SvgDrawShapeLayer = __esm({
9909
- "components/game/molecules/SvgDrawShapeLayer.tsx"() {
9910
- "use client";
9911
- init_SvgDrawShape();
9912
- SvgDrawShapeLayer.displayName = "SvgDrawShapeLayer";
9913
- }
9914
- });
9915
9940
  function GameAudioToggle({
9916
9941
  size = "sm",
9917
9942
  className,
@@ -23878,6 +23903,8 @@ function DataList({
23878
23903
  hasMore,
23879
23904
  children,
23880
23905
  pageSize = 5,
23906
+ sortBy,
23907
+ sortDirection,
23881
23908
  renderItem: schemaRenderItem,
23882
23909
  dragGroup,
23883
23910
  accepts,
@@ -23906,7 +23933,11 @@ function DataList({
23906
23933
  dndItemIdField,
23907
23934
  dndRoot
23908
23935
  });
23909
- const allData = dnd.orderedItems;
23936
+ const orderedData = dnd.orderedItems;
23937
+ const allData = React85__namespace.default.useMemo(
23938
+ () => sortRows(orderedData, sortBy, sortDirection),
23939
+ [orderedData, sortBy, sortDirection]
23940
+ );
23910
23941
  const data = pageSize > 0 ? allData.slice(0, visibleCount) : allData;
23911
23942
  const hasMoreLocal = pageSize > 0 && visibleCount < allData.length;
23912
23943
  const hasRenderProp = typeof children === "function";
@@ -23943,7 +23974,7 @@ function DataList({
23943
23974
  };
23944
23975
  eventBus.emit(`UI:${action.event}`, payload);
23945
23976
  };
23946
- const renderItemActions = (itemData) => {
23977
+ const renderItemActions = (itemData, onPrimary = false) => {
23947
23978
  if (!itemActions || itemActions.length === 0) return null;
23948
23979
  const inline = maxInlineActions != null ? itemActions.slice(0, maxInlineActions) : itemActions;
23949
23980
  const overflow = maxInlineActions != null ? itemActions.slice(maxInlineActions) : [];
@@ -23956,7 +23987,12 @@ function DataList({
23956
23987
  onClick: handleActionClick(action, itemData),
23957
23988
  "data-testid": `action-${action.event}`,
23958
23989
  "data-row-id": String(itemData.id),
23959
- className: cn(action.variant === "danger" && "text-error hover:bg-error/10"),
23990
+ className: cn(
23991
+ action.variant === "danger" && "text-error hover:bg-error/10",
23992
+ // Must sit on the Button itself: the variant's own text colour
23993
+ // beats an inherited one from the row wrapper.
23994
+ onPrimary && "!text-primary-foreground hover:bg-primary-foreground/15"
23995
+ ),
23960
23996
  children: [
23961
23997
  action.icon && renderIconInput2(action.icon, { size: "xs", className: "mr-1" }),
23962
23998
  action.label
@@ -24056,7 +24092,11 @@ function DataList({
24056
24092
  metaFields.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "xs", className: "mt-1 flex-wrap", children: metaFields.map((f3) => {
24057
24093
  const v = getNestedValue(itemData, f3.name);
24058
24094
  if (v === void 0 || v === null || v === "") return null;
24059
- return f3.variant === "badge" ? /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: statusVariant3(String(v)), children: String(v) }, f3.name) : /* @__PURE__ */ jsxRuntime.jsx(
24095
+ return f3.variant === "badge" ? (
24096
+ // `format` applies here too — a boolean field badged
24097
+ // without it renders the raw "false" instead of "No".
24098
+ /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: statusVariant3(String(v)), children: formatValue2(v, f3.format) }, f3.name)
24099
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
24060
24100
  Typography,
24061
24101
  {
24062
24102
  variant: "caption",
@@ -24075,7 +24115,7 @@ function DataList({
24075
24115
  children: formatDate(timestamp)
24076
24116
  }
24077
24117
  ) : /* @__PURE__ */ jsxRuntime.jsx("span", {}),
24078
- renderItemActions(itemData)
24118
+ renderItemActions(itemData, isSent)
24079
24119
  ] })
24080
24120
  ]
24081
24121
  }
@@ -24168,7 +24208,7 @@ function DataList({
24168
24208
  if (val === void 0 || val === null) return null;
24169
24209
  return /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", className: "items-center flex-shrink-0", children: [
24170
24210
  field.icon && renderIconInput2(field.icon, { size: "xs" }),
24171
- /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: statusVariant3(String(val)), children: String(val) })
24211
+ /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: statusVariant3(String(val)), children: formatValue2(val, field.format) })
24172
24212
  ] }, field.name);
24173
24213
  })
24174
24214
  ] }),
@@ -24407,6 +24447,182 @@ var init_FormSection = __esm({
24407
24447
  FormActions.displayName = "FormActions";
24408
24448
  }
24409
24449
  });
24450
+ var ALL_CATEGORY, MAX_RENDERED, GridPicker;
24451
+ var init_GridPicker = __esm({
24452
+ "components/core/molecules/GridPicker.tsx"() {
24453
+ "use client";
24454
+ init_cn();
24455
+ init_Input();
24456
+ init_Badge();
24457
+ init_Stack();
24458
+ ALL_CATEGORY = "__all__";
24459
+ MAX_RENDERED = 300;
24460
+ GridPicker = ({
24461
+ items,
24462
+ value,
24463
+ onChange,
24464
+ categories,
24465
+ searchPlaceholder,
24466
+ renderThumbnail,
24467
+ cellSize = 32,
24468
+ className
24469
+ }) => {
24470
+ const [search, setSearch] = React85.useState("");
24471
+ const [activeCategory, setActiveCategory] = React85.useState(ALL_CATEGORY);
24472
+ const gridRef = React85.useRef(null);
24473
+ const categoryChips = React85.useMemo(() => {
24474
+ if (categories !== void 0) return categories;
24475
+ const seen = [];
24476
+ for (const item of items) {
24477
+ if (!seen.includes(item.category)) seen.push(item.category);
24478
+ }
24479
+ return seen;
24480
+ }, [categories, items]);
24481
+ const filtered = React85.useMemo(() => {
24482
+ const needle = search.trim().toLowerCase();
24483
+ return items.filter((item) => {
24484
+ const matchesCategory = activeCategory === ALL_CATEGORY || item.category === activeCategory;
24485
+ const matchesSearch = needle === "" || item.label.toLowerCase().includes(needle) || item.keywords !== void 0 && item.keywords.some((k) => k.toLowerCase().includes(needle));
24486
+ return matchesCategory && matchesSearch;
24487
+ });
24488
+ }, [items, search, activeCategory]);
24489
+ const visible = React85.useMemo(() => filtered.slice(0, MAX_RENDERED), [filtered]);
24490
+ const truncated = filtered.length - visible.length;
24491
+ const select = React85.useCallback(
24492
+ (item) => {
24493
+ onChange(item.id);
24494
+ },
24495
+ [onChange]
24496
+ );
24497
+ const handleKeyDown = React85.useCallback(
24498
+ (e, index) => {
24499
+ const cells = gridRef.current?.querySelectorAll(
24500
+ "[data-gridpicker-cell]"
24501
+ );
24502
+ if (cells === void 0 || cells.length === 0) return;
24503
+ const columns = (() => {
24504
+ const grid = gridRef.current;
24505
+ if (grid === null) return 1;
24506
+ const style = window.getComputedStyle(grid);
24507
+ const cols = style.gridTemplateColumns.split(" ").filter(Boolean).length;
24508
+ return cols > 0 ? cols : 1;
24509
+ })();
24510
+ let next = -1;
24511
+ if (e.key === "ArrowRight") next = index + 1;
24512
+ else if (e.key === "ArrowLeft") next = index - 1;
24513
+ else if (e.key === "ArrowDown") next = index + columns;
24514
+ else if (e.key === "ArrowUp") next = index - columns;
24515
+ else if (e.key === "Enter" || e.key === " ") {
24516
+ e.preventDefault();
24517
+ select(filtered[index]);
24518
+ return;
24519
+ } else {
24520
+ return;
24521
+ }
24522
+ e.preventDefault();
24523
+ if (next >= 0 && next < cells.length) {
24524
+ cells[next].focus();
24525
+ }
24526
+ },
24527
+ [filtered, select]
24528
+ );
24529
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", className: cn("w-full", className), children: [
24530
+ /* @__PURE__ */ jsxRuntime.jsx(
24531
+ Input,
24532
+ {
24533
+ type: "search",
24534
+ icon: "search",
24535
+ value: search,
24536
+ placeholder: searchPlaceholder,
24537
+ clearable: true,
24538
+ onClear: () => setSearch(""),
24539
+ onChange: (e) => setSearch(e.target.value)
24540
+ }
24541
+ ),
24542
+ categoryChips.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", wrap: true, children: [
24543
+ /* @__PURE__ */ jsxRuntime.jsx(
24544
+ Badge,
24545
+ {
24546
+ variant: activeCategory === ALL_CATEGORY ? "primary" : "neutral",
24547
+ size: "sm",
24548
+ role: "button",
24549
+ tabIndex: 0,
24550
+ "aria-pressed": activeCategory === ALL_CATEGORY,
24551
+ className: "cursor-pointer",
24552
+ onClick: () => setActiveCategory(ALL_CATEGORY),
24553
+ onKeyDown: (e) => {
24554
+ if (e.key === "Enter" || e.key === " ") {
24555
+ e.preventDefault();
24556
+ setActiveCategory(ALL_CATEGORY);
24557
+ }
24558
+ },
24559
+ children: "All"
24560
+ }
24561
+ ),
24562
+ categoryChips.map((category) => /* @__PURE__ */ jsxRuntime.jsx(
24563
+ Badge,
24564
+ {
24565
+ variant: activeCategory === category ? "primary" : "neutral",
24566
+ size: "sm",
24567
+ role: "button",
24568
+ tabIndex: 0,
24569
+ "aria-pressed": activeCategory === category,
24570
+ className: "cursor-pointer",
24571
+ onClick: () => setActiveCategory(category),
24572
+ onKeyDown: (e) => {
24573
+ if (e.key === "Enter" || e.key === " ") {
24574
+ e.preventDefault();
24575
+ setActiveCategory(category);
24576
+ }
24577
+ },
24578
+ children: category
24579
+ },
24580
+ category
24581
+ ))
24582
+ ] }),
24583
+ /* @__PURE__ */ jsxRuntime.jsx(
24584
+ "div",
24585
+ {
24586
+ ref: gridRef,
24587
+ role: "listbox",
24588
+ className: "grid gap-1 overflow-y-auto max-h-64 p-1",
24589
+ style: {
24590
+ gridTemplateColumns: `repeat(auto-fill, minmax(${cellSize}px, 1fr))`
24591
+ },
24592
+ children: visible.map((item, index) => {
24593
+ const selected = item.id === value;
24594
+ return /* @__PURE__ */ jsxRuntime.jsx(
24595
+ "button",
24596
+ {
24597
+ type: "button",
24598
+ role: "option",
24599
+ "aria-selected": selected,
24600
+ "aria-label": item.label,
24601
+ title: item.label,
24602
+ "data-gridpicker-cell": true,
24603
+ tabIndex: selected || value === void 0 && index === 0 ? 0 : -1,
24604
+ onClick: () => select(item),
24605
+ onKeyDown: (e) => handleKeyDown(e, index),
24606
+ className: cn(
24607
+ "flex items-center justify-center rounded-sm",
24608
+ "transition-colors hover:bg-muted",
24609
+ "focus:outline-none focus:ring-1 focus:ring-ring",
24610
+ selected && "bg-primary/10 ring-1 ring-primary"
24611
+ ),
24612
+ style: { width: cellSize, height: cellSize },
24613
+ children: renderThumbnail(item)
24614
+ },
24615
+ item.id
24616
+ );
24617
+ })
24618
+ }
24619
+ ),
24620
+ truncated > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-1 text-xs text-muted-foreground", children: `+${truncated} more \u2014 refine your search` })
24621
+ ] });
24622
+ };
24623
+ GridPicker.displayName = "GridPicker";
24624
+ }
24625
+ });
24410
24626
  function fileIcon(name) {
24411
24627
  const ext = name.split(".").pop()?.toLowerCase() ?? "";
24412
24628
  switch (ext) {
@@ -25435,9 +25651,16 @@ var init_Popover = __esm({
25435
25651
  position = "bottom",
25436
25652
  trigger = "click",
25437
25653
  showArrow = true,
25654
+ open,
25655
+ onOpenChange,
25438
25656
  className
25439
25657
  }) => {
25440
- const [isOpen, setIsOpen] = React85.useState(false);
25658
+ const [uncontrolledOpen, setUncontrolledOpen] = React85.useState(false);
25659
+ const isOpen = open !== void 0 ? open : uncontrolledOpen;
25660
+ const setIsOpen = (next) => {
25661
+ if (open === void 0) setUncontrolledOpen(next);
25662
+ onOpenChange?.(next);
25663
+ };
25441
25664
  const [triggerRect, setTriggerRect] = React85.useState(null);
25442
25665
  const [popoverWidth, setPopoverWidth] = React85.useState(0);
25443
25666
  const triggerRef = React85.useRef(null);
@@ -25470,6 +25693,23 @@ var init_Popover = __esm({
25470
25693
  updatePosition();
25471
25694
  }
25472
25695
  }, [isOpen]);
25696
+ React85.useEffect(() => {
25697
+ if (!isOpen) return;
25698
+ let raf = 0;
25699
+ let lastTop = Number.NaN;
25700
+ let lastLeft = Number.NaN;
25701
+ const track = () => {
25702
+ const rect = triggerRef.current?.getBoundingClientRect();
25703
+ if (rect && (rect.top !== lastTop || rect.left !== lastLeft)) {
25704
+ lastTop = rect.top;
25705
+ lastLeft = rect.left;
25706
+ updatePosition();
25707
+ }
25708
+ raf = requestAnimationFrame(track);
25709
+ };
25710
+ raf = requestAnimationFrame(track);
25711
+ return () => cancelAnimationFrame(raf);
25712
+ }, [isOpen]);
25473
25713
  React85.useEffect(() => {
25474
25714
  if (!mounted) setPopoverWidth(0);
25475
25715
  }, [mounted]);
@@ -26818,6 +27058,80 @@ var init_FlipCard = __esm({
26818
27058
  FlipCard.displayName = "FlipCard";
26819
27059
  }
26820
27060
  });
27061
+ var EMOJI_ITEMS, EmojiPicker;
27062
+ var init_EmojiPicker = __esm({
27063
+ "components/core/molecules/EmojiPicker.tsx"() {
27064
+ "use client";
27065
+ init_useEventBus();
27066
+ init_Button();
27067
+ init_GridPicker();
27068
+ init_Popover();
27069
+ EMOJI_ITEMS = (() => {
27070
+ const items = [];
27071
+ for (const name of emojilib.ordered) {
27072
+ const entry = emojilib.lib[name];
27073
+ if (entry === void 0 || entry.char === null || entry.char === "") continue;
27074
+ items.push({
27075
+ id: entry.char,
27076
+ label: name.replace(/_/g, " "),
27077
+ category: entry.category.replace(/_/g, " "),
27078
+ keywords: entry.keywords
27079
+ });
27080
+ }
27081
+ return items;
27082
+ })();
27083
+ EmojiPicker = ({
27084
+ pickEvent,
27085
+ position = "top",
27086
+ triggerIcon = "smile",
27087
+ triggerLabel = "Add emoji",
27088
+ className
27089
+ }) => {
27090
+ const eventBus = useEventBus();
27091
+ const [open, setOpen] = React85.useState(false);
27092
+ const handlePick = (glyph) => {
27093
+ if (pickEvent !== void 0) {
27094
+ const payload = { emoji: glyph };
27095
+ eventBus.emit(`UI:${pickEvent}`, payload);
27096
+ }
27097
+ setOpen(false);
27098
+ };
27099
+ return /* @__PURE__ */ jsxRuntime.jsx(
27100
+ Popover,
27101
+ {
27102
+ position,
27103
+ trigger: "click",
27104
+ showArrow: false,
27105
+ open,
27106
+ onOpenChange: setOpen,
27107
+ content: /* @__PURE__ */ jsxRuntime.jsx(
27108
+ GridPicker,
27109
+ {
27110
+ items: EMOJI_ITEMS,
27111
+ onChange: handlePick,
27112
+ searchPlaceholder: "Search emoji\u2026",
27113
+ renderThumbnail: (item) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xl leading-none", "aria-hidden": "true", children: item.id }),
27114
+ cellSize: 32,
27115
+ className: "w-80"
27116
+ }
27117
+ ),
27118
+ children: /* @__PURE__ */ jsxRuntime.jsx(
27119
+ Button,
27120
+ {
27121
+ variant: "ghost",
27122
+ icon: triggerIcon,
27123
+ "aria-label": triggerLabel,
27124
+ title: triggerLabel,
27125
+ className,
27126
+ "data-testid": "emoji-picker-trigger"
27127
+ }
27128
+ )
27129
+ }
27130
+ );
27131
+ };
27132
+ EmojiPicker.displayName = "EmojiPicker";
27133
+ }
27134
+ });
26821
27135
  function toISODate(d) {
26822
27136
  return d.toISOString().slice(0, 10);
26823
27137
  }
@@ -27770,13 +28084,13 @@ var init_MapView = __esm({
27770
28084
  shadowSize: [41, 41]
27771
28085
  });
27772
28086
  L.Marker.prototype.options.icon = defaultIcon;
27773
- const { useEffect: useEffect63, useRef: useRef61, useCallback: useCallback95, useState: useState91 } = React85__namespace.default;
28087
+ const { useEffect: useEffect62, useRef: useRef61, useCallback: useCallback95, useState: useState93 } = React85__namespace.default;
27774
28088
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
27775
28089
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
27776
28090
  function MapUpdater({ centerLat, centerLng, zoom }) {
27777
28091
  const map = useMap();
27778
28092
  const prevRef = useRef61({ centerLat, centerLng, zoom });
27779
- useEffect63(() => {
28093
+ useEffect62(() => {
27780
28094
  const prev = prevRef.current;
27781
28095
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
27782
28096
  map.setView([centerLat, centerLng], zoom);
@@ -27787,7 +28101,7 @@ var init_MapView = __esm({
27787
28101
  }
27788
28102
  function MapClickHandler({ onMapClick }) {
27789
28103
  const map = useMap();
27790
- useEffect63(() => {
28104
+ useEffect62(() => {
27791
28105
  if (!onMapClick) return;
27792
28106
  const handler = (e) => {
27793
28107
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -27815,7 +28129,7 @@ var init_MapView = __esm({
27815
28129
  showAttribution = true
27816
28130
  }) {
27817
28131
  const eventBus = useEventBus2();
27818
- const [clickedPosition, setClickedPosition] = useState91(null);
28132
+ const [clickedPosition, setClickedPosition] = useState93(null);
27819
28133
  const handleMapClick = useCallback95((lat, lng) => {
27820
28134
  if (showClickedPin) {
27821
28135
  setClickedPosition({ lat, lng });
@@ -28660,6 +28974,7 @@ function TableView({
28660
28974
  fields,
28661
28975
  itemActions,
28662
28976
  maxInlineActions,
28977
+ itemClickEvent,
28663
28978
  selectable = false,
28664
28979
  selectEvent,
28665
28980
  selectedIds,
@@ -28704,9 +29019,9 @@ function TableView({
28704
29019
  dndItemIdField,
28705
29020
  dndRoot
28706
29021
  });
28707
- const ordered = dnd.orderedItems;
28708
- const data = pageSize > 0 ? ordered.slice(0, visibleCount) : ordered;
28709
- const hasMore = pageSize > 0 && visibleCount < ordered.length;
29022
+ const ordered2 = dnd.orderedItems;
29023
+ const data = pageSize > 0 ? ordered2.slice(0, visibleCount) : ordered2;
29024
+ const hasMore = pageSize > 0 && visibleCount < ordered2.length;
28710
29025
  const hasRenderProp = typeof children === "function";
28711
29026
  const idField = dndItemIdField ?? "id";
28712
29027
  const isCoarsePointer = useMediaQuery("(pointer: coarse)");
@@ -28759,6 +29074,14 @@ function TableView({
28759
29074
  };
28760
29075
  eventBus.emit(`UI:${action.event}`, payload);
28761
29076
  };
29077
+ const handleRowClick = (row) => () => {
29078
+ if (!itemClickEvent) return;
29079
+ const payload = {
29080
+ id: row.id,
29081
+ row
29082
+ };
29083
+ eventBus.emit(`UI:${itemClickEvent}`, payload);
29084
+ };
28762
29085
  const colFloors = React85__namespace.default.useMemo(
28763
29086
  () => colDefs.map((col) => {
28764
29087
  const longest = data.reduce((widest, row) => {
@@ -28835,10 +29158,12 @@ function TableView({
28835
29158
  role: "row",
28836
29159
  "data-entity-row": true,
28837
29160
  "data-entity-id": id,
29161
+ onClick: itemClickEvent ? handleRowClick(row) : void 0,
28838
29162
  style: !hasRenderProp ? { gridTemplateColumns } : void 0,
28839
29163
  className: cn(
28840
29164
  "group items-center gap-3 transition-colors duration-fast",
28841
29165
  hasRenderProp ? "flex" : "grid",
29166
+ itemClickEvent && "cursor-pointer",
28842
29167
  lk.rowPad,
28843
29168
  lk.divider && "border-b border-[var(--color-border)]",
28844
29169
  lk.striped && index % 2 === 1 && "bg-[var(--color-surface-subtle)]",
@@ -28846,7 +29171,7 @@ function TableView({
28846
29171
  look === "bordered" && "[&>*]:border-r [&>*]:border-[var(--color-border)] [&>*:last-child]:border-r-0"
28847
29172
  ),
28848
29173
  children: [
28849
- selectable && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex items-center", children: /* @__PURE__ */ jsxRuntime.jsx(
29174
+ selectable && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex items-center", onClick: itemClickEvent ? (e) => e.stopPropagation() : void 0, children: /* @__PURE__ */ jsxRuntime.jsx(
28850
29175
  Checkbox,
28851
29176
  {
28852
29177
  checked: selected.has(id),
@@ -28871,6 +29196,7 @@ function TableView({
28871
29196
  HStack,
28872
29197
  {
28873
29198
  gap: "xs",
29199
+ onClick: itemClickEvent ? (e) => e.stopPropagation() : void 0,
28874
29200
  className: cn(
28875
29201
  // Pinned: the fixed column tracks routinely overflow the caller's
28876
29202
  // scroll container, which used to leave the actions off-screen.
@@ -28940,7 +29266,7 @@ function TableView({
28940
29266
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "chevron-down", size: "xs", className: "mr-1" }),
28941
29267
  t("common.showMore"),
28942
29268
  " (",
28943
- t("common.remaining", { count: ordered.length - visibleCount }),
29269
+ t("common.remaining", { count: ordered2.length - visibleCount }),
28944
29270
  ")"
28945
29271
  ] }) })
28946
29272
  ]
@@ -38379,6 +38705,16 @@ var init_DetailPanel = __esm({
38379
38705
  DetailPanel.displayName = "DetailPanel";
38380
38706
  }
38381
38707
  });
38708
+
38709
+ // components/game/atoms/DrawGroup.tsx
38710
+ function DrawGroup(_props) {
38711
+ return null;
38712
+ }
38713
+ var init_DrawGroup = __esm({
38714
+ "components/game/atoms/DrawGroup.tsx"() {
38715
+ "use client";
38716
+ }
38717
+ });
38382
38718
  function extractTitle(children) {
38383
38719
  if (!React85__namespace.default.isValidElement(children)) return void 0;
38384
38720
  const props = children.props;
@@ -44082,6 +44418,7 @@ var init_component_registry_generated = __esm({
44082
44418
  init_DocSidebar();
44083
44419
  init_DocTOC();
44084
44420
  init_DocumentViewer();
44421
+ init_DrawGroup();
44085
44422
  init_DrawShape();
44086
44423
  init_DrawShapeLayer();
44087
44424
  init_DrawSprite();
@@ -44091,6 +44428,7 @@ var init_component_registry_generated = __esm({
44091
44428
  init_Drawer();
44092
44429
  init_DrawerSlot();
44093
44430
  init_EdgeDecoration();
44431
+ init_EmojiPicker();
44094
44432
  init_EmptyState();
44095
44433
  init_ErrorBoundary();
44096
44434
  init_ErrorState();
@@ -44225,10 +44563,6 @@ var init_component_registry_generated = __esm({
44225
44563
  init_SubagentTracePanel();
44226
44564
  init_SvgBranch();
44227
44565
  init_SvgConnection();
44228
- init_SvgDrawGroup();
44229
- init_SvgDrawShape();
44230
- init_SvgDrawShapeLayer();
44231
- init_SvgDrawText();
44232
44566
  init_SvgFlow();
44233
44567
  init_SvgGrid();
44234
44568
  init_SvgLobe();
@@ -44239,7 +44573,6 @@ var init_component_registry_generated = __esm({
44239
44573
  init_SvgRing();
44240
44574
  init_SvgShield();
44241
44575
  init_SvgStack();
44242
- init_SvgStage();
44243
44576
  init_SwipeableRow();
44244
44577
  init_Switch();
44245
44578
  init_TabbedContainer();
@@ -44353,6 +44686,7 @@ var init_component_registry_generated = __esm({
44353
44686
  "DocSidebar": DocSidebar,
44354
44687
  "DocTOC": DocTOC,
44355
44688
  "DocumentViewer": DocumentViewer,
44689
+ "DrawGroup": DrawGroup,
44356
44690
  "DrawShape": DrawShape,
44357
44691
  "DrawShapeLayer": DrawShapeLayer,
44358
44692
  "DrawSprite": DrawSprite,
@@ -44362,6 +44696,7 @@ var init_component_registry_generated = __esm({
44362
44696
  "Drawer": Drawer,
44363
44697
  "DrawerSlot": DrawerSlot,
44364
44698
  "EdgeDecoration": EdgeDecoration,
44699
+ "EmojiPicker": EmojiPicker,
44365
44700
  "EmptyState": EmptyState,
44366
44701
  "ErrorBoundary": ErrorBoundary,
44367
44702
  "ErrorState": ErrorState,
@@ -44501,10 +44836,6 @@ var init_component_registry_generated = __esm({
44501
44836
  "SubagentTracePanel": SubagentTracePanel,
44502
44837
  "SvgBranch": SvgBranch,
44503
44838
  "SvgConnection": SvgConnection,
44504
- "SvgDrawGroup": SvgDrawGroup,
44505
- "SvgDrawShape": SvgDrawShape,
44506
- "SvgDrawShapeLayer": SvgDrawShapeLayer,
44507
- "SvgDrawText": SvgDrawText,
44508
44839
  "SvgFlow": SvgFlow,
44509
44840
  "SvgGrid": SvgGrid,
44510
44841
  "SvgLobe": SvgLobe,
@@ -44515,7 +44846,6 @@ var init_component_registry_generated = __esm({
44515
44846
  "SvgRing": SvgRing,
44516
44847
  "SvgShield": SvgShield,
44517
44848
  "SvgStack": SvgStack,
44518
- "SvgStage": SvgStage,
44519
44849
  "SwipeableRow": SwipeableRow,
44520
44850
  "Switch": Switch,
44521
44851
  "TabbedContainer": TabbedContainer,
@@ -44794,7 +45124,19 @@ function UISlotComponent({
44794
45124
  const suspenseConfig = React85.useContext(SuspenseConfigContext);
44795
45125
  const contained = React85.useContext(SlotContainedContext);
44796
45126
  const schemaCtx = providers.useEntitySchemaOptional();
44797
- const content = slots[slot];
45127
+ const rawContent = slots[slot];
45128
+ const binding = providers.useEntityBindingSnapshot(rawContent?.sourceTrait);
45129
+ const content = React85.useMemo(() => {
45130
+ if (!rawContent) return rawContent;
45131
+ const resolvedProps = resolveRenderBindingMarkers(
45132
+ rawContent.props,
45133
+ rawContent.sourceTrait,
45134
+ binding.entity,
45135
+ binding.config,
45136
+ binding.state
45137
+ );
45138
+ return resolvedProps === rawContent.props ? rawContent : { ...rawContent, props: resolvedProps };
45139
+ }, [rawContent, binding.entity, binding.config, binding.state]);
44798
45140
  if (children !== void 0) {
44799
45141
  if (pattern === "clear") {
44800
45142
  return null;
@@ -45138,6 +45480,7 @@ function isPlainConfigObject(value) {
45138
45480
  return proto === Object.prototype || proto === null;
45139
45481
  }
45140
45482
  function substituteTraitRefsDeep(value, pathKey) {
45483
+ if (core.isRenderBindingMarker(value)) return value;
45141
45484
  if (typeof value === "string") {
45142
45485
  const match = TRAIT_BINDING_RE.exec(value);
45143
45486
  if (match) {
@@ -45210,7 +45553,14 @@ function SlotContentRenderer({
45210
45553
  onDismiss,
45211
45554
  patternPath
45212
45555
  }) {
45213
- const entityProp = content.props.entity;
45556
+ const ambientScope = providers.useTraitScope();
45557
+ const bindingTrait = content.sourceTrait ?? ambientScope?.trait;
45558
+ const binding = providers.useEntityBindingSnapshot(bindingTrait);
45559
+ const liveProps = React85.useMemo(
45560
+ () => resolveRenderBindingMarkers(content.props, bindingTrait, binding.entity, binding.config, binding.state),
45561
+ [content.props, bindingTrait, binding.entity, binding.config, binding.state]
45562
+ );
45563
+ const entityProp = liveProps.entity;
45214
45564
  if (content.pattern === "form-section") {
45215
45565
  slotLog.debug("SlotContentRenderer:form-section-render", {
45216
45566
  contentId: content.id,
@@ -45241,7 +45591,7 @@ function SlotContentRenderer({
45241
45591
  const orbitalName = schemaCtx && content.sourceTrait !== void 0 ? schemaCtx.orbitalsByTrait.get(content.sourceTrait) : void 0;
45242
45592
  const PatternComponent = getComponentForPattern(content.pattern);
45243
45593
  if (PatternComponent) {
45244
- const childrenConfig = content.props.children;
45594
+ const childrenConfig = liveProps.children;
45245
45595
  const isSingleChild = typeof childrenConfig === "string" || typeof childrenConfig === "object" && childrenConfig !== null && !Array.isArray(childrenConfig) && "type" in childrenConfig;
45246
45596
  const hasChildren = PATTERNS_WITH_CHILDREN.has(content.pattern) || Array.isArray(childrenConfig) && childrenConfig.length > 0 || isSingleChild;
45247
45597
  const isDrawHost = patterns.isDrawHostPattern(content.pattern);
@@ -45252,9 +45602,9 @@ function SlotContentRenderer({
45252
45602
  fromState: content.fromState,
45253
45603
  entity: content.entity
45254
45604
  }) : void 0;
45255
- const incomingChildren = content.props.children;
45605
+ const incomingChildren = liveProps.children;
45256
45606
  const childrenIsRenderFn = typeof incomingChildren === "function";
45257
- const { children: _childrenConfig, ...restPropsNoChildren } = content.props;
45607
+ const { children: _childrenConfig, ...restPropsNoChildren } = liveProps;
45258
45608
  const restProps = childrenIsRenderFn ? { ...restPropsNoChildren, children: incomingChildren } : restPropsNoChildren;
45259
45609
  const nodeSlotOverrides = {};
45260
45610
  for (const slotKey of CONTENT_NODE_SLOTS) {
@@ -45363,7 +45713,7 @@ function SlotContentRenderer({
45363
45713
  "data-orb-path": patternPath ?? "root",
45364
45714
  "data-orb-pattern": content.pattern,
45365
45715
  "data-orb-orbital": orbitalName,
45366
- children: content.props.children ?? /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "p-4 text-sm text-muted-foreground border border-dashed border-border rounded", children: [
45716
+ children: liveProps.children ?? /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "p-4 text-sm text-muted-foreground border border-dashed border-border rounded", children: [
45367
45717
  "Unknown pattern: ",
45368
45718
  content.pattern,
45369
45719
  content.sourceTrait && /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", className: "ml-2", children: [
@@ -45435,6 +45785,7 @@ var scopeWrapLog, TRAIT_BINDING_RE, SuspenseConfigContext, SlotContainedContext,
45435
45785
  var init_UISlotRenderer = __esm({
45436
45786
  "components/core/organisms/UISlotRenderer.tsx"() {
45437
45787
  "use client";
45788
+ init_resolve_render_bindings();
45438
45789
  init_Modal();
45439
45790
  init_Drawer();
45440
45791
  init_Toast();
@@ -45829,9 +46180,10 @@ function VerificationProvider({
45829
46180
  }));
45830
46181
  const effectResults = Array.isArray(payload["effectResults"]) ? payload["effectResults"] : [];
45831
46182
  for (const er of effectResults) {
46183
+ const target = er["entity"] ?? er["service"];
45832
46184
  effects.push({
45833
46185
  type: String(er["type"] ?? er["effect"] ?? "server-effect"),
45834
- args: [er["entity"] ?? er["service"] ?? ""].filter(Boolean),
46186
+ args: typeof target === "string" && target !== "" ? [target] : [],
45835
46187
  status: er["error"] ? "failed" : "executed",
45836
46188
  error: er["error"]
45837
46189
  });
@@ -46620,7 +46972,7 @@ function reEmitServerEvent(eventBus, emitted, origin) {
46620
46972
  sourceTrait: evTrait,
46621
46973
  origin
46622
46974
  });
46623
- eventBus.emit(key, emitted.payload);
46975
+ eventBus.emit(key, emitted.payload, emitted.source);
46624
46976
  }
46625
46977
  function isBusPushEnvelope(value) {
46626
46978
  return value.type === "bus" && typeof value.event === "string";
@@ -46788,7 +47140,12 @@ function ServerBridgeProvider({
46788
47140
  }
46789
47141
  if (result.emittedEvents) {
46790
47142
  for (const emitted of result.emittedEvents) {
46791
- reEmitServerEvent(eventBus, emitted, orbitalName);
47143
+ if (emitted.event === event) continue;
47144
+ reEmitServerEvent(
47145
+ eventBus,
47146
+ { ...emitted, source: { ...emitted.source, dispatched: true } },
47147
+ orbitalName
47148
+ );
46792
47149
  }
46793
47150
  }
46794
47151
  } else if (result.error) {
@@ -46926,10 +47283,29 @@ function useTraitScopeChain2() {
46926
47283
  const chain = React85.useContext(TraitScopeContext);
46927
47284
  return chain ?? EMPTY_CHAIN;
46928
47285
  }
46929
- function useTraitScope() {
47286
+ function useTraitScope2() {
46930
47287
  const chain = React85.useContext(TraitScopeContext);
46931
47288
  return chain && chain.length > 0 ? chain[0] : null;
46932
47289
  }
47290
+ var EntityBindingContext = React85.createContext(null);
47291
+ var EMPTY_ENTITY = {};
47292
+ var NOOP_SUBSCRIBE = () => () => void 0;
47293
+ function useEntityBindingSnapshot2(traitName) {
47294
+ const source = React85.useContext(EntityBindingContext);
47295
+ const entity = React85.useSyncExternalStore(
47296
+ source !== null && traitName !== void 0 ? (onStoreChange) => source.subscribe(traitName, onStoreChange) : NOOP_SUBSCRIBE,
47297
+ () => source !== null && traitName !== void 0 ? source.getEntitySnapshot(traitName) : EMPTY_ENTITY
47298
+ );
47299
+ const config = React85.useMemo(
47300
+ () => source !== null && traitName !== void 0 ? source.getConfig(traitName) : void 0,
47301
+ [source, traitName]
47302
+ );
47303
+ return {
47304
+ entity,
47305
+ config,
47306
+ state: source !== null && traitName !== void 0 ? source.getState(traitName) : ""
47307
+ };
47308
+ }
46933
47309
 
46934
47310
  // providers/OfflineModeProvider.tsx
46935
47311
  init_offline_executor();
@@ -47022,6 +47398,10 @@ function GameAudioProvider2({
47022
47398
  }
47023
47399
  GameAudioProvider2.displayName = "GameAudioProvider";
47024
47400
 
47401
+ Object.defineProperty(exports, "ANONYMOUS_USER", {
47402
+ enumerable: true,
47403
+ get: function () { return core.ANONYMOUS_USER; }
47404
+ });
47025
47405
  Object.defineProperty(exports, "DesignThemeProvider", {
47026
47406
  enumerable: true,
47027
47407
  get: function () { return context.DesignThemeProvider; }
@@ -47030,12 +47410,9 @@ Object.defineProperty(exports, "useDesignTheme", {
47030
47410
  enumerable: true,
47031
47411
  get: function () { return context.useDesignTheme; }
47032
47412
  });
47033
- Object.defineProperty(exports, "ANONYMOUS_USER", {
47034
- enumerable: true,
47035
- get: function () { return core.ANONYMOUS_USER; }
47036
- });
47037
47413
  exports.CurrentPagePathContext = CurrentPagePathContext;
47038
47414
  exports.CurrentPagePathProvider = CurrentPagePathProvider;
47415
+ exports.EntityBindingContext = EntityBindingContext;
47039
47416
  exports.EntitySchemaProvider = EntitySchemaProvider;
47040
47417
  exports.EventBusContext = EventBusContext2;
47041
47418
  exports.EventBusProvider = EventBusProvider;
@@ -47065,6 +47442,7 @@ exports.matchPathAmong = matchPathAmong2;
47065
47442
  exports.pathMatches = pathMatches2;
47066
47443
  exports.useActivePage = useActivePage2;
47067
47444
  exports.useCurrentPagePath = useCurrentPagePath2;
47445
+ exports.useEntityBindingSnapshot = useEntityBindingSnapshot2;
47068
47446
  exports.useEntitySchema = useEntitySchema;
47069
47447
  exports.useEntitySchemaOptional = useEntitySchemaOptional6;
47070
47448
  exports.useGameAudioContext = useGameAudioContext2;
@@ -47083,7 +47461,7 @@ exports.useSelectionOptional = useSelectionOptional;
47083
47461
  exports.useServerBridge = useServerBridge;
47084
47462
  exports.useTrait = useTrait;
47085
47463
  exports.useTraitContext = useTraitContext;
47086
- exports.useTraitScope = useTraitScope;
47464
+ exports.useTraitScope = useTraitScope2;
47087
47465
  exports.useTraitScopeChain = useTraitScopeChain2;
47088
47466
  exports.useUser = useUser;
47089
47467
  exports.useUserForEvaluation = useUserForEvaluation;