@almadar/ui 6.0.0 → 6.2.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';
@@ -3733,9 +3733,16 @@ var init_Input = __esm({
3733
3733
  }
3734
3734
  };
3735
3735
  const handleKeyDown = (e) => {
3736
- if (action && e.key === "Enter") {
3737
- eventBus.emit(`UI:${action}`, { value: e.currentTarget.value });
3736
+ if (!action || e.key !== "Enter") return;
3737
+ const raw = e.currentTarget.value;
3738
+ if (type === "number") {
3739
+ if (raw.trim() === "") return;
3740
+ const numeric = Number(raw);
3741
+ if (Number.isNaN(numeric)) return;
3742
+ eventBus.emit(`UI:${action}`, { value: numeric });
3743
+ return;
3738
3744
  }
3745
+ eventBus.emit(`UI:${action}`, { value: raw });
3739
3746
  };
3740
3747
  const wrapField = (field, fullWidth = true) => /* @__PURE__ */ jsxs("div", { className: fullWidth ? "w-full" : "w-fit", children: [
3741
3748
  label && /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium text-foreground mb-1", children: label }),
@@ -6258,6 +6265,7 @@ function resolveFxView(item, presets) {
6258
6265
  vy: item.vy ?? row.vy,
6259
6266
  vz: item.vz ?? row.vz,
6260
6267
  particleCount: item.particleCount ?? row.particleCount,
6268
+ animation: item.animation ?? row.animation,
6261
6269
  shape: row.shape,
6262
6270
  count: row.count,
6263
6271
  glow: row.glow,
@@ -7789,9 +7797,6 @@ var init_isometric = __esm({
7789
7797
  };
7790
7798
  }
7791
7799
  });
7792
- function isAnimationName(name) {
7793
- return ANIMATION_NAMES.includes(name);
7794
- }
7795
7800
  function frameRect(frame, row, columns, frameWidth, frameHeight) {
7796
7801
  return {
7797
7802
  sx: frame % columns * frameWidth,
@@ -9618,13 +9623,17 @@ var init_DrawSprite = __esm({
9618
9623
  return;
9619
9624
  }
9620
9625
  if (isSpriteSheetAtlas(atlas)) {
9621
- const def = isAnimationName(node.animation) ? atlas.animations[node.animation] : void 0;
9626
+ const def = atlas.animations[node.animation];
9622
9627
  if (!def) {
9623
9628
  paintFallbackSquare(painter, node, dctx, "sprite-missing");
9624
9629
  return;
9625
9630
  }
9626
- const { frame } = getCurrentFrameFromDef(node.loop === void 0 ? def : { ...def, loop: node.loop }, dctx.time);
9631
+ const { frame } = getCurrentFrameFromDef(node.loop === void 0 ? def : { ...def, loop: node.loop }, node.clockMs ?? dctx.time);
9627
9632
  const r = frameRect(frame, def.row, atlas.columns, atlas.frameWidth, atlas.frameHeight);
9633
+ if (![r.sx, r.sy, r.sw, r.sh].every(Number.isFinite)) {
9634
+ paintFallbackSquare(painter, node, dctx, "sprite-missing");
9635
+ return;
9636
+ }
9628
9637
  src = { x: r.sx, y: r.sy, w: r.sw, h: r.sh };
9629
9638
  }
9630
9639
  }
@@ -10172,7 +10181,10 @@ function expandFxItem(view, node, epochNowMs, dim) {
10172
10181
  anchor: "center",
10173
10182
  width: spriteSize,
10174
10183
  height: spriteSize,
10175
- opacity: fade
10184
+ opacity: fade,
10185
+ animation: view.animation ?? sprite.animations?.[0],
10186
+ // One-shot fx rows (burst) play from the item's birth, not wall-time.
10187
+ clockMs: ageMs
10176
10188
  });
10177
10189
  } else {
10178
10190
  out.push(...proceduralShapes(view, pos, fade, progress));
@@ -11752,6 +11764,7 @@ var init_GameShell = __esm({
11752
11764
  init_Typography();
11753
11765
  init_AtlasImage();
11754
11766
  GAME_FONTS = {
11767
+ fredoka: "Fredoka",
11755
11768
  future: "Kenney Future",
11756
11769
  "future-narrow": "Kenney Future Narrow",
11757
11770
  pixel: "Kenney Pixel",
@@ -11768,10 +11781,10 @@ var init_GameShell = __esm({
11768
11781
  showTopBar = true,
11769
11782
  children,
11770
11783
  backgroundAsset,
11771
- fontFamily = "future",
11784
+ fontFamily,
11772
11785
  "data-theme": dataTheme
11773
11786
  }) => {
11774
- const font = GAME_FONTS[fontFamily] ?? fontFamily;
11787
+ const font = fontFamily ? GAME_FONTS[fontFamily] ?? fontFamily : void 0;
11775
11788
  return /* @__PURE__ */ jsxs(
11776
11789
  Box,
11777
11790
  {
@@ -11786,8 +11799,11 @@ var init_GameShell = __esm({
11786
11799
  color: "var(--color-foreground, #e0e0e0)",
11787
11800
  // The fontFamily knob is a scoped override of the theme contract's
11788
11801
  // 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`
11802
+ // the active theme's --font-family-body. Set ONLY when explicitly
11803
+ // passed an always-on inline stamp would shadow the orbital's
11804
+ // inline-theme font-family-display token (inline style beats the
11805
+ // theme provider's vars for the whole shell subtree).
11806
+ ...font ? { "--font-family-display": `'${font}', ui-sans-serif, system-ui, sans-serif` } : {}
11791
11807
  },
11792
11808
  children: [
11793
11809
  backgroundAsset && /* @__PURE__ */ jsx(
@@ -12251,6 +12267,16 @@ function traceShapes(panel, k, width, height) {
12251
12267
  }
12252
12268
  return out;
12253
12269
  }
12270
+ function drawableNeedsAnimation(nodes) {
12271
+ if (!nodes) return false;
12272
+ return nodes.some((node) => {
12273
+ if (node.type === "draw-sprite") return node.animation !== void 0;
12274
+ if (node.type === "draw-fx-layer") return Array.isArray(node.items) && node.items.length > 0;
12275
+ if (node.type === "draw-sprite-layer") return Array.isArray(node.items) && node.items.some((it) => it.animation !== void 0);
12276
+ if (node.type === "draw-group") return Array.isArray(node.items) && drawableNeedsAnimation(node.items);
12277
+ return false;
12278
+ });
12279
+ }
12254
12280
  var DASH_PATTERNS, TRACE_SERIES_COLORS, LearningCanvas;
12255
12281
  var init_LearningCanvas = __esm({
12256
12282
  "components/learning/atoms/LearningCanvas.tsx"() {
@@ -12258,6 +12284,8 @@ var init_LearningCanvas = __esm({
12258
12284
  init_cn();
12259
12285
  init_perf();
12260
12286
  init_useEventBus();
12287
+ init_webPainter2d();
12288
+ init_paintDispatch();
12261
12289
  DASH_PATTERNS = { dashed: [6, 4], dotted: [2, 3] };
12262
12290
  TRACE_SERIES_COLORS = ["#2563eb", "#dc2626", "#16a34a", "#f59e0b"];
12263
12291
  LearningCanvas = ({
@@ -12266,6 +12294,8 @@ var init_LearningCanvas = __esm({
12266
12294
  height = 400,
12267
12295
  backgroundColor,
12268
12296
  shapes = [],
12297
+ drawables,
12298
+ projector,
12269
12299
  readouts,
12270
12300
  traces,
12271
12301
  interactive = false,
@@ -12279,6 +12309,9 @@ var init_LearningCanvas = __esm({
12279
12309
  const eventBus = useEventBus();
12280
12310
  const animRef = useRef(0);
12281
12311
  const hoverIndexRef = useRef(-1);
12312
+ const [drawVersion, setDrawVersion] = useState(0);
12313
+ const invalidateRef = useRef(() => setDrawVersion((v) => v + 1));
12314
+ const needsAnim = useMemo(() => drawableNeedsAnimation(drawables), [drawables]);
12282
12315
  const findShapeAt = useCallback((clientX, clientY) => {
12283
12316
  const canvas = canvasRef.current;
12284
12317
  if (!canvas) return -1;
@@ -12322,20 +12355,29 @@ var init_LearningCanvas = __esm({
12322
12355
  for (const shape of derivedShapes) {
12323
12356
  if (shape.type === "text") drawShape(ctx, shape, width, height, derivedShapes);
12324
12357
  }
12358
+ if (drawables?.length && projector) {
12359
+ const painter = createWebPainter(ctx, invalidateRef.current);
12360
+ const timeMs = needsAnim && typeof performance !== "undefined" ? performance.now() : 0;
12361
+ const dctx = { projector, time: timeMs, invalidate: invalidateRef.current };
12362
+ for (const node of drawables) {
12363
+ paintDrawable(painter, node, dctx);
12364
+ }
12365
+ }
12325
12366
  perfEnd("learningcanvas:paint", _perfT);
12326
- }, [width, height, backgroundColor, derivedShapes]);
12367
+ }, [width, height, backgroundColor, derivedShapes, drawables, projector, needsAnim]);
12327
12368
  useEffect(() => {
12328
12369
  draw();
12329
- }, [draw]);
12370
+ }, [draw, drawVersion]);
12330
12371
  useEffect(() => {
12331
- if (!animate) return;
12372
+ const shouldAnimate = animate || needsAnim;
12373
+ if (!shouldAnimate) return;
12332
12374
  const loop = () => {
12333
12375
  draw();
12334
12376
  animRef.current = requestAnimationFrame(loop);
12335
12377
  };
12336
12378
  animRef.current = requestAnimationFrame(loop);
12337
12379
  return () => cancelAnimationFrame(animRef.current);
12338
- }, [animate, draw]);
12380
+ }, [animate, needsAnim, draw]);
12339
12381
  const handlePointerMove = useCallback(
12340
12382
  (e) => {
12341
12383
  if (!interactive) return;
@@ -24463,8 +24505,9 @@ var init_DashboardLayout = __esm({
24463
24505
  if (notificationClickEvent) eventBus.emit(`UI:${notificationClickEvent}`, {});
24464
24506
  if (onNotificationClick) onNotificationClick();
24465
24507
  };
24466
- const handleTopBarActionClick = (event) => {
24467
- eventBus.emit(`UI:${event}`, {});
24508
+ const handleTopBarActionClick = (action) => {
24509
+ if (action.event) eventBus.emit(`UI:${action.event}`, {});
24510
+ if (action.navigatesTo) eventBus.emit("UI:NAVIGATE", { url: action.navigatesTo });
24468
24511
  };
24469
24512
  const [sidebarOpen, setSidebarOpen] = useState(false);
24470
24513
  const [userMenuOpen, setUserMenuOpen] = useState(false);
@@ -24679,7 +24722,7 @@ var init_DashboardLayout = __esm({
24679
24722
  {
24680
24723
  variant: "ghost",
24681
24724
  className: "relative p-2 rounded-full hover:bg-muted dark:hover:bg-muted",
24682
- onClick: () => handleTopBarActionClick(action.event),
24725
+ onClick: () => handleTopBarActionClick(action),
24683
24726
  "aria-label": action.label ?? (typeof action.icon === "string" ? action.icon : void 0),
24684
24727
  children: [
24685
24728
  /* @__PURE__ */ jsx(Icon, { icon: action.icon, className: "h-5 w-5 text-muted-foreground dark:text-muted-foreground" }),
@@ -24696,7 +24739,7 @@ var init_DashboardLayout = __esm({
24696
24739
  )
24697
24740
  ]
24698
24741
  },
24699
- `${action.event}-${idx}`
24742
+ `${action.event ?? action.navigatesTo ?? "action"}-${idx}`
24700
24743
  )),
24701
24744
  notificationsEnabled && /* @__PURE__ */ jsxs(
24702
24745
  Button,
@@ -30294,6 +30337,7 @@ var init_MathCanvas = __esm({
30294
30337
  angles = [],
30295
30338
  hops = [],
30296
30339
  shapes = [],
30340
+ drawables,
30297
30341
  readouts,
30298
30342
  traces,
30299
30343
  interactive = false,
@@ -30600,6 +30644,42 @@ var init_MathCanvas = __esm({
30600
30644
  hops,
30601
30645
  shapes
30602
30646
  ]);
30647
+ const projector = useMemo(() => {
30648
+ if (!drawables?.length) return void 0;
30649
+ const margin = 24;
30650
+ const plotW = width - margin * 2;
30651
+ const plotH = height - margin * 2;
30652
+ const xScale = plotW / (xMax - xMin);
30653
+ const mapX = (x) => margin + (x - xMin) / (xMax - xMin) * plotW;
30654
+ const mapY = (y) => height - (margin + (y - yMin) / (yMax - yMin) * plotH);
30655
+ const project = (pos) => ({ x: mapX(pos.x), y: mapY(pos.y) });
30656
+ const anchorPoint = (pos, anchor) => {
30657
+ const base = project(pos);
30658
+ if (anchor === "top-left") return base;
30659
+ const cx = base.x + xScale / 2;
30660
+ if (anchor === "ground") return { x: cx, y: base.y + xScale * 0.92 };
30661
+ return { x: cx, y: base.y + xScale / 2 };
30662
+ };
30663
+ const cellPath = (pos) => {
30664
+ const base = project(pos);
30665
+ return [
30666
+ { x: base.x, y: base.y },
30667
+ { x: base.x + xScale, y: base.y },
30668
+ { x: base.x + xScale, y: base.y + xScale },
30669
+ { x: base.x, y: base.y + xScale }
30670
+ ];
30671
+ };
30672
+ return {
30673
+ project,
30674
+ anchorPoint,
30675
+ cellPath,
30676
+ tileWidth: xScale,
30677
+ floorHeight: xScale / 2,
30678
+ diamondTopY: 0,
30679
+ squareGrid: true,
30680
+ worldPixelDirect: false
30681
+ };
30682
+ }, [drawables?.length, width, height, xMin, xMax, yMin, yMax]);
30603
30683
  return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
30604
30684
  title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
30605
30685
  /* @__PURE__ */ jsx(
@@ -30609,6 +30689,8 @@ var init_MathCanvas = __esm({
30609
30689
  height,
30610
30690
  backgroundColor,
30611
30691
  shapes: derivedShapes,
30692
+ drawables,
30693
+ projector,
30612
30694
  readouts,
30613
30695
  traces,
30614
30696
  interactive,
@@ -31838,7 +31920,7 @@ var init_MapView = __esm({
31838
31920
  shadowSize: [41, 41]
31839
31921
  });
31840
31922
  L.Marker.prototype.options.icon = defaultIcon;
31841
- const { useEffect: useEffect63, useRef: useRef63, useCallback: useCallback90, useState: useState94 } = React85__default;
31923
+ const { useEffect: useEffect63, useRef: useRef63, useCallback: useCallback90, useState: useState95 } = React85__default;
31842
31924
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
31843
31925
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
31844
31926
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -31883,7 +31965,7 @@ var init_MapView = __esm({
31883
31965
  showAttribution = true
31884
31966
  }) {
31885
31967
  const eventBus = useEventBus2();
31886
- const [clickedPosition, setClickedPosition] = useState94(null);
31968
+ const [clickedPosition, setClickedPosition] = useState95(null);
31887
31969
  const handleMapClick = useCallback90((lat, lng) => {
31888
31970
  if (showClickedPin) {
31889
31971
  setClickedPosition({ lat, lng });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/ui",
3
- "version": "6.0.0",
3
+ "version": "6.2.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.77.0",
121
+ "@almadar/core": "^10.79.0",
122
122
  "@almadar/evaluator": "^2.43.0",
123
123
  "@almadar/logger": "^1.12.0",
124
- "@almadar/runtime": "^6.67.0",
125
- "@almadar/std": "^16.197.0",
124
+ "@almadar/runtime": "^6.69.0",
125
+ "@almadar/std": "^16.199.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";