@almadar/ui 5.166.0 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,7 @@ import { createLogger, setNamespaceLevel, isLogLevelEnabled } from '@almadar/log
6
6
  import { perfStore, pushPerfEntry, wrapCallbackForEvent, perfStart, perfEnd, perfGauge, prepareSchemaForPreview, perfTimeAsync, collectTraitRefsFromResolvedTrait, buildOrbitalsByTrait, collectEmbeddedTraits } from '@almadar/runtime/ui';
7
7
  export { PERF_NAMESPACE, adjustSchemaForMockData, buildMockData, clearPerf, perfEnd, perfStart, perfTime, prepareSchemaForPreview, wrapCallbackForEvent } from '@almadar/runtime/ui';
8
8
  import { StateMachineManager, collectDeclaredConfigDefaults, resolveCallSitePayloadCaptures, createServerEffectHandlers, EffectExecutor, createContextFromBindings, createTickScheduler, isValidCronExpression, parseDurationString, InMemoryPersistence, normalizeCallSiteConfigToValues, interpolateValue } from '@almadar/runtime';
9
- import { mergeEntityFrame, isCircuitEvent, applyListenPayloadMapping, schemaToIR, getPage, clearSchemaCache as clearSchemaCache$1, walkSExpr, buildResolvedTraitConfigs, isRenderBindingMarker, isFileValue, isInlineTrait, containsEntityBinding, isSExpr, isEventPayloadValue, RENDER_BINDING_MARKER, ANIMATION_NAMES } from '@almadar/core';
9
+ import { mergeEntityFrame, isCircuitEvent, applyListenPayloadMapping, schemaToIR, getPage, clearSchemaCache as clearSchemaCache$1, walkSExpr, buildResolvedTraitConfigs, isRenderBindingMarker, isFileValue, isInlineTrait, containsEntityBinding, isSExpr, isEventPayloadValue, RENDER_BINDING_MARKER } from '@almadar/core';
10
10
  import { clsx } from 'clsx';
11
11
  import { twMerge } from 'tailwind-merge';
12
12
  import * as LucideIcons2 from 'lucide-react';
@@ -6258,6 +6258,7 @@ function resolveFxView(item, presets) {
6258
6258
  vy: item.vy ?? row.vy,
6259
6259
  vz: item.vz ?? row.vz,
6260
6260
  particleCount: item.particleCount ?? row.particleCount,
6261
+ animation: item.animation ?? row.animation,
6261
6262
  shape: row.shape,
6262
6263
  count: row.count,
6263
6264
  glow: row.glow,
@@ -7789,9 +7790,6 @@ var init_isometric = __esm({
7789
7790
  };
7790
7791
  }
7791
7792
  });
