@almadar/ui 5.143.0 → 5.145.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.
@@ -12,7 +12,7 @@ import { useTranslate } from '@almadar/ui/hooks';
12
12
  import { useUISlots, useTheme } from '@almadar/ui/context';
13
13
  import { evaluate, createMinimalContext } from '@almadar/evaluator';
14
14
  import { createContextFromBindings, interpolateValue } from '@almadar/runtime';
15
- import { mergeEntityFrame, isInlineTrait, isRenderBindingMarker } from '@almadar/core';
15
+ import { mergeEntityFrame, isInlineTrait, isRenderBindingMarker, ANIMATION_NAMES } from '@almadar/core';
16
16
  import { createPortal } from 'react-dom';
17
17
  import { wrapCallbackForEvent } from '@almadar/runtime/ui';
18
18
  import { Link, Outlet, useLocation } from 'react-router-dom';
@@ -1451,6 +1451,10 @@ var init_Icon = __esm({
1451
1451
  function isTilesheet(a) {
1452
1452
  return typeof a.tileWidth === "number";
1453
1453
  }
1454
+ function isSpriteSheetAtlas(a) {
1455
+ const s = a;
1456
+ return typeof s.frameWidth === "number" && typeof s.frameHeight === "number" && typeof s.animations === "object";
1457
+ }
1454
1458
  function getAtlas(url, onReady) {
1455
1459
  if (atlasCache.has(url)) return atlasCache.get(url) ?? void 0;
1456
1460
  atlasCache.set(url, void 0);
@@ -1490,6 +1494,7 @@ function subRectFor(atlas, sprite) {
1490
1494
  sh: atlas.tileHeight
1491
1495
  };
1492
1496
  }
1497
+ if (isSpriteSheetAtlas(atlas)) return null;
1493
1498
  const st = atlas.subTextures[sprite];
1494
1499
  if (!st) return null;
1495
1500
  return { sx: st.x, sy: st.y, sw: st.width, sh: st.height };
@@ -1799,7 +1804,7 @@ var init_Button = __esm({
1799
1804
  "data-testid": dataTestId ?? (action ? `action-${action}` : void 0),
1800
1805
  children: [
1801
1806
  isLoading ? /* @__PURE__ */ jsx(Loader2, { className: "h-icon-default w-icon-default animate-spin" }) : resolvedLeftIcon && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: resolvedLeftIcon }),
1802
- children || label,
1807
+ (Array.isArray(children) ? children.length > 0 : Boolean(children)) ? children : label,
1803
1808
  resolvedRightIcon && !isLoading && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: resolvedRightIcon })
1804
1809
  ]
1805
1810
  }
@@ -15946,6 +15951,138 @@ var init_contract = __esm({
15946
15951
  "lib/drawable/contract.ts"() {
15947
15952
  }
15948
15953
  });
