@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.
@@ -9177,6 +9177,12 @@ function createWebPainter(ctx, onAssetLoad) {
9177
9177
  for (let i = 1; i < points.length; i++) ctx.lineTo(points[i].x, points[i].y);
9178
9178
  if (closed) ctx.closePath();
9179
9179
  };
9180
+ const toCanvasStyle = (style) => {
9181
+ if (typeof style === "string") return style;
9182
+ 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);
9183
+ for (const stop of style.stops) g.addColorStop(stop.offset, stop.color);
9184
+ return g;
9185
+ };
9180
9186
  return {
9181
9187
  setViewport(width, height, dpr) {
9182
9188
  vw = width;
@@ -9230,47 +9236,47 @@ function createWebPainter(ctx, onAssetLoad) {
9230
9236
  ctx.drawImage(img, dest.x, dest.y, dw, dh);
9231
9237
  }
9232
9238
  },
9233
- fillRect(x, y, w, h, color) {
9234
- ctx.fillStyle = color;
9239
+ fillRect(x, y, w, h, style) {
9240
+ ctx.fillStyle = toCanvasStyle(style);
9235
9241
  ctx.fillRect(x, y, w, h);
9236
9242
  },
9237
- strokeRect(x, y, w, h, color, lineWidth = 1) {
9238
- ctx.strokeStyle = color;
9243
+ strokeRect(x, y, w, h, style, lineWidth = 1) {
9244
+ ctx.strokeStyle = toCanvasStyle(style);
9239
9245
  ctx.lineWidth = lineWidth;
9240
9246
  ctx.strokeRect(x, y, w, h);
9241
9247
  },
9242
- fillPoly(points, color) {
9248
+ fillPoly(points, style) {
9243
9249
  if (points.length === 0) return;
9244
9250
  tracePoly(points, true);
9245
- ctx.fillStyle = color;
9251
+ ctx.fillStyle = toCanvasStyle(style);
9246
9252
  ctx.fill();
9247
9253
  },
9248
- strokePoly(points, color, lineWidth = 1, closed = false) {
9254
+ strokePoly(points, style, lineWidth = 1, closed = false) {
9249
9255
  if (points.length === 0) return;
9250
9256
  tracePoly(points, closed);
9251
- ctx.strokeStyle = color;
9257
+ ctx.strokeStyle = toCanvasStyle(style);
9252
9258
  ctx.lineWidth = lineWidth;
9253
9259
  ctx.stroke();
9254
9260
  },
9255
- fillEllipse(cx, cy, rx, ry, color) {
9261
+ fillEllipse(cx, cy, rx, ry, style) {
9256
9262
  ctx.beginPath();
9257
9263
  ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
9258
- ctx.fillStyle = color;
9264
+ ctx.fillStyle = toCanvasStyle(style);
9259
9265
  ctx.fill();
9260
9266
  },
9261
- strokeEllipse(cx, cy, rx, ry, color, lineWidth = 1) {
9267
+ strokeEllipse(cx, cy, rx, ry, style, lineWidth = 1) {
9262
9268
  ctx.beginPath();
9263
9269
  ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
9264
- ctx.strokeStyle = color;
9270
+ ctx.strokeStyle = toCanvasStyle(style);
9265
9271
  ctx.lineWidth = lineWidth;
9266
9272
  ctx.stroke();
9267
9273
  },
9268
- fillPath(d, color) {
9269
- ctx.fillStyle = color;
9274
+ fillPath(d, style) {
9275
+ ctx.fillStyle = toCanvasStyle(style);
9270
9276
  ctx.fill(new Path2D(d));
9271
9277
  },
9272
- strokePath(d, color, lineWidth = 1) {
9273
- ctx.strokeStyle = color;
9278
+ strokePath(d, style, lineWidth = 1) {
9279
+ ctx.strokeStyle = toCanvasStyle(style);
9274
9280
  ctx.lineWidth = lineWidth;
9275
9281
  ctx.stroke(new Path2D(d));
9276
9282
  },
@@ -9445,6 +9451,84 @@ var init_registry = __esm({
9445
9451
  DrawableRegistryContext = createContext(null);
9446
9452
  }
9447
9453
  });
9454
+ function isAnimatedShape(node) {
9455
+ return Boolean(node.animation && node.animation.durationMs > 0 && node.animation.keyframes.length > 0);
9456
+ }
9457
+ function applyShapeAnimation(node, timeMs) {
9458
+ const anim = node.animation;
9459
+ if (!anim || !(anim.durationMs > 0) || anim.keyframes.length === 0) return node;
9460
+ const frames = [...anim.keyframes].sort((a, b) => a.at - b.at);
9461
+ const cycle = timeMs / anim.durationMs;
9462
+ const t = anim.loop === false ? Math.min(cycle, 1) : cycle - Math.floor(cycle);
9463
+ const out = { ...node };
9464
+ const trackValue = (key) => {
9465
+ const defined = frames.filter((f3) => f3[key] !== void 0);
9466
+ if (defined.length === 0) return void 0;
9467
+ let prev;
9468
+ let next;
9469
+ for (const f3 of defined) {
9470
+ if (f3.at <= t) prev = f3;
9471
+ else if (!next) next = f3;
9472
+ }
9473
+ if (!prev) return defined[0][key];
9474
+ if (!next) return prev[key];
9475
+ const span = next.at - prev.at;
9476
+ const k = span > 0 ? (t - prev.at) / span : 1;
9477
+ const a = prev[key];
9478
+ const b = next[key];
9479
+ if (typeof a === "number" && typeof b === "number") return lerp(a, b, k);
9480
+ return a;
9481
+ };
9482
+ for (const key of NUMERIC_TRACKS) {
9483
+ const v = trackValue(key);
9484
+ if (v !== void 0) out[key] = v;
9485
+ }
9486
+ const fill = trackValue("fill");
9487
+ if (fill !== void 0) out.fill = fill;
9488
+ const stroke = trackValue("stroke");
9489
+ if (stroke !== void 0) out.stroke = stroke;
9490
+ const shadowFrames = frames.filter((f3) => f3.shadow !== void 0);
9491
+ if (shadowFrames.length > 0) {
9492
+ const sh = trackValue("shadow");
9493
+ if (sh !== void 0) {
9494
+ let prevSh;
9495
+ let nextSh;
9496
+ for (const f3 of shadowFrames) {
9497
+ if (f3.at <= t) prevSh = f3;
9498
+ else if (!nextSh) nextSh = f3;
9499
+ }
9500
+ if (prevSh?.shadow && nextSh?.shadow) {
9501
+ const span = nextSh.at - prevSh.at;
9502
+ const k = span > 0 ? (t - prevSh.at) / span : 1;
9503
+ out.shadow = { color: prevSh.shadow.color, blur: lerp(prevSh.shadow.blur, nextSh.shadow.blur, k) };
9504
+ } else {
9505
+ out.shadow = sh;
9506
+ }
9507
+ }
9508
+ }
9509
+ return out;
9510
+ }
9511
+ function gradientStyle(g, ox, oy, scale) {
9512
+ if (g.kind === "linear") {
9513
+ if (!g.from || !g.to) return void 0;
9514
+ return {
9515
+ kind: "linear",
9516
+ x1: ox + g.from.x * scale,
9517
+ y1: oy + g.from.y * scale,
9518
+ x2: ox + g.to.x * scale,
9519
+ y2: oy + g.to.y * scale,
9520
+ stops: g.stops
9521
+ };
9522
+ }
9523
+ if (!g.center || g.radius === void 0) return void 0;
9524
+ return {
9525
+ kind: "radial",
9526
+ cx: ox + g.center.x * scale,
9527
+ cy: oy + g.center.y * scale,
9528
+ r: g.radius * scale,
9529
+ stops: g.stops
9530
+ };
9531
+ }
9448
9532
  function DrawShape(props) {
9449
9533
  const register = useContext(DrawableRegistryContext);
9450
9534
  if (register) {
@@ -9455,25 +9539,51 @@ function DrawShape(props) {
9455
9539
  ...opacity !== void 0 && opacity > 0 ? { opacity } : {}
9456
9540
  };
9457
9541
  register(node);
9458
- return /* @__PURE__ */ jsx("div", { "data-draw-shape-debug": "", style: { position: "absolute", width: 4, height: 4, background: "red", zIndex: 9999 } });
9459
9542
  }
9460
9543
  return null;
9461
9544
  }
9462
- var paintShape;
9545
+ var NUMERIC_TRACKS, lerp, paintShape;
9463
9546
  var init_DrawShape = __esm({
9464
9547
  "components/game/atoms/DrawShape.tsx"() {
9465
9548
  "use client";
9466
9549
  init_contract();
9467
9550
  init_registry();
9468
- paintShape = (painter, node, dctx) => {
9551
+ NUMERIC_TRACKS = [
9552
+ "offsetX",
9553
+ "offsetY",
9554
+ "rotate",
9555
+ "opacity",
9556
+ "radiusX",
9557
+ "radiusY",
9558
+ "width",
9559
+ "height",
9560
+ "strokeWidth"
9561
+ ];
9562
+ lerp = (a, b, k) => a + (b - a) * k;
9563
+ paintShape = (painter, rawNode, dctx) => {
9564
+ const node = dctx.time > 0 ? applyShapeAnimation(rawNode, dctx.time) : rawNode;
9469
9565
  if (!isValidScenePos(node.position)) return;
9470
9566
  painter.save();
9471
9567
  if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
9568
+ const origin = dctx.projector.project(node.position);
9569
+ const tileWidth = dctx.projector.tileWidth;
9570
+ if (node.rotate) {
9571
+ const pivot = node.pivot ?? { x: 0.5, y: 0.5 };
9572
+ const px = origin.x + pivot.x * tileWidth;
9573
+ const py = origin.y + pivot.y * tileWidth;
9574
+ painter.translate(px, py);
9575
+ painter.rotate(node.rotate);
9576
+ painter.translate(-px, -py);
9577
+ }
9578
+ if (node.shadow) painter.setShadow({ color: node.shadow.color, blur: node.shadow.blur * tileWidth });
9579
+ const fill = node.fill === "none" ? void 0 : node.fill;
9580
+ const stroke = node.stroke === "none" ? void 0 : node.stroke;
9581
+ const pxFill = node.gradient ? gradientStyle(node.gradient, origin.x, origin.y, tileWidth) ?? fill : fill;
9472
9582
  switch (node.shape) {
9473
9583
  case "cell": {
9474
9584
  const pts = dctx.projector.cellPath(node.position);
9475
- if (node.fill) painter.fillPoly(pts, node.fill);
9476
- if (node.stroke) painter.strokePoly(pts, node.stroke, node.strokeWidth ?? 1, true);
9585
+ if (pxFill) painter.fillPoly(pts, pxFill);
9586
+ if (stroke) painter.strokePoly(pts, stroke, node.strokeWidth ?? 1, true);
9477
9587
  break;
9478
9588
  }
9479
9589
  case "rect": {
@@ -9483,8 +9593,8 @@ var init_DrawShape = __esm({
9483
9593
  const y = p.y + (node.offsetY ?? 0) * tw;
9484
9594
  const w = (node.width ?? 0) * tw;
9485
9595
  const h = (node.height ?? 0) * tw;
9486
- if (node.fill) painter.fillRect(x, y, w, h, node.fill);
9487
- if (node.stroke) painter.strokeRect(x, y, w, h, node.stroke, node.strokeWidth ?? 1);
9596
+ if (pxFill) painter.fillRect(x, y, w, h, pxFill);
9597
+ if (stroke) painter.strokeRect(x, y, w, h, stroke, node.strokeWidth ?? 1);
9488
9598
  break;
9489
9599
  }
9490
9600
  case "ellipse": {
@@ -9494,26 +9604,26 @@ var init_DrawShape = __esm({
9494
9604
  const cy = p.y + (node.offsetY ?? 0) * tw;
9495
9605
  const rx = (node.radiusX ?? 0) * tw;
9496
9606
  const ry = (node.radiusY ?? rx) * tw;
9497
- if (node.fill) painter.fillEllipse(cx, cy, rx, ry, node.fill);
9498
- if (node.stroke) painter.strokeEllipse(cx, cy, rx, ry, node.stroke, node.strokeWidth ?? 1);
9607
+ if (pxFill) painter.fillEllipse(cx, cy, rx, ry, pxFill);
9608
+ if (stroke) painter.strokeEllipse(cx, cy, rx, ry, stroke, node.strokeWidth ?? 1);
9499
9609
  break;
9500
9610
  }
9501
9611
  case "poly": {
9502
- const base = dctx.projector.project(node.position);
9503
- const tw = dctx.projector.tileWidth;
9504
- const pts = (node.points ?? []).map((pt) => ({ x: base.x + pt.x * tw, y: base.y + pt.y * tw }));
9505
- if (node.fill) painter.fillPoly(pts, node.fill);
9506
- if (node.stroke) painter.strokePoly(pts, node.stroke, node.strokeWidth ?? 1, true);
9612
+ const pts = (node.points ?? []).map((pt) => ({
9613
+ x: origin.x + ((node.offsetX ?? 0) + pt.x) * tileWidth,
9614
+ y: origin.y + ((node.offsetY ?? 0) + pt.y) * tileWidth
9615
+ }));
9616
+ if (pxFill) painter.fillPoly(pts, pxFill);
9617
+ if (stroke) painter.strokePoly(pts, stroke, node.strokeWidth ?? 1, true);
9507
9618
  break;
9508
9619
  }
9509
9620
  case "path": {
9510
9621
  if (!node.d) break;
9511
- const base = dctx.projector.project(node.position);
9512
- const tw = dctx.projector.tileWidth;
9513
- painter.translate(base.x, base.y);
9514
- painter.scale(tw, tw);
9515
- if (node.fill) painter.fillPath(node.d, node.fill);
9516
- if (node.stroke) painter.strokePath(node.d, node.stroke, (node.strokeWidth ?? 1) / tw);
9622
+ painter.translate(origin.x + (node.offsetX ?? 0) * tileWidth, origin.y + (node.offsetY ?? 0) * tileWidth);
9623
+ painter.scale(tileWidth, tileWidth);
9624
+ const localFill = node.gradient ? gradientStyle(node.gradient, 0, 0, 1) ?? fill : fill;
9625
+ if (localFill) painter.fillPath(node.d, localFill);
9626
+ if (stroke) painter.strokePath(node.d, stroke, (node.strokeWidth ?? 1) / tileWidth);
9517
9627
  break;
9518
9628
  }
9519
9629
  }
@@ -9725,7 +9835,6 @@ function Canvas2D({
9725
9835
  childDrawablesRef.current.push(node);
9726
9836
  }, []);
9727
9837
  const hasJsxChildren = React84.Children.count(children) > 0;
9728
- drawables && drawables.length > 0 ? drawables : childDrawablesRef.current;
9729
9838
  function isDrawableLayer(node) {
9730
9839
  return node.type === "draw-sprite-layer" || node.type === "draw-shape-layer" || node.type === "draw-text-layer";
9731
9840
  }
@@ -9874,11 +9983,24 @@ function Canvas2D({
9874
9983
  }, [showMinimap, scenePositions]);
9875
9984
  const miniMapWidth = gridExtent.width || 10;
9876
9985
  const miniMapHeight = gridExtent.height || 10;
9877
- const draw = useCallback(() => {
9986
+ const drawableIsAnimated = (node) => {
9987
+ if (node.type === "draw-shape") return isAnimatedShape(node);
9988
+ if (node.type === "draw-group") return Array.isArray(node.items) && node.items.some(drawableIsAnimated);
9989
+ if (node.type === "draw-shape-layer") return Array.isArray(node.items) && node.items.some(isAnimatedShape);
9990
+ return false;
9991
+ };
9992
+ const animRafRef = useRef(0);
9993
+ const drawTimeRef = useRef(() => void 0);
9994
+ const draw = useCallback((timeMs = 0) => {
9878
9995
  const canvas = canvasRef.current;
9879
9996
  if (!canvas) return;
9880
9997
  const ctx = canvas.getContext("2d");
9881
9998
  if (!ctx) return;
9999
+ const scheduleAnimation = (nodes) => {
10000
+ if (!nodes.some(drawableIsAnimated)) return;
10001
+ cancelAnimationFrame(animRafRef.current);
10002
+ animRafRef.current = requestAnimationFrame(() => drawTimeRef.current(performance.now()));
10003
+ };
9882
10004
  const dpr = window.devicePixelRatio || 1;
9883
10005
  canvas.width = viewportSize.width * dpr;
9884
10006
  canvas.height = viewportSize.height * dpr;
@@ -9911,14 +10033,24 @@ function Canvas2D({
9911
10033
  if (!drawables || drawables.length === 0) {
9912
10034
  const childDrawables = childDrawablesRef.current;
9913
10035
  if (childDrawables.length === 0) return;
10036
+ const cam0 = cameraRef.current;
10037
+ if (camera !== "follow" && dragDistance() === 0) {
10038
+ const focus = cameraPos ?? defaultGridFocus;
10039
+ if (focus) {
10040
+ const p = projector.anchorPoint(focus, "center");
10041
+ cam0.x = p.x - viewportSize.width / 2;
10042
+ cam0.y = p.y - viewportSize.height / 2;
10043
+ }
10044
+ }
9914
10045
  const painter0 = createWebPainter(ctx, bumpAtlas);
9915
10046
  painter0.save();
9916
10047
  painter0.translate(viewportSize.width / 2, viewportSize.height / 2);
9917
- painter0.scale(cameraRef.current.zoom, cameraRef.current.zoom);
9918
- painter0.translate(-viewportSize.width / 2, -viewportSize.height / 2);
9919
- const dctx0 = { projector, time: 0, invalidate: bumpAtlas };
10048
+ painter0.scale(cam0.zoom, cam0.zoom);
10049
+ painter0.translate(-viewportSize.width / 2 - cam0.x, -viewportSize.height / 2 - cam0.y);
10050
+ const dctx0 = { projector, time: timeMs, invalidate: bumpAtlas };
9920
10051
  for (const node of childDrawables) paintDrawable(painter0, node, dctx0);
9921
10052
  painter0.restore();
10053
+ scheduleAnimation(childDrawables);
9922
10054
  return;
9923
10055
  }
9924
10056
  const cam = cameraRef.current;
@@ -9938,10 +10070,15 @@ function Canvas2D({
9938
10070
  painter.translate(viewportSize.width / 2, viewportSize.height / 2);
9939
10071
  painter.scale(cam.zoom, cam.zoom);
9940
10072
  painter.translate(-viewportSize.width / 2 - cam.x, -viewportSize.height / 2 - cam.y);
9941
- const dctx = { projector, time: 0, invalidate: bumpAtlas };
10073
+ const dctx = { projector, time: timeMs, invalidate: bumpAtlas };
9942
10074
  for (const node of drawables) paintDrawable(painter, node, dctx);
9943
10075
  painter.restore();
10076
+ scheduleAnimation(drawables);
9944
10077
  }, [viewportSize, backgroundImage, bgColor, drawables, projector, cameraRef, bumpAtlas, getImage, cameraPos, defaultGridFocus, camera, dragDistance]);
10078
+ useEffect(() => {
10079
+ drawTimeRef.current = draw;
10080
+ }, [draw]);
10081
+ useEffect(() => () => cancelAnimationFrame(animRafRef.current), []);
9945
10082
  useEffect(() => {
9946
10083
  if (camera !== "follow" || !followTarget) return;
9947
10084
  const p = projector.anchorPoint(followTarget, "center");
@@ -10164,15 +10301,7 @@ function Canvas2D({
10164
10301
  mapHeight: miniMapHeight
10165
10302
  }
10166
10303
  ) }),
10167
- hasJsxChildren && /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: { position: "absolute", width: 0, height: 0, overflow: "hidden" }, children }),
10168
- /* @__PURE__ */ jsxs("div", { "data-debug": "", style: { position: "absolute", top: 0, left: 0, background: "yellow", color: "black", zIndex: 9999, fontSize: 24, padding: 8 }, children: [
10169
- "C=",
10170
- React84.Children.count(children),
10171
- " J=",
10172
- String(hasJsxChildren),
10173
- " D=",
10174
- drawables?.length ?? -1
10175
- ] })
10304
+ hasJsxChildren && /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: { position: "absolute", width: 0, height: 0, overflow: "hidden" }, children })
10176
10305
  ]
10177
10306
  }
10178
10307
  ) });
@@ -10197,6 +10326,7 @@ var init_Canvas2D = __esm({
10197
10326
  init_webPainter2d();
10198
10327
  init_projector();
10199
10328
  init_paintDispatch();
10329
+ init_DrawShape();
10200
10330
  init_registry();
10201
10331
  init_hitTest();
10202
10332
  init_isometric();
@@ -44984,8 +45114,13 @@ function SlotContentRenderer({
44984
45114
  const isSingleChild = typeof childrenConfig === "string" || typeof childrenConfig === "object" && childrenConfig !== null && !Array.isArray(childrenConfig) && "type" in childrenConfig;
44985
45115
  const hasChildren = PATTERNS_WITH_CHILDREN.has(content.pattern) || Array.isArray(childrenConfig) && childrenConfig.length > 0 || isSingleChild;
44986
45116
  const isDrawHost = isDrawHostPattern(content.pattern);
45117
+ const arr = Array.isArray(childrenConfig) ? childrenConfig : childrenConfig ? [childrenConfig] : [];
45118
+ const hasTraitChildren = arr.some(
45119
+ (c) => typeof c === "string" && TRAIT_BINDING_RE.test(c)
45120
+ );
45121
+ const drawHostUsesReactChildren = isDrawHost && hasTraitChildren;
44987
45122
  const myPath = patternPath ?? "root";
44988
- const renderedChildren = hasChildren && !isDrawHost ? renderPatternChildren(childrenConfig, onDismiss, content.id, myPath, content.sourceTrait, {
45123
+ const renderedChildren = hasChildren && (!isDrawHost || drawHostUsesReactChildren) ? renderPatternChildren(childrenConfig, onDismiss, content.id, myPath, content.sourceTrait, {
44989
45124
  slot: content.slot,
44990
45125
  transitionEvent: content.transitionEvent,
44991
45126
  fromState: content.fromState,
@@ -45046,7 +45181,7 @@ function SlotContentRenderer({
45046
45181
  for (const [k, v] of Object.entries(nodeSlotOverrides)) {
45047
45182
  finalProps[k] = v;
45048
45183
  }
45049
- if (isDrawHost && Array.isArray(childrenConfig) && childrenConfig.length > 0) {
45184
+ if (isDrawHost && !drawHostUsesReactChildren && Array.isArray(childrenConfig) && childrenConfig.length > 0) {
45050
45185
  finalProps.drawables = toDrawableNodes(childrenConfig);
45051
45186
  }
45052
45187
  const entityVal = finalProps.entity;
@@ -45225,6 +45360,8 @@ var init_UISlotRenderer = __esm({
45225
45360
  "vstack",
45226
45361
  "hstack",
45227
45362
  "box",
45363
+ "canvas",
45364
+ "canvas-2d",
45228
45365
  "grid",
45229
45366
  "center",
45230
45367
  "card",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/ui",
3
- "version": "5.139.0",
3
+ "version": "5.140.0",
4
4
  "description": "React UI components, hooks, and providers for Almadar",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -118,11 +118,11 @@
118
118
  "access": "public"
119
119
  },
120
120
  "dependencies": {
121
- "@almadar/core": "^10.45.0",
121
+ "@almadar/core": "^10.46.0",
122
122
  "@almadar/evaluator": "^2.38.0",
123
123
  "@almadar/logger": "^1.10.0",
124
124
  "@almadar/runtime": "^6.47.0",
125
- "@almadar/std": "^16.155.0",
125
+ "@almadar/std": "^16.156.0",
126
126
  "@almadar/syntax": "^1.13.0",
127
127
  "@dnd-kit/core": "^6.3.1",
128
128
  "@dnd-kit/sortable": "^10.0.0",