@almadar/ui 5.139.0 → 5.140.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.
@@ -9251,6 +9251,12 @@ function createWebPainter(ctx, onAssetLoad) {
9251
9251
  for (let i = 1; i < points.length; i++) ctx.lineTo(points[i].x, points[i].y);
9252
9252
  if (closed) ctx.closePath();
9253
9253
  };
9254
+ const toCanvasStyle = (style) => {
9255
+ if (typeof style === "string") return style;
9256
+ const g = style.kind === "linear" ? ctx.createLinearGradient(style.x1, style.y1, style.x2, style.y2) : ctx.createRadialGradient(style.cx, style.cy, 0, style.cx, style.cy, style.r);
9257
+ for (const stop of style.stops) g.addColorStop(stop.offset, stop.color);
9258
+ return g;
9259
+ };
9254
9260
  return {
9255
9261
  setViewport(width, height, dpr) {
9256
9262
  vw = width;
@@ -9304,47 +9310,47 @@ function createWebPainter(ctx, onAssetLoad) {
9304
9310
  ctx.drawImage(img, dest.x, dest.y, dw, dh);
9305
9311
  }
9306
9312
  },
9307
- fillRect(x, y, w, h, color) {
9308
- ctx.fillStyle = color;
9313
+ fillRect(x, y, w, h, style) {
9314
+ ctx.fillStyle = toCanvasStyle(style);
9309
9315
  ctx.fillRect(x, y, w, h);
9310
9316
  },
9311
- strokeRect(x, y, w, h, color, lineWidth = 1) {
9312
- ctx.strokeStyle = color;
9317
+ strokeRect(x, y, w, h, style, lineWidth = 1) {
9318
+ ctx.strokeStyle = toCanvasStyle(style);
9313
9319
  ctx.lineWidth = lineWidth;
9314
9320
  ctx.strokeRect(x, y, w, h);
9315
9321
  },
9316
- fillPoly(points, color) {
9322
+ fillPoly(points, style) {
9317
9323
  if (points.length === 0) return;
9318
9324
  tracePoly(points, true);
9319
- ctx.fillStyle = color;
9325
+ ctx.fillStyle = toCanvasStyle(style);
9320
9326
  ctx.fill();
9321
9327
  },
9322
- strokePoly(points, color, lineWidth = 1, closed = false) {
9328
+ strokePoly(points, style, lineWidth = 1, closed = false) {
9323
9329
  if (points.length === 0) return;
9324
9330
  tracePoly(points, closed);
9325
- ctx.strokeStyle = color;
9331
+ ctx.strokeStyle = toCanvasStyle(style);
9326
9332
  ctx.lineWidth = lineWidth;
9327
9333
  ctx.stroke();
9328
9334
  },
9329
- fillEllipse(cx, cy, rx, ry, color) {
9335
+ fillEllipse(cx, cy, rx, ry, style) {
9330
9336
  ctx.beginPath();
9331
9337
  ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
9332
- ctx.fillStyle = color;
9338
+ ctx.fillStyle = toCanvasStyle(style);
9333
9339
  ctx.fill();
9334
9340
  },
9335
- strokeEllipse(cx, cy, rx, ry, color, lineWidth = 1) {
9341
+ strokeEllipse(cx, cy, rx, ry, style, lineWidth = 1) {
9336
9342
  ctx.beginPath();
9337
9343
  ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
9338
- ctx.strokeStyle = color;
9344
+ ctx.strokeStyle = toCanvasStyle(style);
9339
9345
  ctx.lineWidth = lineWidth;
9340
9346
  ctx.stroke();
9341
9347
  },
9342
- fillPath(d, color) {
9343
- ctx.fillStyle = color;
9348
+ fillPath(d, style) {
9349
+ ctx.fillStyle = toCanvasStyle(style);
9344
9350
  ctx.fill(new Path2D(d));
9345
9351
  },
9346
- strokePath(d, color, lineWidth = 1) {
9347
- ctx.strokeStyle = color;
9352
+ strokePath(d, style, lineWidth = 1) {
9353
+ ctx.strokeStyle = toCanvasStyle(style);
9348
9354
  ctx.lineWidth = lineWidth;
9349
9355
  ctx.stroke(new Path2D(d));
9350
9356
  },
@@ -9519,6 +9525,84 @@ var init_registry = __esm({
9519
9525
  DrawableRegistryContext = React84.createContext(null);
9520
9526
  }
9521
9527
  });
9528
+ function isAnimatedShape(node) {
9529
+ return Boolean(node.animation && node.animation.durationMs > 0 && node.animation.keyframes.length > 0);
9530
+ }
9531
+ function applyShapeAnimation(node, timeMs) {
9532
+ const anim = node.animation;
9533
+ if (!anim || !(anim.durationMs > 0) || anim.keyframes.length === 0) return node;
9534
+ const frames = [...anim.keyframes].sort((a, b) => a.at - b.at);
9535
+ const cycle = timeMs / anim.durationMs;
9536
+ const t = anim.loop === false ? Math.min(cycle, 1) : cycle - Math.floor(cycle);
9537
+ const out = { ...node };
9538
+ const trackValue = (key) => {
9539
+ const defined = frames.filter((f3) => f3[key] !== void 0);
9540
+ if (defined.length === 0) return void 0;
9541
+ let prev;
9542
+ let next;
9543
+ for (const f3 of defined) {
9544
+ if (f3.at <= t) prev = f3;
9545
+ else if (!next) next = f3;
9546
+ }
9547
+ if (!prev) return defined[0][key];
9548
+ if (!next) return prev[key];
9549
+ const span = next.at - prev.at;
9550
+ const k = span > 0 ? (t - prev.at) / span : 1;
9551
+ const a = prev[key];
9552
+ const b = next[key];
9553
+ if (typeof a === "number" && typeof b === "number") return lerp(a, b, k);
9554
+ return a;
9555
+ };
9556
+ for (const key of NUMERIC_TRACKS) {
9557
+ const v = trackValue(key);
9558
+ if (v !== void 0) out[key] = v;
9559
+ }
9560
+ const fill = trackValue("fill");
9561
+ if (fill !== void 0) out.fill = fill;
9562
+ const stroke = trackValue("stroke");
9563
+ if (stroke !== void 0) out.stroke = stroke;
9564
+ const shadowFrames = frames.filter((f3) => f3.shadow !== void 0);
9565
+ if (shadowFrames.length > 0) {
9566
+ const sh = trackValue("shadow");
9567
+ if (sh !== void 0) {
9568
+ let prevSh;
9569
+ let nextSh;
9570
+ for (const f3 of shadowFrames) {
9571
+ if (f3.at <= t) prevSh = f3;
9572
+ else if (!nextSh) nextSh = f3;
9573
+ }
9574
+ if (prevSh?.shadow && nextSh?.shadow) {
9575
+ const span = nextSh.at - prevSh.at;
9576
+ const k = span > 0 ? (t - prevSh.at) / span : 1;
9577
+ out.shadow = { color: prevSh.shadow.color, blur: lerp(prevSh.shadow.blur, nextSh.shadow.blur, k) };
9578
+ } else {
9579
+ out.shadow = sh;
9580
+ }
9581
+ }
9582
+ }
9583
+ return out;
9584
+ }
9585
+ function gradientStyle(g, ox, oy, scale) {
9586
+ if (g.kind === "linear") {
9587
+ if (!g.from || !g.to) return void 0;
9588
+ return {
9589
+ kind: "linear",
9590
+ x1: ox + g.from.x * scale,
9591
+ y1: oy + g.from.y * scale,
9592
+ x2: ox + g.to.x * scale,
9593
+ y2: oy + g.to.y * scale,
9594
+ stops: g.stops
9595
+ };
9596
+ }
9597
+ if (!g.center || g.radius === void 0) return void 0;
9598
+ return {
9599
+ kind: "radial",
9600
+ cx: ox + g.center.x * scale,
9601
+ cy: oy + g.center.y * scale,
9602
+ r: g.radius * scale,
9603
+ stops: g.stops
9604
+ };
9605
+ }
9522
9606
  function DrawShape(props) {
9523
9607
  const register = React84.useContext(DrawableRegistryContext);
9524
9608
  if (register) {
@@ -9529,25 +9613,51 @@ function DrawShape(props) {
9529
9613
  ...opacity !== void 0 && opacity > 0 ? { opacity } : {}
9530
9614
  };
9531
9615
  register(node);
9532
- return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-draw-shape-debug": "", style: { position: "absolute", width: 4, height: 4, background: "red", zIndex: 9999 } });
9533
9616
  }
9534
9617
  return null;
9535
9618
  }
9536
- var paintShape;
9619
+ var NUMERIC_TRACKS, lerp, paintShape;
9537
9620
  var init_DrawShape = __esm({
9538
9621
  "components/game/atoms/DrawShape.tsx"() {
9539
9622
  "use client";
9540
9623
  init_contract();
9541
9624
  init_registry();
9542
- paintShape = (painter, node, dctx) => {
9625
+ NUMERIC_TRACKS = [
9626
+ "offsetX",
9627
+ "offsetY",
9628
+ "rotate",
9629
+ "opacity",
9630
+ "radiusX",
9631
+ "radiusY",
9632
+ "width",
9633
+ "height",
9634
+ "strokeWidth"
9635
+ ];
9636
+ lerp = (a, b, k) => a + (b - a) * k;
9637
+ paintShape = (painter, rawNode, dctx) => {
9638
+ const node = dctx.time > 0 ? applyShapeAnimation(rawNode, dctx.time) : rawNode;
9543
9639
  if (!isValidScenePos(node.position)) return;
9544
9640
  painter.save();
9545
9641
  if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
9642
+ const origin = dctx.projector.project(node.position);
9643
+ const tileWidth = dctx.projector.tileWidth;
9644
+ if (node.rotate) {
9645
+ const pivot = node.pivot ?? { x: 0.5, y: 0.5 };
9646
+ const px = origin.x + pivot.x * tileWidth;
9647
+ const py = origin.y + pivot.y * tileWidth;
9648
+ painter.translate(px, py);
9649
+ painter.rotate(node.rotate);
9650
+ painter.translate(-px, -py);
9651
+ }
9652
+ if (node.shadow) painter.setShadow({ color: node.shadow.color, blur: node.shadow.blur * tileWidth });
9653
+ const fill = node.fill === "none" ? void 0 : node.fill;
9654
+ const stroke = node.stroke === "none" ? void 0 : node.stroke;
9655
+ const pxFill = node.gradient ? gradientStyle(node.gradient, origin.x, origin.y, tileWidth) ?? fill : fill;
9546
9656
  switch (node.shape) {
9547
9657
  case "cell": {
9548
9658
  const pts = dctx.projector.cellPath(node.position);
9549
- if (node.fill) painter.fillPoly(pts, node.fill);
9550
- if (node.stroke) painter.strokePoly(pts, node.stroke, node.strokeWidth ?? 1, true);
9659
+ if (pxFill) painter.fillPoly(pts, pxFill);
9660
+ if (stroke) painter.strokePoly(pts, stroke, node.strokeWidth ?? 1, true);
9551
9661
  break;
9552
9662
  }
9553
9663
  case "rect": {
@@ -9557,8 +9667,8 @@ var init_DrawShape = __esm({
9557
9667
  const y = p.y + (node.offsetY ?? 0) * tw;
9558
9668
  const w = (node.width ?? 0) * tw;
9559
9669
  const h = (node.height ?? 0) * tw;
9560
- if (node.fill) painter.fillRect(x, y, w, h, node.fill);
9561
- if (node.stroke) painter.strokeRect(x, y, w, h, node.stroke, node.strokeWidth ?? 1);
9670
+ if (pxFill) painter.fillRect(x, y, w, h, pxFill);
9671
+ if (stroke) painter.strokeRect(x, y, w, h, stroke, node.strokeWidth ?? 1);
9562
9672
  break;
9563
9673
  }
9564
9674
  case "ellipse": {
@@ -9568,26 +9678,26 @@ var init_DrawShape = __esm({
9568
9678
  const cy = p.y + (node.offsetY ?? 0) * tw;
9569
9679
  const rx = (node.radiusX ?? 0) * tw;
9570
9680
  const ry = (node.radiusY ?? rx) * tw;
9571
- if (node.fill) painter.fillEllipse(cx, cy, rx, ry, node.fill);
9572
- if (node.stroke) painter.strokeEllipse(cx, cy, rx, ry, node.stroke, node.strokeWidth ?? 1);
9681
+ if (pxFill) painter.fillEllipse(cx, cy, rx, ry, pxFill);
9682
+ if (stroke) painter.strokeEllipse(cx, cy, rx, ry, stroke, node.strokeWidth ?? 1);
9573
9683
  break;
9574
9684
  }
9575
9685
  case "poly": {
9576
- const base = dctx.projector.project(node.position);
9577
- const tw = dctx.projector.tileWidth;
9578
- const pts = (node.points ?? []).map((pt) => ({ x: base.x + pt.x * tw, y: base.y + pt.y * tw }));
9579
- if (node.fill) painter.fillPoly(pts, node.fill);
9580
- if (node.stroke) painter.strokePoly(pts, node.stroke, node.strokeWidth ?? 1, true);
9686
+ const pts = (node.points ?? []).map((pt) => ({
9687
+ x: origin.x + ((node.offsetX ?? 0) + pt.x) * tileWidth,
9688
+ y: origin.y + ((node.offsetY ?? 0) + pt.y) * tileWidth
9689
+ }));
9690
+ if (pxFill) painter.fillPoly(pts, pxFill);
9691
+ if (stroke) painter.strokePoly(pts, stroke, node.strokeWidth ?? 1, true);
9581
9692
  break;
9582
9693
  }
9583
9694
  case "path": {
9584
9695
  if (!node.d) break;
9585
- const base = dctx.projector.project(node.position);
9586
- const tw = dctx.projector.tileWidth;
9587
- painter.translate(base.x, base.y);
9588
- painter.scale(tw, tw);
9589
- if (node.fill) painter.fillPath(node.d, node.fill);
9590
- if (node.stroke) painter.strokePath(node.d, node.stroke, (node.strokeWidth ?? 1) / tw);
9696
+ painter.translate(origin.x + (node.offsetX ?? 0) * tileWidth, origin.y + (node.offsetY ?? 0) * tileWidth);
9697
+ painter.scale(tileWidth, tileWidth);
9698
+ const localFill = node.gradient ? gradientStyle(node.gradient, 0, 0, 1) ?? fill : fill;
9699
+ if (localFill) painter.fillPath(node.d, localFill);
9700
+ if (stroke) painter.strokePath(node.d, stroke, (node.strokeWidth ?? 1) / tileWidth);
9591
9701
  break;
9592
9702
  }
9593
9703
  }
@@ -9799,7 +9909,6 @@ function Canvas2D({
9799
9909
  childDrawablesRef.current.push(node);
9800
9910
  }, []);
9801
9911
  const hasJsxChildren = React84__namespace.Children.count(children) > 0;
9802
- drawables && drawables.length > 0 ? drawables : childDrawablesRef.current;
9803
9912
  function isDrawableLayer(node) {
9804
9913
  return node.type === "draw-sprite-layer" || node.type === "draw-shape-layer" || node.type === "draw-text-layer";
9805
9914
  }
@@ -9948,11 +10057,24 @@ function Canvas2D({
9948
10057
  }, [showMinimap, scenePositions]);
9949
10058
  const miniMapWidth = gridExtent.width || 10;
9950
10059
  const miniMapHeight = gridExtent.height || 10;
9951
- const draw = React84.useCallback(() => {
10060
+ const drawableIsAnimated = (node) => {
10061
+ if (node.type === "draw-shape") return isAnimatedShape(node);
10062
+ if (node.type === "draw-group") return Array.isArray(node.items) && node.items.some(drawableIsAnimated);
10063
+ if (node.type === "draw-shape-layer") return Array.isArray(node.items) && node.items.some(isAnimatedShape);
10064
+ return false;
10065
+ };
10066
+ const animRafRef = React84.useRef(0);
10067
+ const drawTimeRef = React84.useRef(() => void 0);
10068
+ const draw = React84.useCallback((timeMs = 0) => {
9952
10069
  const canvas = canvasRef.current;
9953
10070
  if (!canvas) return;
9954
10071
  const ctx = canvas.getContext("2d");
9955
10072
  if (!ctx) return;
10073
+ const scheduleAnimation = (nodes) => {
10074
+ if (!nodes.some(drawableIsAnimated)) return;
10075
+ cancelAnimationFrame(animRafRef.current);
10076
+ animRafRef.current = requestAnimationFrame(() => drawTimeRef.current(performance.now()));
10077
+ };
9956
10078
  const dpr = window.devicePixelRatio || 1;
9957
10079
  canvas.width = viewportSize.width * dpr;
9958
10080
  canvas.height = viewportSize.height * dpr;
@@ -9985,14 +10107,24 @@ function Canvas2D({
9985
10107
  if (!drawables || drawables.length === 0) {
9986
10108
  const childDrawables = childDrawablesRef.current;
9987
10109
  if (childDrawables.length === 0) return;
10110
+ const cam0 = cameraRef.current;
10111
+ if (camera !== "follow" && dragDistance() === 0) {
10112
+ const focus = cameraPos ?? defaultGridFocus;
10113
+ if (focus) {
10114
+ const p = projector.anchorPoint(focus, "center");
10115
+ cam0.x = p.x - viewportSize.width / 2;
10116
+ cam0.y = p.y - viewportSize.height / 2;
10117
+ }
10118
+ }
9988
10119
  const painter0 = createWebPainter(ctx, bumpAtlas);
9989
10120
  painter0.save();
9990
10121
  painter0.translate(viewportSize.width / 2, viewportSize.height / 2);
9991
- painter0.scale(cameraRef.current.zoom, cameraRef.current.zoom);
9992
- painter0.translate(-viewportSize.width / 2, -viewportSize.height / 2);
9993
- const dctx0 = { projector, time: 0, invalidate: bumpAtlas };
10122
+ painter0.scale(cam0.zoom, cam0.zoom);
10123
+ painter0.translate(-viewportSize.width / 2 - cam0.x, -viewportSize.height / 2 - cam0.y);
10124
+ const dctx0 = { projector, time: timeMs, invalidate: bumpAtlas };
9994
10125
  for (const node of childDrawables) paintDrawable(painter0, node, dctx0);
9995
10126
  painter0.restore();
10127
+ scheduleAnimation(childDrawables);
9996
10128
  return;
9997
10129
  }
9998
10130
  const cam = cameraRef.current;
@@ -10012,10 +10144,15 @@ function Canvas2D({
10012
10144
  painter.translate(viewportSize.width / 2, viewportSize.height / 2);
10013
10145
  painter.scale(cam.zoom, cam.zoom);
10014
10146
  painter.translate(-viewportSize.width / 2 - cam.x, -viewportSize.height / 2 - cam.y);
10015
- const dctx = { projector, time: 0, invalidate: bumpAtlas };
10147
+ const dctx = { projector, time: timeMs, invalidate: bumpAtlas };
10016
10148
  for (const node of drawables) paintDrawable(painter, node, dctx);
10017
10149
  painter.restore();
10150
+ scheduleAnimation(drawables);
10018
10151
  }, [viewportSize, backgroundImage, bgColor, drawables, projector, cameraRef, bumpAtlas, getImage, cameraPos, defaultGridFocus, camera, dragDistance]);
10152
+ React84.useEffect(() => {
10153
+ drawTimeRef.current = draw;
10154
+ }, [draw]);
10155
+ React84.useEffect(() => () => cancelAnimationFrame(animRafRef.current), []);
10019
10156
  React84.useEffect(() => {
10020
10157
  if (camera !== "follow" || !followTarget) return;
10021
10158
  const p = projector.anchorPoint(followTarget, "center");
@@ -10238,15 +10375,7 @@ function Canvas2D({
10238
10375
  mapHeight: miniMapHeight
10239
10376
  }
10240
10377
  ) }),
10241
- hasJsxChildren && /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", style: { position: "absolute", width: 0, height: 0, overflow: "hidden" }, children }),
10242
- /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-debug": "", style: { position: "absolute", top: 0, left: 0, background: "yellow", color: "black", zIndex: 9999, fontSize: 24, padding: 8 }, children: [
10243
- "C=",
10244
- React84__namespace.Children.count(children),
10245
- " J=",
10246
- String(hasJsxChildren),
10247
- " D=",
10248
- drawables?.length ?? -1
10249
- ] })
10378
+ hasJsxChildren && /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", style: { position: "absolute", width: 0, height: 0, overflow: "hidden" }, children })
10250
10379
  ]
10251
10380
  }
10252
10381
  ) });
@@ -10271,6 +10400,7 @@ var init_Canvas2D = __esm({
10271
10400
  init_webPainter2d();
10272
10401
  init_projector();
10273
10402
  init_paintDispatch();
10403
+ init_DrawShape();
10274
10404
  init_registry();
10275
10405
  init_hitTest();
10276
10406
  init_isometric();
@@ -45058,8 +45188,13 @@ function SlotContentRenderer({
45058
45188
  const isSingleChild = typeof childrenConfig === "string" || typeof childrenConfig === "object" && childrenConfig !== null && !Array.isArray(childrenConfig) && "type" in childrenConfig;
45059
45189
  const hasChildren = PATTERNS_WITH_CHILDREN.has(content.pattern) || Array.isArray(childrenConfig) && childrenConfig.length > 0 || isSingleChild;
45060
45190
  const isDrawHost = patterns.isDrawHostPattern(content.pattern);
45191
+ const arr = Array.isArray(childrenConfig) ? childrenConfig : childrenConfig ? [childrenConfig] : [];
45192
+ const hasTraitChildren = arr.some(
45193
+ (c) => typeof c === "string" && TRAIT_BINDING_RE.test(c)
45194
+ );
45195
+ const drawHostUsesReactChildren = isDrawHost && hasTraitChildren;
45061
45196
  const myPath = patternPath ?? "root";
45062
- const renderedChildren = hasChildren && !isDrawHost ? renderPatternChildren(childrenConfig, onDismiss, content.id, myPath, content.sourceTrait, {
45197
+ const renderedChildren = hasChildren && (!isDrawHost || drawHostUsesReactChildren) ? renderPatternChildren(childrenConfig, onDismiss, content.id, myPath, content.sourceTrait, {
45063
45198
  slot: content.slot,
45064
45199
  transitionEvent: content.transitionEvent,
45065
45200
  fromState: content.fromState,
@@ -45120,7 +45255,7 @@ function SlotContentRenderer({
45120
45255
  for (const [k, v] of Object.entries(nodeSlotOverrides)) {
45121
45256
  finalProps[k] = v;
45122
45257
  }
45123
- if (isDrawHost && Array.isArray(childrenConfig) && childrenConfig.length > 0) {
45258
+ if (isDrawHost && !drawHostUsesReactChildren && Array.isArray(childrenConfig) && childrenConfig.length > 0) {
45124
45259
  finalProps.drawables = toDrawableNodes(childrenConfig);
45125
45260
  }
45126
45261
  const entityVal = finalProps.entity;
@@ -45299,6 +45434,8 @@ var init_UISlotRenderer = __esm({
45299
45434
  "vstack",
45300
45435
  "hstack",
45301
45436
  "box",
45437
+ "canvas",
45438
+ "canvas-2d",
45302
45439
  "grid",
45303
45440
  "center",
45304
45441
  "card",