15954
+
15955
+ // lib/spriteSheetConstants.ts
15956
+ var SHEET_COLUMNS, SPRITE_SHEET_LAYOUT;
15957
+ var init_spriteSheetConstants = __esm({
15958
+ "lib/spriteSheetConstants.ts"() {
15959
+ SHEET_COLUMNS = 8;
15960
+ SPRITE_SHEET_LAYOUT = {
15961
+ idle: { row: 0, frames: 4, frameRate: 6, loop: true },
15962
+ walk: { row: 1, frames: 8, frameRate: 10, loop: true },
15963
+ attack: { row: 2, frames: 6, frameRate: 12, loop: false },
15964
+ hit: { row: 3, frames: 3, frameRate: 8, loop: false },
15965
+ death: { row: 4, frames: 6, frameRate: 8, loop: false }
15966
+ };
15967
+ }
15968
+ });
15969
+ function isAnimationName(name) {
15970
+ return ANIMATION_NAMES.includes(name);
15971
+ }
15972
+ function inferDirection(dx, dy) {
15973
+ if (dx === 0 && dy === 0) return "se";
15974
+ if (dx >= 0 && dy >= 0) return "se";
15975
+ if (dx <= 0 && dy >= 0) return "sw";
15976
+ if (dx >= 0 && dy <= 0) return "ne";
15977
+ return "nw";
15978
+ }
15979
+ function resolveSheetDirection(facing) {
15980
+ switch (facing) {
15981
+ case "se":
15982
+ return { sheetDir: "se", flipX: false };
15983
+ case "sw":
15984
+ return { sheetDir: "sw", flipX: false };
15985
+ case "ne":
15986
+ return { sheetDir: "sw", flipX: true };
15987
+ case "nw":
15988
+ return { sheetDir: "se", flipX: true };
15989
+ }
15990
+ }
15991
+ function frameRect(frame, row, columns, frameWidth, frameHeight) {
15992
+ return {
15993
+ sx: frame % columns * frameWidth,
15994
+ sy: row * frameHeight,
15995
+ sw: frameWidth,
15996
+ sh: frameHeight
15997
+ };
15998
+ }
15999
+ function getCurrentFrameFromDef(def, elapsed) {
16000
+ const frameDuration = 1e3 / def.frameRate;
16001
+ const totalDuration = def.frames * frameDuration;
16002
+ if (def.loop) {
16003
+ const frame2 = Math.floor(elapsed % totalDuration / frameDuration);
16004
+ return { frame: frame2, finished: false };
16005
+ }
16006
+ if (elapsed >= totalDuration) {
16007
+ return { frame: def.frames - 1, finished: true };
16008
+ }
16009
+ const frame = Math.floor(elapsed / frameDuration);
16010
+ return { frame, finished: false };
16011
+ }
16012
+ function getCurrentFrame(animName, elapsed) {
16013
+ return getCurrentFrameFromDef(SPRITE_SHEET_LAYOUT[animName], elapsed);
16014
+ }
16015
+ function resolveFrame(sheetUrls, frameDims, animState) {
16016
+ if (!sheetUrls) return null;
16017
+ const real = sheetUrls[animState.direction];
16018
+ const { sheetUrl, flipX } = real ? { sheetUrl: real, flipX: false } : (() => {
16019
+ const legacy = resolveSheetDirection(animState.direction);
16020
+ return { sheetUrl: sheetUrls[legacy.sheetDir], flipX: legacy.flipX };
16021
+ })();
16022
+ if (!sheetUrl) return null;
16023
+ const def = SPRITE_SHEET_LAYOUT[animState.animation];
16024
+ const { frame } = getCurrentFrame(animState.animation, animState.elapsed);
16025
+ const rect = frameRect(frame, def.row, def.frames, frameDims.width, frameDims.height);
16026
+ return {
16027
+ sheetUrl,
16028
+ sx: rect.sx,
16029
+ sy: rect.sy,
16030
+ sw: rect.sw,
16031
+ sh: rect.sh,
16032
+ flipX
16033
+ };
16034
+ }
16035
+ function createUnitAnimationState(unitId) {
16036
+ return {
16037
+ unitId,
16038
+ animation: "idle",
16039
+ direction: "se",
16040
+ frame: 0,
16041
+ elapsed: 0,
16042
+ queuedAnimation: null,
16043
+ finished: false
16044
+ };
16045
+ }
16046
+ function transitionAnimation(state, newAnim, direction) {
16047
+ if (state.animation === "death" && state.finished) return state;
16048
+ if (state.animation === newAnim && SPRITE_SHEET_LAYOUT[newAnim].loop) {
16049
+ return direction ? { ...state, direction } : state;
16050
+ }
16051
+ return {
16052
+ ...state,
16053
+ animation: newAnim,
16054
+ direction: direction ?? state.direction,
16055
+ frame: 0,
16056
+ elapsed: 0,
16057
+ queuedAnimation: null,
16058
+ finished: false
16059
+ };
16060
+ }
16061
+ function tickAnimationState(state, deltaMs) {
16062
+ const newElapsed = state.elapsed + deltaMs;
16063
+ const { frame, finished } = getCurrentFrame(state.animation, newElapsed);
16064
+ const def = SPRITE_SHEET_LAYOUT[state.animation];
16065
+ if (finished && !def.loop && !state.finished) {
16066
+ if (state.animation === "death") {
16067
+ return { ...state, elapsed: newElapsed, frame, finished: true };
16068
+ }
16069
+ const nextAnim = state.queuedAnimation ?? "idle";
16070
+ return {
16071
+ ...state,
16072
+ animation: nextAnim,
16073
+ elapsed: 0,
16074
+ frame: 0,
16075
+ queuedAnimation: null,
16076
+ finished: false
16077
+ };
16078
+ }
16079
+ return { ...state, elapsed: newElapsed, frame, finished };
16080
+ }
16081
+ var init_spriteAnimation = __esm({
16082
+ "lib/spriteAnimation.ts"() {
16083
+ init_spriteSheetConstants();
16084
+ }
16085
+ });
15949
16086
  function warnMissingOnce(reason, node) {
15950
16087
  const key = `${reason}:${node.asset.url}:${String(node.asset.atlas)}:${String(node.asset.sprite)}`;
15951
16088
  if (loggedMissing.has(key)) return;
@@ -15963,6 +16100,9 @@ function paintFallbackSquare(painter, node, dctx, reason) {
15963
16100
  painter.strokeRect(rect.x, rect.y, rect.w, rect.h, "#5e564b", Math.max(1, tw / 32));
15964
16101
  painter.restore();
15965
16102
  }
16103
+ function isAnimatedSprite(node) {
16104
+ return node.animation !== void 0 && typeof node.asset?.atlas === "string";
16105
+ }
15966
16106
  function DrawSprite(_props) {
15967
16107
  return null;
15968
16108
  }
@@ -15971,6 +16111,7 @@ var init_DrawSprite = __esm({
15971
16111
  "components/game/atoms/DrawSprite.tsx"() {
15972
16112
  "use client";
15973
16113
  init_atlasSlice();
16114
+ init_spriteAnimation();
15974
16115
  init_imageCache();
15975
16116
  init_contract();
15976
16117
  spriteLog = createLogger("almadar:ui:draw-sprite");
@@ -15984,6 +16125,23 @@ var init_DrawSprite = __esm({
15984
16125
  return;
15985
16126
  }
15986
16127
  let src = typeof node.frame === "object" ? node.frame : void 0;
16128
+ if (!src && node.animation !== void 0 && typeof node.asset.atlas === "string") {
16129
+ const atlas = getAtlas(node.asset.atlas, dctx.invalidate);
16130
+ if (!atlas) {
16131
+ if (atlasFailed(node.asset.atlas)) paintFallbackSquare(painter, node, dctx, "atlas-failed");
16132
+ return;
16133
+ }
16134
+ if (isSpriteSheetAtlas(atlas)) {
16135
+ const def = isAnimationName(node.animation) ? atlas.animations[node.animation] : void 0;
16136
+ if (!def) {
16137
+ paintFallbackSquare(painter, node, dctx, "sprite-missing");
16138
+ return;
16139
+ }
16140
+ const { frame } = getCurrentFrameFromDef(node.loop === void 0 ? def : { ...def, loop: node.loop }, dctx.time);
16141
+ const r = frameRect(frame, def.row, atlas.columns, atlas.frameWidth, atlas.frameHeight);
16142
+ src = { x: r.sx, y: r.sy, w: r.sw, h: r.sh };
16143
+ }
16144
+ }
15987
16145
  if (!src && isAtlasAsset(node.asset)) {
15988
16146
  const atlas = getAtlas(node.asset.atlas, dctx.invalidate);
15989
16147
  if (!atlas) {
@@ -16701,9 +16859,11 @@ function Canvas2D({
16701
16859
  const miniMapHeight = gridExtent.height || 10;
16702
16860
  const drawableIsAnimated = (node) => {
16703
16861
  if (node.type === "draw-shape") return isAnimatedShape(node);
16862
+ if (node.type === "draw-sprite") return isAnimatedSprite(node);
16704
16863
  if (node.type === "draw-group")
16705
16864
  return isAnimatedGroup(node) || Array.isArray(node.items) && node.items.some(drawableIsAnimated);
16706
16865
  if (node.type === "draw-shape-layer") return Array.isArray(node.items) && node.items.some(isAnimatedShape);
16866
+ if (node.type === "draw-sprite-layer") return Array.isArray(node.items) && node.items.some(isAnimatedSprite);
16707
16867
  return false;
16708
16868
  };
16709
16869
  const animRafRef = useRef(0);
@@ -17044,6 +17204,7 @@ var init_Canvas2D = __esm({
17044
17204
  init_projector();
17045
17205
  init_paintDispatch();
17046
17206
  init_DrawShape();
17207
+ init_DrawSprite();
17047
17208
  init_DrawGroup();
17048
17209
  init_registry();
17049
17210
  init_hitTest();
@@ -17117,6 +17278,7 @@ function Canvas({
17117
17278
  cameraMode: to3DCameraMode(camera?.mode),
17118
17279
  ...zoom !== void 0 ? { scale: zoom } : {},
17119
17280
  ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
17281
+ ...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
17120
17282
  ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
17121
17283
  unitScale,
17122
17284
  backgroundColor,
@@ -20105,7 +20267,7 @@ var init_DashboardLayout = __esm({
20105
20267
  }, [isMobile, sidebarOpen]);
20106
20268
  const location = useLocation();
20107
20269
  const ctxPagePath = useCurrentPagePath();
20108
- const activePath = currentPath ?? ctxPagePath ?? location.pathname;
20270
+ const activePath = (currentPath || void 0) ?? ctxPagePath ?? location.pathname;
20109
20271
  const { user: authUser, signOut: authSignOut } = useAuthContext();
20110
20272
  const user = userProp || (authUser ? {
20111
20273
  name: authUser.displayName || authUser.email?.split("@")[0] || "User",
@@ -20572,6 +20734,14 @@ var init_DashboardLayout = __esm({
20572
20734
  NavLinkBottom.displayName = "NavLinkBottom";
20573
20735
  }
20574
20736
  });
20737
+ function downloadItemUrl(url, label) {
20738
+ const a = document.createElement("a");
20739
+ a.href = url;
20740
+ a.download = url.startsWith("data:") ? label : url.split("/").pop() ?? "download";
20741
+ document.body.appendChild(a);
20742
+ a.click();
20743
+ a.remove();
20744
+ }
20575
20745
  function computeMenuStyle(position, triggerRect) {
20576
20746
  const isTop = position.startsWith("top");
20577
20747
  const isRight = position.endsWith("right") || position.endsWith("end");
@@ -20624,6 +20794,7 @@ function SubMenu({
20624
20794
  onClick: () => {
20625
20795
  if (item.disabled) return;
20626
20796
  if (item.event) eventBus.emit(`UI:${item.event}`, { itemId, label: item.label });
20797
+ if (item.url) downloadItemUrl(item.url, item.label);
20627
20798
  item.onClick?.();
20628
20799
  },
20629
20800
  "aria-disabled": item.disabled || void 0,
@@ -20777,6 +20948,7 @@ var init_Menu = __esm({
20777
20948
  setActiveSubMenu(itemId);
20778
20949
  } else {
20779
20950
  if (item.event) eventBus.emit(`UI:${item.event}`, { itemId, label: item.label });
20951
+ if (item.url) downloadItemUrl(item.url, item.label);
20780
20952
  item.onClick?.();
20781
20953
  setIsOpen(false);
20782
20954
  }
@@ -20812,12 +20984,19 @@ var init_Menu = __esm({
20812
20984
  "bottom-end": "bottom-start"
20813
20985
  };
20814
20986
  const effectivePosition = direction === "rtl" ? rtlMirror[position] ?? position : position;
20815
- const triggerChild = React77__default.isValidElement(trigger) ? trigger : /* @__PURE__ */ jsx(Typography, { variant: "small", as: "span", children: trigger });
20816
- const triggerElement = React77__default.cloneElement(
20817
- triggerChild,
20987
+ const triggerElement = React77__default.isValidElement(trigger) ? React77__default.cloneElement(trigger, {
20988
+ ref: triggerRef,
20989
+ onClick: handleToggle
20990
+ }) : /* @__PURE__ */ jsx(
20991
+ Box,
20818
20992
  {
20819
- ref: triggerRef,
20820
- onClick: handleToggle
20993
+ as: "span",
20994
+ ref: (el) => {
20995
+ triggerRef.current = el;
20996
+ },
20997
+ onClick: handleToggle,
20998
+ className: "inline-flex",
20999
+ children: typeof trigger === "string" || typeof trigger === "number" ? /* @__PURE__ */ jsx(Typography, { variant: "small", as: "span", children: trigger }) : trigger
20821
21000
  }
20822
21001
  );
20823
21002
  const renderMenuItems = (menuItems) => menuItems.map((item, index) => {
@@ -26502,134 +26681,6 @@ var init_makeAsset = __esm({
26502
26681
  }
26503
26682
  });
26504
26683
 
26505
- // lib/spriteSheetConstants.ts
26506
- var SHEET_COLUMNS, SPRITE_SHEET_LAYOUT;
26507
- var init_spriteSheetConstants = __esm({
26508
- "lib/spriteSheetConstants.ts"() {
26509
- SHEET_COLUMNS = 8;
26510
- SPRITE_SHEET_LAYOUT = {
26511
- idle: { row: 0, frames: 4, frameRate: 6, loop: true },
26512
- walk: { row: 1, frames: 8, frameRate: 10, loop: true },
26513
- attack: { row: 2, frames: 6, frameRate: 12, loop: false },
26514
- hit: { row: 3, frames: 3, frameRate: 8, loop: false },
26515
- death: { row: 4, frames: 6, frameRate: 8, loop: false }
26516
- };
26517
- }
26518
- });
26519
-
26520
- // lib/spriteAnimation.ts
26521
- function inferDirection(dx, dy) {
26522
- if (dx === 0 && dy === 0) return "se";
26523
- if (dx >= 0 && dy >= 0) return "se";
26524
- if (dx <= 0 && dy >= 0) return "sw";
26525
- if (dx >= 0 && dy <= 0) return "ne";
26526
- return "nw";
26527
- }
26528
- function resolveSheetDirection(facing) {
26529
- switch (facing) {
26530
- case "se":
26531
- return { sheetDir: "se", flipX: false };
26532
- case "sw":
26533
- return { sheetDir: "sw", flipX: false };
26534
- case "ne":
26535
- return { sheetDir: "sw", flipX: true };
26536
- case "nw":
26537
- return { sheetDir: "se", flipX: true };
26538
- }
26539
- }
26540
- function frameRect(frame, row, columns, frameWidth, frameHeight) {
26541
- return {
26542
- sx: frame % columns * frameWidth,
26543
- sy: row * frameHeight,
26544
- sw: frameWidth,
26545
- sh: frameHeight
26546
- };
26547
- }
26548
- function getCurrentFrameFromDef(def, elapsed) {
26549
- const frameDuration = 1e3 / def.frameRate;
26550
- const totalDuration = def.frames * frameDuration;
26551
- if (def.loop) {
26552
- const frame2 = Math.floor(elapsed % totalDuration / frameDuration);
26553
- return { frame: frame2, finished: false };
26554
- }
26555
- if (elapsed >= totalDuration) {
26556
- return { frame: def.frames - 1, finished: true };
26557
- }
26558
- const frame = Math.floor(elapsed / frameDuration);
26559
- return { frame, finished: false };
26560
- }
26561
- function getCurrentFrame(animName, elapsed) {
26562
- return getCurrentFrameFromDef(SPRITE_SHEET_LAYOUT[animName], elapsed);
26563
- }
26564
- function resolveFrame(sheetUrls, frameDims, animState) {
26565
- if (!sheetUrls) return null;
26566
- const { sheetDir, flipX } = resolveSheetDirection(animState.direction);
26567
- const sheetUrl = sheetUrls[sheetDir];
26568
- if (!sheetUrl) return null;
26569
- const def = SPRITE_SHEET_LAYOUT[animState.animation];
26570
- const { frame } = getCurrentFrame(animState.animation, animState.elapsed);
26571
- const rect = frameRect(frame, def.row, def.frames, frameDims.width, frameDims.height);
26572
- return {
26573
- sheetUrl,
26574
- sx: rect.sx,
26575
- sy: rect.sy,
26576
- sw: rect.sw,
26577
- sh: rect.sh,
26578
- flipX
26579
- };
26580
- }
26581
- function createUnitAnimationState(unitId) {
26582
- return {
26583
- unitId,
26584
- animation: "idle",
26585
- direction: "se",
26586
- frame: 0,
26587
- elapsed: 0,
26588
- queuedAnimation: null,
26589
- finished: false
26590
- };
26591
- }
26592
- function transitionAnimation(state, newAnim, direction) {
26593
- if (state.animation === "death" && state.finished) return state;
26594
- if (state.animation === newAnim && SPRITE_SHEET_LAYOUT[newAnim].loop) {
26595
- return direction ? { ...state, direction } : state;
26596
- }
26597
- return {
26598
- ...state,
26599
- animation: newAnim,
26600
- direction: direction ?? state.direction,
26601
- frame: 0,
26602
- elapsed: 0,
26603
- queuedAnimation: null,
26604
- finished: false
26605
- };
26606
- }
26607
- function tickAnimationState(state, deltaMs) {
26608
- const newElapsed = state.elapsed + deltaMs;
26609
- const { frame, finished } = getCurrentFrame(state.animation, newElapsed);
26610
- const def = SPRITE_SHEET_LAYOUT[state.animation];
26611
- if (finished && !def.loop && !state.finished) {
26612
- if (state.animation === "death") {
26613
- return { ...state, elapsed: newElapsed, frame, finished: true };
26614
- }
26615
- const nextAnim = state.queuedAnimation ?? "idle";
26616
- return {
26617
- ...state,
26618
- animation: nextAnim,
26619
- elapsed: 0,
26620
- frame: 0,
26621
- queuedAnimation: null,
26622
- finished: false
26623
- };
26624
- }
26625
- return { ...state, elapsed: newElapsed, frame, finished };
26626
- }
26627
- var init_spriteAnimation = __esm({
26628
- "lib/spriteAnimation.ts"() {
26629
- init_spriteSheetConstants();
26630
- }
26631
- });
26632
-
26633
26684
  // lib/gameShared.ts
26634
26685
  var init_gameShared = __esm({
26635
26686
  "lib/gameShared.ts"() {