7792
- function isAnimationName(name) {
7793
- return ANIMATION_NAMES.includes(name);
7794
- }
7795
7793
  function frameRect(frame, row, columns, frameWidth, frameHeight) {
7796
7794
  return {
7797
7795
  sx: frame % columns * frameWidth,
@@ -9618,13 +9616,17 @@ var init_DrawSprite = __esm({
9618
9616
  return;
9619
9617
  }
9620
9618
  if (isSpriteSheetAtlas(atlas)) {
9621
- const def = isAnimationName(node.animation) ? atlas.animations[node.animation] : void 0;
9619
+ const def = atlas.animations[node.animation];
9622
9620
  if (!def) {
9623
9621
  paintFallbackSquare(painter, node, dctx, "sprite-missing");
9624
9622
  return;
9625
9623
  }
9626
- const { frame } = getCurrentFrameFromDef(node.loop === void 0 ? def : { ...def, loop: node.loop }, dctx.time);
9624
+ const { frame } = getCurrentFrameFromDef(node.loop === void 0 ? def : { ...def, loop: node.loop }, node.clockMs ?? dctx.time);
9627
9625
  const r = frameRect(frame, def.row, atlas.columns, atlas.frameWidth, atlas.frameHeight);
9626
+ if (![r.sx, r.sy, r.sw, r.sh].every(Number.isFinite)) {
9627
+ paintFallbackSquare(painter, node, dctx, "sprite-missing");
9628
+ return;
9629
+ }
9628
9630
  src = { x: r.sx, y: r.sy, w: r.sw, h: r.sh };
9629
9631
  }
9630
9632
  }
@@ -10172,7 +10174,10 @@ function expandFxItem(view, node, epochNowMs, dim) {
10172
10174
  anchor: "center",
10173
10175
  width: spriteSize,
10174
10176
  height: spriteSize,
10175
- opacity: fade
10177
+ opacity: fade,
10178
+ animation: view.animation ?? sprite.animations?.[0],
10179
+ // One-shot fx rows (burst) play from the item's birth, not wall-time.
10180
+ clockMs: ageMs
10176
10181
  });
10177
10182
  } else {
10178
10183
  out.push(...proceduralShapes(view, pos, fade, progress));
@@ -11752,6 +11757,7 @@ var init_GameShell = __esm({
11752
11757
  init_Typography();
11753
11758
  init_AtlasImage();
11754
11759
  GAME_FONTS = {
11760
+ fredoka: "Fredoka",
11755
11761
  future: "Kenney Future",
11756
11762
  "future-narrow": "Kenney Future Narrow",
11757
11763
  pixel: "Kenney Pixel",
@@ -11768,10 +11774,10 @@ var init_GameShell = __esm({
11768
11774
  showTopBar = true,
11769
11775
  children,
11770
11776
  backgroundAsset,
11771
- fontFamily = "future",
11777
+ fontFamily,
11772
11778
  "data-theme": dataTheme
11773
11779
  }) => {
11774
- const font = GAME_FONTS[fontFamily] ?? fontFamily;
11780
+ const font = fontFamily ? GAME_FONTS[fontFamily] ?? fontFamily : void 0;
11775
11781
  return /* @__PURE__ */ jsxs(
11776
11782
  Box,
11777
11783
  {
@@ -11786,8 +11792,11 @@ var init_GameShell = __esm({
11786
11792
  color: "var(--color-foreground, #e0e0e0)",
11787
11793
  // The fontFamily knob is a scoped override of the theme contract's
11788
11794
  // display slot: titles/numerics take the game face, body text keeps
11789
- // the active theme's --font-family-body.
11790
- "--font-family-display": `'${font}', ui-sans-serif, system-ui, sans-serif`
11795
+ // the active theme's --font-family-body. Set ONLY when explicitly
11796
+ // passed an always-on inline stamp would shadow the orbital's
11797
+ // inline-theme font-family-display token (inline style beats the
11798
+ // theme provider's vars for the whole shell subtree).
11799
+ ...font ? { "--font-family-display": `'${font}', ui-sans-serif, system-ui, sans-serif` } : {}
11791
11800
  },
11792
11801
  children: [
11793
11802
  backgroundAsset && /* @__PURE__ */ jsx(
@@ -12251,6 +12260,16 @@ function traceShapes(panel, k, width, height) {
12251
12260
  }
12252
12261
  return out;
12253
12262
  }
12263
+ function drawableNeedsAnimation(nodes) {
12264
+ if (!nodes) return false;
12265
+ return nodes.some((node) => {
12266
+ if (node.type === "draw-sprite") return node.animation !== void 0;
12267
+ if (node.type === "draw-fx-layer") return Array.isArray(node.items) && node.items.length > 0;
12268
+ if (node.type === "draw-sprite-layer") return Array.isArray(node.items) && node.items.some((it) => it.animation !== void 0);
12269
+ if (node.type === "draw-group") return Array.isArray(node.items) && drawableNeedsAnimation(node.items);
12270
+ return false;
12271
+ });
12272
+ }
12254
12273
  var DASH_PATTERNS, TRACE_SERIES_COLORS, LearningCanvas;
12255
12274
  var init_LearningCanvas = __esm({
12256
12275
  "components/learning/atoms/LearningCanvas.tsx"() {
@@ -12258,6 +12277,8 @@ var init_LearningCanvas = __esm({
12258
12277
  init_cn();
12259
12278
  init_perf();
12260
12279
  init_useEventBus();
12280
+ init_webPainter2d();
12281
+ init_paintDispatch();
12261
12282
  DASH_PATTERNS = { dashed: [6, 4], dotted: [2, 3] };
12262
12283
  TRACE_SERIES_COLORS = ["#2563eb", "#dc2626", "#16a34a", "#f59e0b"];
12263
12284
  LearningCanvas = ({
@@ -12266,6 +12287,8 @@ var init_LearningCanvas = __esm({
12266
12287
  height = 400,
12267
12288
  backgroundColor,
12268
12289
  shapes = [],
12290
+ drawables,
12291
+ projector,
12269
12292
  readouts,
12270
12293
  traces,
12271
12294
  interactive = false,
@@ -12279,6 +12302,9 @@ var init_LearningCanvas = __esm({
12279
12302
  const eventBus = useEventBus();
12280
12303
  const animRef = useRef(0);
12281
12304
  const hoverIndexRef = useRef(-1);
12305
+ const [drawVersion, setDrawVersion] = useState(0);
12306
+ const invalidateRef = useRef(() => setDrawVersion((v) => v + 1));
12307
+ const needsAnim = useMemo(() => drawableNeedsAnimation(drawables), [drawables]);
12282
12308
  const findShapeAt = useCallback((clientX, clientY) => {
12283
12309
  const canvas = canvasRef.current;
12284
12310
  if (!canvas) return -1;
@@ -12322,20 +12348,29 @@ var init_LearningCanvas = __esm({
12322
12348
  for (const shape of derivedShapes) {
12323
12349
  if (shape.type === "text") drawShape(ctx, shape, width, height, derivedShapes);
12324
12350
  }
12351
+ if (drawables?.length && projector) {
12352
+ const painter = createWebPainter(ctx, invalidateRef.current);
12353
+ const timeMs = needsAnim && typeof performance !== "undefined" ? performance.now() : 0;
12354
+ const dctx = { projector, time: timeMs, invalidate: invalidateRef.current };
12355
+ for (const node of drawables) {
12356
+ paintDrawable(painter, node, dctx);
12357
+ }
12358
+ }
12325
12359
  perfEnd("learningcanvas:paint", _perfT);
12326
- }, [width, height, backgroundColor, derivedShapes]);
12360
+ }, [width, height, backgroundColor, derivedShapes, drawables, projector, needsAnim]);
12327
12361
  useEffect(() => {
12328
12362
  draw();
12329
- }, [draw]);
12363
+ }, [draw, drawVersion]);
12330
12364
  useEffect(() => {
12331
- if (!animate) return;
12365
+ const shouldAnimate = animate || needsAnim;
12366
+ if (!shouldAnimate) return;
12332
12367
  const loop = () => {
12333
12368
  draw();
12334
12369
  animRef.current = requestAnimationFrame(loop);
12335
12370
  };
12336
12371
  animRef.current = requestAnimationFrame(loop);
12337
12372
  return () => cancelAnimationFrame(animRef.current);
12338
- }, [animate, draw]);
12373
+ }, [animate, needsAnim, draw]);
12339
12374
  const handlePointerMove = useCallback(
12340
12375
  (e) => {
12341
12376
  if (!interactive) return;
@@ -24463,8 +24498,9 @@ var init_DashboardLayout = __esm({
24463
24498
  if (notificationClickEvent) eventBus.emit(`UI:${notificationClickEvent}`, {});
24464
24499
  if (onNotificationClick) onNotificationClick();
24465
24500
  };
24466
- const handleTopBarActionClick = (event) => {
24467
- eventBus.emit(`UI:${event}`, {});
24501
+ const handleTopBarActionClick = (action) => {
24502
+ if (action.event) eventBus.emit(`UI:${action.event}`, {});
24503
+ if (action.navigatesTo) eventBus.emit("UI:NAVIGATE", { url: action.navigatesTo });
24468
24504
  };
24469
24505
  const [sidebarOpen, setSidebarOpen] = useState(false);
24470
24506
  const [userMenuOpen, setUserMenuOpen] = useState(false);
@@ -24679,7 +24715,7 @@ var init_DashboardLayout = __esm({
24679
24715
  {
24680
24716
  variant: "ghost",
24681
24717
  className: "relative p-2 rounded-full hover:bg-muted dark:hover:bg-muted",
24682
- onClick: () => handleTopBarActionClick(action.event),
24718
+ onClick: () => handleTopBarActionClick(action),
24683
24719
  "aria-label": action.label ?? (typeof action.icon === "string" ? action.icon : void 0),
24684
24720
  children: [
24685
24721
  /* @__PURE__ */ jsx(Icon, { icon: action.icon, className: "h-5 w-5 text-muted-foreground dark:text-muted-foreground" }),
@@ -24696,7 +24732,7 @@ var init_DashboardLayout = __esm({
24696
24732
  )
24697
24733
  ]
24698
24734
  },
24699
- `${action.event}-${idx}`
24735
+ `${action.event ?? action.navigatesTo ?? "action"}-${idx}`
24700
24736
  )),
24701
24737
  notificationsEnabled && /* @__PURE__ */ jsxs(
24702
24738
  Button,
@@ -30294,6 +30330,7 @@ var init_MathCanvas = __esm({
30294
30330
  angles = [],
30295
30331
  hops = [],
30296
30332
  shapes = [],
30333
+ drawables,
30297
30334
  readouts,
30298
30335
  traces,
30299
30336
  interactive = false,
@@ -30600,6 +30637,42 @@ var init_MathCanvas = __esm({
30600
30637
  hops,
30601
30638
  shapes
30602
30639
  ]);
30640
+ const projector = useMemo(() => {
30641
+ if (!drawables?.length) return void 0;
30642
+ const margin = 24;
30643
+ const plotW = width - margin * 2;
30644
+ const plotH = height - margin * 2;
30645
+ const xScale = plotW / (xMax - xMin);
30646
+ const mapX = (x) => margin + (x - xMin) / (xMax - xMin) * plotW;
30647
+ const mapY = (y) => height - (margin + (y - yMin) / (yMax - yMin) * plotH);
30648
+ const project = (pos) => ({ x: mapX(pos.x), y: mapY(pos.y) });
30649
+ const anchorPoint = (pos, anchor) => {
30650
+ const base = project(pos);
30651
+ if (anchor === "top-left") return base;
30652
+ const cx = base.x + xScale / 2;
30653
+ if (anchor === "ground") return { x: cx, y: base.y + xScale * 0.92 };
30654
+ return { x: cx, y: base.y + xScale / 2 };
30655
+ };
30656
+ const cellPath = (pos) => {
30657
+ const base = project(pos);
30658
+ return [
30659
+ { x: base.x, y: base.y },
30660
+ { x: base.x + xScale, y: base.y },
30661
+ { x: base.x + xScale, y: base.y + xScale },
30662
+ { x: base.x, y: base.y + xScale }
30663
+ ];
30664
+ };
30665
+ return {
30666
+ project,
30667
+ anchorPoint,
30668
+ cellPath,
30669
+ tileWidth: xScale,
30670
+ floorHeight: xScale / 2,
30671
+ diamondTopY: 0,
30672
+ squareGrid: true,
30673
+ worldPixelDirect: false
30674
+ };
30675
+ }, [drawables?.length, width, height, xMin, xMax, yMin, yMax]);
30603
30676
  return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
30604
30677
  title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
30605
30678
  /* @__PURE__ */ jsx(
@@ -30609,6 +30682,8 @@ var init_MathCanvas = __esm({
30609
30682
  height,
30610
30683
  backgroundColor,
30611
30684
  shapes: derivedShapes,
30685
+ drawables,
30686
+ projector,
30612
30687
  readouts,
30613
30688
  traces,
30614
30689
  interactive,
@@ -31838,7 +31913,7 @@ var init_MapView = __esm({
31838
31913
  shadowSize: [41, 41]
31839
31914
  });
31840
31915
  L.Marker.prototype.options.icon = defaultIcon;
31841
- const { useEffect: useEffect63, useRef: useRef63, useCallback: useCallback90, useState: useState94 } = React85__default;
31916
+ const { useEffect: useEffect63, useRef: useRef63, useCallback: useCallback90, useState: useState95 } = React85__default;
31842
31917
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
31843
31918
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
31844
31919
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -31883,7 +31958,7 @@ var init_MapView = __esm({
31883
31958
  showAttribution = true
31884
31959
  }) {
31885
31960
  const eventBus = useEventBus2();
31886
- const [clickedPosition, setClickedPosition] = useState94(null);
31961
+ const [clickedPosition, setClickedPosition] = useState95(null);
31887
31962
  const handleMapClick = useCallback90((lat, lng) => {
31888
31963
  if (showClickedPin) {
31889
31964
  setClickedPosition({ lat, lng });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/ui",
3
- "version": "5.166.0",
3
+ "version": "6.1.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.76.0",
121
+ "@almadar/core": "^10.78.0",
122
122
  "@almadar/evaluator": "^2.43.0",
123
123
  "@almadar/logger": "^1.12.0",
124
- "@almadar/runtime": "^6.66.0",
125
- "@almadar/std": "^16.196.0",
124
+ "@almadar/runtime": "^6.68.0",
125
+ "@almadar/std": "^16.198.0",
126
126
  "@almadar/syntax": "^1.16.0",
127
127
  "@dnd-kit/core": "^6.3.1",
128
128
  "@dnd-kit/sortable": "^10.0.0",
package/themes/index.css CHANGED
@@ -12,7 +12,7 @@
12
12
  * plus VT323 for retro's pixel display type.
13
13
  * Combined into a single Google Fonts request for fewer round-trips. font-display: swap
14
14
  * means body text renders immediately with the fallback chain while the web fonts load. */
15
- @import url("https://fonts.googleapis.com/css2?family=Fraunces:wght@400;500;700;900&family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&family=VT323&display=swap");
15
+ @import url("https://fonts.googleapis.com/css2?family=Fraunces:wght@400;500;700;900&family=Fredoka:wght@400;500;600;700&family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&family=VT323&display=swap");
16
16
 
17
17
  /* Kenney game font faces — consumed by the game themes and GameShell's fontFamily knob. */
18
18
  @import "./_fonts-kenney.css";