@almadar/ui 5.9.8 → 5.9.10

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.
@@ -14619,7 +14619,7 @@ var init_MapView = __esm({
14619
14619
  shadowSize: [41, 41]
14620
14620
  });
14621
14621
  L.Marker.prototype.options.icon = defaultIcon;
14622
- const { useEffect: useEffect89, useRef: useRef88, useCallback: useCallback129, useState: useState124 } = React98__namespace.default;
14622
+ const { useEffect: useEffect89, useRef: useRef88, useCallback: useCallback130, useState: useState124 } = React98__namespace.default;
14623
14623
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
14624
14624
  const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
14625
14625
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -14665,7 +14665,7 @@ var init_MapView = __esm({
14665
14665
  }) {
14666
14666
  const eventBus = useEventBus3();
14667
14667
  const [clickedPosition, setClickedPosition] = useState124(null);
14668
- const handleMapClick = useCallback129((lat, lng) => {
14668
+ const handleMapClick = useCallback130((lat, lng) => {
14669
14669
  if (showClickedPin) {
14670
14670
  setClickedPosition({ lat, lng });
14671
14671
  }
@@ -14674,7 +14674,7 @@ var init_MapView = __esm({
14674
14674
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
14675
14675
  }
14676
14676
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
14677
- const handleMarkerClick = useCallback129((marker) => {
14677
+ const handleMarkerClick = useCallback130((marker) => {
14678
14678
  onMarkerClick?.(marker);
14679
14679
  if (markerClickEvent) {
14680
14680
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -30399,12 +30399,15 @@ function GameCanvas2D({
30399
30399
  tickEvent,
30400
30400
  drawEvent,
30401
30401
  fps = 60,
30402
+ backgroundImage,
30403
+ assetBaseUrl = "",
30402
30404
  className
30403
30405
  }) {
30404
30406
  const canvasRef = React98__namespace.useRef(null);
30405
30407
  const rafRef = React98__namespace.useRef(0);
30406
30408
  const frameRef = React98__namespace.useRef(0);
30407
30409
  const lastTimeRef = React98__namespace.useRef(0);
30410
+ const imageCache = React98__namespace.useRef(/* @__PURE__ */ new Map());
30408
30411
  const emit = useEmitEvent();
30409
30412
  const onDrawRef = React98__namespace.useRef(onDraw);
30410
30413
  onDrawRef.current = onDraw;
@@ -30416,6 +30419,18 @@ function GameCanvas2D({
30416
30419
  drawEventRef.current = drawEvent;
30417
30420
  const emitRef = React98__namespace.useRef(emit);
30418
30421
  emitRef.current = emit;
30422
+ const loadImage = React98__namespace.useCallback((url) => {
30423
+ const fullUrl = url.startsWith("http") ? url : `${assetBaseUrl}${url}`;
30424
+ const cached = imageCache.current.get(fullUrl);
30425
+ if (cached?.complete && cached.naturalWidth > 0) return cached;
30426
+ if (!cached) {
30427
+ const img = new Image();
30428
+ img.crossOrigin = "anonymous";
30429
+ img.src = fullUrl;
30430
+ imageCache.current.set(fullUrl, img);
30431
+ }
30432
+ return null;
30433
+ }, [assetBaseUrl]);
30419
30434
  React98__namespace.useEffect(() => {
30420
30435
  const canvas = canvasRef.current;
30421
30436
  if (!canvas) return;
@@ -30437,6 +30452,12 @@ function GameCanvas2D({
30437
30452
  if (tickEventRef.current) {
30438
30453
  emitRef.current(tickEventRef.current, { dt, frame });
30439
30454
  }
30455
+ if (backgroundImage) {
30456
+ const bgImg = loadImage(backgroundImage);
30457
+ if (bgImg) {
30458
+ ctx.drawImage(bgImg, 0, 0, width, height);
30459
+ }
30460
+ }
30440
30461
  onDrawRef.current?.(ctx, frame);
30441
30462
  if (drawEventRef.current) {
30442
30463
  emitRef.current(drawEventRef.current, { frame });
@@ -60012,6 +60033,16 @@ function collectTraitRefsFromEffects(effects, into) {
60012
60033
  }
60013
60034
  }
60014
60035
  }
60036
+ function collectTraitRefsFromResolvedTrait(trait) {
60037
+ const out = /* @__PURE__ */ new Set();
60038
+ for (const transition of trait.transitions ?? []) {
60039
+ collectTraitRefsFromEffects(transition.effects, out);
60040
+ }
60041
+ for (const tick of trait.ticks ?? []) {
60042
+ collectTraitRefsFromEffects(tick.effects, out);
60043
+ }
60044
+ return out;
60045
+ }
60015
60046
  function collectEmbeddedTraits(schema) {
60016
60047
  const out = /* @__PURE__ */ new Set();
60017
60048
  if (!schema?.orbitals) return out;
@@ -61671,23 +61702,48 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFa
61671
61702
  return null;
61672
61703
  }
61673
61704
  function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, onNavigate, onLocalFallback, persistence }) {
61674
- const { traits: traits2, allEntities, ir } = useResolvedSchema(schema, pageName);
61705
+ const { traits: traits2, allEntities, allTraits, ir } = useResolvedSchema(schema, pageName);
61675
61706
  const allPageTraits = React98.useMemo(() => {
61676
- if (pageName && traits2.length > 0) return traits2;
61677
- if (!ir?.pages || ir.pages.size <= 1) return traits2;
61678
- const firstPage = ir.pages.values().next().value;
61679
- if (!firstPage) return traits2;
61680
- const firstPageTraits = [];
61681
- const seen = /* @__PURE__ */ new Set();
61682
- for (const binding of firstPage.traits) {
61683
- const name = binding.trait.name;
61684
- if (name && !seen.has(name)) {
61685
- seen.add(name);
61686
- firstPageTraits.push(binding);
61687
- }
61688
- }
61689
- return firstPageTraits.length > 0 ? firstPageTraits : traits2;
61690
- }, [ir, traits2, pageName]);
61707
+ let base;
61708
+ if (pageName && traits2.length > 0) {
61709
+ base = traits2;
61710
+ } else if (!ir?.pages || ir.pages.size <= 1) {
61711
+ base = traits2;
61712
+ } else {
61713
+ const firstPage = ir.pages.values().next().value;
61714
+ if (!firstPage) {
61715
+ base = traits2;
61716
+ } else {
61717
+ const firstPageTraits = [];
61718
+ const seen = /* @__PURE__ */ new Set();
61719
+ for (const binding of firstPage.traits) {
61720
+ const name = binding.trait.name;
61721
+ if (name && !seen.has(name)) {
61722
+ seen.add(name);
61723
+ firstPageTraits.push(binding);
61724
+ }
61725
+ }
61726
+ base = firstPageTraits.length > 0 ? firstPageTraits : traits2;
61727
+ }
61728
+ }
61729
+ const byName = new Set(base.map((b) => b.trait.name));
61730
+ const extra = [];
61731
+ const queue = [...base];
61732
+ while (queue.length > 0) {
61733
+ const binding = queue.shift();
61734
+ if (!binding) continue;
61735
+ for (const refName of collectTraitRefsFromResolvedTrait(binding.trait)) {
61736
+ if (byName.has(refName)) continue;
61737
+ const rt = allTraits.get(refName);
61738
+ if (!rt) continue;
61739
+ byName.add(refName);
61740
+ const sibling = { trait: rt, linkedEntity: rt.linkedEntity };
61741
+ extra.push(sibling);
61742
+ queue.push(sibling);
61743
+ }
61744
+ }
61745
+ return extra.length > 0 ? [...base, ...extra] : base;
61746
+ }, [ir, traits2, pageName, allTraits]);
61691
61747
  React98.useMemo(() => {
61692
61748
  const parsed = schema;
61693
61749
  const orbitals = parsed?.orbitals;
@@ -61876,11 +61932,13 @@ function OrbPreview({
61876
61932
  return match?.page.name;
61877
61933
  }, [pages, initialPagePath]);
61878
61934
  const [currentPage, setCurrentPage] = React98.useState(initialPageName);
61935
+ const prevInitialPagePathRef = React98.useRef(initialPagePath);
61879
61936
  React98.useEffect(() => {
61880
- if (initialPageName && initialPageName !== currentPage) {
61937
+ if (prevInitialPagePathRef.current !== initialPagePath) {
61938
+ prevInitialPagePathRef.current = initialPagePath;
61881
61939
  setCurrentPage(initialPageName);
61882
61940
  }
61883
- }, [initialPageName, currentPage]);
61941
+ }, [initialPagePath, initialPageName]);
61884
61942
  const handleNavigate = React98.useCallback((path) => {
61885
61943
  const match = pages.find(({ page }) => page.path === path);
61886
61944
  navLog.debug("handleNavigate", () => ({
package/dist/avl/index.js CHANGED
@@ -14570,7 +14570,7 @@ var init_MapView = __esm({
14570
14570
  shadowSize: [41, 41]
14571
14571
  });
14572
14572
  L.Marker.prototype.options.icon = defaultIcon;
14573
- const { useEffect: useEffect89, useRef: useRef88, useCallback: useCallback129, useState: useState124 } = React98__default;
14573
+ const { useEffect: useEffect89, useRef: useRef88, useCallback: useCallback130, useState: useState124 } = React98__default;
14574
14574
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
14575
14575
  const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
14576
14576
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -14616,7 +14616,7 @@ var init_MapView = __esm({
14616
14616
  }) {
14617
14617
  const eventBus = useEventBus3();
14618
14618
  const [clickedPosition, setClickedPosition] = useState124(null);
14619
- const handleMapClick = useCallback129((lat, lng) => {
14619
+ const handleMapClick = useCallback130((lat, lng) => {
14620
14620
  if (showClickedPin) {
14621
14621
  setClickedPosition({ lat, lng });
14622
14622
  }
@@ -14625,7 +14625,7 @@ var init_MapView = __esm({
14625
14625
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
14626
14626
  }
14627
14627
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
14628
- const handleMarkerClick = useCallback129((marker) => {
14628
+ const handleMarkerClick = useCallback130((marker) => {
14629
14629
  onMarkerClick?.(marker);
14630
14630
  if (markerClickEvent) {
14631
14631
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -30350,12 +30350,15 @@ function GameCanvas2D({
30350
30350
  tickEvent,
30351
30351
  drawEvent,
30352
30352
  fps = 60,
30353
+ backgroundImage,
30354
+ assetBaseUrl = "",
30353
30355
  className
30354
30356
  }) {
30355
30357
  const canvasRef = React98.useRef(null);
30356
30358
  const rafRef = React98.useRef(0);
30357
30359
  const frameRef = React98.useRef(0);
30358
30360
  const lastTimeRef = React98.useRef(0);
30361
+ const imageCache = React98.useRef(/* @__PURE__ */ new Map());
30359
30362
  const emit = useEmitEvent();
30360
30363
  const onDrawRef = React98.useRef(onDraw);
30361
30364
  onDrawRef.current = onDraw;
@@ -30367,6 +30370,18 @@ function GameCanvas2D({
30367
30370
  drawEventRef.current = drawEvent;
30368
30371
  const emitRef = React98.useRef(emit);
30369
30372
  emitRef.current = emit;
30373
+ const loadImage = React98.useCallback((url) => {
30374
+ const fullUrl = url.startsWith("http") ? url : `${assetBaseUrl}${url}`;
30375
+ const cached = imageCache.current.get(fullUrl);
30376
+ if (cached?.complete && cached.naturalWidth > 0) return cached;
30377
+ if (!cached) {
30378
+ const img = new Image();
30379
+ img.crossOrigin = "anonymous";
30380
+ img.src = fullUrl;
30381
+ imageCache.current.set(fullUrl, img);
30382
+ }
30383
+ return null;
30384
+ }, [assetBaseUrl]);
30370
30385
  React98.useEffect(() => {
30371
30386
  const canvas = canvasRef.current;
30372
30387
  if (!canvas) return;
@@ -30388,6 +30403,12 @@ function GameCanvas2D({
30388
30403
  if (tickEventRef.current) {
30389
30404
  emitRef.current(tickEventRef.current, { dt, frame });
30390
30405
  }
30406
+ if (backgroundImage) {
30407
+ const bgImg = loadImage(backgroundImage);
30408
+ if (bgImg) {
30409
+ ctx.drawImage(bgImg, 0, 0, width, height);
30410
+ }
30411
+ }
30391
30412
  onDrawRef.current?.(ctx, frame);
30392
30413
  if (drawEventRef.current) {
30393
30414
  emitRef.current(drawEventRef.current, { frame });
@@ -59963,6 +59984,16 @@ function collectTraitRefsFromEffects(effects, into) {
59963
59984
  }
59964
59985
  }
59965
59986
  }
59987
+ function collectTraitRefsFromResolvedTrait(trait) {
59988
+ const out = /* @__PURE__ */ new Set();
59989
+ for (const transition of trait.transitions ?? []) {
59990
+ collectTraitRefsFromEffects(transition.effects, out);
59991
+ }
59992
+ for (const tick of trait.ticks ?? []) {
59993
+ collectTraitRefsFromEffects(tick.effects, out);
59994
+ }
59995
+ return out;
59996
+ }
59966
59997
  function collectEmbeddedTraits(schema) {
59967
59998
  const out = /* @__PURE__ */ new Set();
59968
59999
  if (!schema?.orbitals) return out;
@@ -61622,23 +61653,48 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFa
61622
61653
  return null;
61623
61654
  }
61624
61655
  function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, onNavigate, onLocalFallback, persistence }) {
61625
- const { traits: traits2, allEntities, ir } = useResolvedSchema(schema, pageName);
61656
+ const { traits: traits2, allEntities, allTraits, ir } = useResolvedSchema(schema, pageName);
61626
61657
  const allPageTraits = useMemo(() => {
61627
- if (pageName && traits2.length > 0) return traits2;
61628
- if (!ir?.pages || ir.pages.size <= 1) return traits2;
61629
- const firstPage = ir.pages.values().next().value;
61630
- if (!firstPage) return traits2;
61631
- const firstPageTraits = [];
61632
- const seen = /* @__PURE__ */ new Set();
61633
- for (const binding of firstPage.traits) {
61634
- const name = binding.trait.name;
61635
- if (name && !seen.has(name)) {
61636
- seen.add(name);
61637
- firstPageTraits.push(binding);
61638
- }
61639
- }
61640
- return firstPageTraits.length > 0 ? firstPageTraits : traits2;
61641
- }, [ir, traits2, pageName]);
61658
+ let base;
61659
+ if (pageName && traits2.length > 0) {
61660
+ base = traits2;
61661
+ } else if (!ir?.pages || ir.pages.size <= 1) {
61662
+ base = traits2;
61663
+ } else {
61664
+ const firstPage = ir.pages.values().next().value;
61665
+ if (!firstPage) {
61666
+ base = traits2;
61667
+ } else {
61668
+ const firstPageTraits = [];
61669
+ const seen = /* @__PURE__ */ new Set();
61670
+ for (const binding of firstPage.traits) {
61671
+ const name = binding.trait.name;
61672
+ if (name && !seen.has(name)) {
61673
+ seen.add(name);
61674
+ firstPageTraits.push(binding);
61675
+ }
61676
+ }
61677
+ base = firstPageTraits.length > 0 ? firstPageTraits : traits2;
61678
+ }
61679
+ }
61680
+ const byName = new Set(base.map((b) => b.trait.name));
61681
+ const extra = [];
61682
+ const queue = [...base];
61683
+ while (queue.length > 0) {
61684
+ const binding = queue.shift();
61685
+ if (!binding) continue;
61686
+ for (const refName of collectTraitRefsFromResolvedTrait(binding.trait)) {
61687
+ if (byName.has(refName)) continue;
61688
+ const rt = allTraits.get(refName);
61689
+ if (!rt) continue;
61690
+ byName.add(refName);
61691
+ const sibling = { trait: rt, linkedEntity: rt.linkedEntity };
61692
+ extra.push(sibling);
61693
+ queue.push(sibling);
61694
+ }
61695
+ }
61696
+ return extra.length > 0 ? [...base, ...extra] : base;
61697
+ }, [ir, traits2, pageName, allTraits]);
61642
61698
  useMemo(() => {
61643
61699
  const parsed = schema;
61644
61700
  const orbitals = parsed?.orbitals;
@@ -61827,11 +61883,13 @@ function OrbPreview({
61827
61883
  return match?.page.name;
61828
61884
  }, [pages, initialPagePath]);
61829
61885
  const [currentPage, setCurrentPage] = useState(initialPageName);
61886
+ const prevInitialPagePathRef = useRef(initialPagePath);
61830
61887
  useEffect(() => {
61831
- if (initialPageName && initialPageName !== currentPage) {
61888
+ if (prevInitialPagePathRef.current !== initialPagePath) {
61889
+ prevInitialPagePathRef.current = initialPagePath;
61832
61890
  setCurrentPage(initialPageName);
61833
61891
  }
61834
- }, [initialPageName, currentPage]);
61892
+ }, [initialPagePath, initialPageName]);
61835
61893
  const handleNavigate = useCallback((path) => {
61836
61894
  const match = pages.find(({ page }) => page.path === path);
61837
61895
  navLog.debug("handleNavigate", () => ({
@@ -8676,7 +8676,7 @@ var init_MapView = __esm({
8676
8676
  shadowSize: [41, 41]
8677
8677
  });
8678
8678
  L.Marker.prototype.options.icon = defaultIcon;
8679
- const { useEffect: useEffect72, useRef: useRef66, useCallback: useCallback127, useState: useState110 } = React80__namespace.default;
8679
+ const { useEffect: useEffect72, useRef: useRef66, useCallback: useCallback128, useState: useState110 } = React80__namespace.default;
8680
8680
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
8681
8681
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
8682
8682
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -8722,7 +8722,7 @@ var init_MapView = __esm({
8722
8722
  }) {
8723
8723
  const eventBus = useEventBus2();
8724
8724
  const [clickedPosition, setClickedPosition] = useState110(null);
8725
- const handleMapClick = useCallback127((lat, lng) => {
8725
+ const handleMapClick = useCallback128((lat, lng) => {
8726
8726
  if (showClickedPin) {
8727
8727
  setClickedPosition({ lat, lng });
8728
8728
  }
@@ -8731,7 +8731,7 @@ var init_MapView = __esm({
8731
8731
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
8732
8732
  }
8733
8733
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
8734
- const handleMarkerClick = useCallback127((marker) => {
8734
+ const handleMarkerClick = useCallback128((marker) => {
8735
8735
  onMarkerClick?.(marker);
8736
8736
  if (markerClickEvent) {
8737
8737
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -25784,12 +25784,15 @@ function GameCanvas2D({
25784
25784
  tickEvent,
25785
25785
  drawEvent,
25786
25786
  fps = 60,
25787
+ backgroundImage,
25788
+ assetBaseUrl = "",
25787
25789
  className
25788
25790
  }) {
25789
25791
  const canvasRef = React80__namespace.useRef(null);
25790
25792
  const rafRef = React80__namespace.useRef(0);
25791
25793
  const frameRef = React80__namespace.useRef(0);
25792
25794
  const lastTimeRef = React80__namespace.useRef(0);
25795
+ const imageCache = React80__namespace.useRef(/* @__PURE__ */ new Map());
25793
25796
  const emit = useEmitEvent();
25794
25797
  const onDrawRef = React80__namespace.useRef(onDraw);
25795
25798
  onDrawRef.current = onDraw;
@@ -25801,6 +25804,18 @@ function GameCanvas2D({
25801
25804
  drawEventRef.current = drawEvent;
25802
25805
  const emitRef = React80__namespace.useRef(emit);
25803
25806
  emitRef.current = emit;
25807
+ const loadImage = React80__namespace.useCallback((url) => {
25808
+ const fullUrl = url.startsWith("http") ? url : `${assetBaseUrl}${url}`;
25809
+ const cached = imageCache.current.get(fullUrl);
25810
+ if (cached?.complete && cached.naturalWidth > 0) return cached;
25811
+ if (!cached) {
25812
+ const img = new Image();
25813
+ img.crossOrigin = "anonymous";
25814
+ img.src = fullUrl;
25815
+ imageCache.current.set(fullUrl, img);
25816
+ }
25817
+ return null;
25818
+ }, [assetBaseUrl]);
25804
25819
  React80__namespace.useEffect(() => {
25805
25820
  const canvas = canvasRef.current;
25806
25821
  if (!canvas) return;
@@ -25822,6 +25837,12 @@ function GameCanvas2D({
25822
25837
  if (tickEventRef.current) {
25823
25838
  emitRef.current(tickEventRef.current, { dt, frame });
25824
25839
  }
25840
+ if (backgroundImage) {
25841
+ const bgImg = loadImage(backgroundImage);
25842
+ if (bgImg) {
25843
+ ctx.drawImage(bgImg, 0, 0, width, height);
25844
+ }
25845
+ }
25825
25846
  onDrawRef.current?.(ctx, frame);
25826
25847
  if (drawEventRef.current) {
25827
25848
  emitRef.current(drawEventRef.current, { frame });
@@ -8627,7 +8627,7 @@ var init_MapView = __esm({
8627
8627
  shadowSize: [41, 41]
8628
8628
  });
8629
8629
  L.Marker.prototype.options.icon = defaultIcon;
8630
- const { useEffect: useEffect72, useRef: useRef66, useCallback: useCallback127, useState: useState110 } = React80__default;
8630
+ const { useEffect: useEffect72, useRef: useRef66, useCallback: useCallback128, useState: useState110 } = React80__default;
8631
8631
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
8632
8632
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
8633
8633
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -8673,7 +8673,7 @@ var init_MapView = __esm({
8673
8673
  }) {
8674
8674
  const eventBus = useEventBus2();
8675
8675
  const [clickedPosition, setClickedPosition] = useState110(null);
8676
- const handleMapClick = useCallback127((lat, lng) => {
8676
+ const handleMapClick = useCallback128((lat, lng) => {
8677
8677
  if (showClickedPin) {
8678
8678
  setClickedPosition({ lat, lng });
8679
8679
  }
@@ -8682,7 +8682,7 @@ var init_MapView = __esm({
8682
8682
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
8683
8683
  }
8684
8684
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
8685
- const handleMarkerClick = useCallback127((marker) => {
8685
+ const handleMarkerClick = useCallback128((marker) => {
8686
8686
  onMarkerClick?.(marker);
8687
8687
  if (markerClickEvent) {
8688
8688
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -25735,12 +25735,15 @@ function GameCanvas2D({
25735
25735
  tickEvent,
25736
25736
  drawEvent,
25737
25737
  fps = 60,
25738
+ backgroundImage,
25739
+ assetBaseUrl = "",
25738
25740
  className
25739
25741
  }) {
25740
25742
  const canvasRef = React80.useRef(null);
25741
25743
  const rafRef = React80.useRef(0);
25742
25744
  const frameRef = React80.useRef(0);
25743
25745
  const lastTimeRef = React80.useRef(0);
25746
+ const imageCache = React80.useRef(/* @__PURE__ */ new Map());
25744
25747
  const emit = useEmitEvent();
25745
25748
  const onDrawRef = React80.useRef(onDraw);
25746
25749
  onDrawRef.current = onDraw;
@@ -25752,6 +25755,18 @@ function GameCanvas2D({
25752
25755
  drawEventRef.current = drawEvent;
25753
25756
  const emitRef = React80.useRef(emit);
25754
25757
  emitRef.current = emit;
25758
+ const loadImage = React80.useCallback((url) => {
25759
+ const fullUrl = url.startsWith("http") ? url : `${assetBaseUrl}${url}`;
25760
+ const cached = imageCache.current.get(fullUrl);
25761
+ if (cached?.complete && cached.naturalWidth > 0) return cached;
25762
+ if (!cached) {
25763
+ const img = new Image();
25764
+ img.crossOrigin = "anonymous";
25765
+ img.src = fullUrl;
25766
+ imageCache.current.set(fullUrl, img);
25767
+ }
25768
+ return null;
25769
+ }, [assetBaseUrl]);
25755
25770
  React80.useEffect(() => {
25756
25771
  const canvas = canvasRef.current;
25757
25772
  if (!canvas) return;
@@ -25773,6 +25788,12 @@ function GameCanvas2D({
25773
25788
  if (tickEventRef.current) {
25774
25789
  emitRef.current(tickEventRef.current, { dt, frame });
25775
25790
  }
25791
+ if (backgroundImage) {
25792
+ const bgImg = loadImage(backgroundImage);
25793
+ if (bgImg) {
25794
+ ctx.drawImage(bgImg, 0, 0, width, height);
25795
+ }
25796
+ }
25776
25797
  onDrawRef.current?.(ctx, frame);
25777
25798
  if (drawEventRef.current) {
25778
25799
  emitRef.current(drawEventRef.current, { frame });
@@ -13,10 +13,14 @@ export interface GameCanvas2DProps {
13
13
  drawEvent?: string;
14
14
  /** Target frames per second */
15
15
  fps?: number;
16
+ /** Background image URL */
17
+ backgroundImage?: string;
18
+ /** Base URL prefix for asset URLs */
19
+ assetBaseUrl?: string;
16
20
  /** Additional CSS classes */
17
21
  className?: string;
18
22
  }
19
- export declare function GameCanvas2D({ width, height, onDraw, onTick, tickEvent, drawEvent, fps, className, }: GameCanvas2DProps): import("react/jsx-runtime").JSX.Element;
23
+ export declare function GameCanvas2D({ width, height, onDraw, onTick, tickEvent, drawEvent, fps, backgroundImage, assetBaseUrl, className, }: GameCanvas2DProps): import("react/jsx-runtime").JSX.Element;
20
24
  export declare namespace GameCanvas2D {
21
25
  var displayName: string;
22
26
  }
@@ -10244,7 +10244,7 @@ var init_MapView = __esm({
10244
10244
  shadowSize: [41, 41]
10245
10245
  });
10246
10246
  L.Marker.prototype.options.icon = defaultIcon;
10247
- const { useEffect: useEffect69, useRef: useRef65, useCallback: useCallback113, useState: useState99 } = React86__namespace.default;
10247
+ const { useEffect: useEffect69, useRef: useRef65, useCallback: useCallback114, useState: useState99 } = React86__namespace.default;
10248
10248
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
10249
10249
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
10250
10250
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -10290,7 +10290,7 @@ var init_MapView = __esm({
10290
10290
  }) {
10291
10291
  const eventBus = useEventBus2();
10292
10292
  const [clickedPosition, setClickedPosition] = useState99(null);
10293
- const handleMapClick = useCallback113((lat, lng) => {
10293
+ const handleMapClick = useCallback114((lat, lng) => {
10294
10294
  if (showClickedPin) {
10295
10295
  setClickedPosition({ lat, lng });
10296
10296
  }
@@ -10299,7 +10299,7 @@ var init_MapView = __esm({
10299
10299
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
10300
10300
  }
10301
10301
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
10302
- const handleMarkerClick = useCallback113((marker) => {
10302
+ const handleMarkerClick = useCallback114((marker) => {
10303
10303
  onMarkerClick?.(marker);
10304
10304
  if (markerClickEvent) {
10305
10305
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -26811,12 +26811,15 @@ function GameCanvas2D({
26811
26811
  tickEvent,
26812
26812
  drawEvent,
26813
26813
  fps = 60,
26814
+ backgroundImage,
26815
+ assetBaseUrl = "",
26814
26816
  className
26815
26817
  }) {
26816
26818
  const canvasRef = React86__namespace.useRef(null);
26817
26819
  const rafRef = React86__namespace.useRef(0);
26818
26820
  const frameRef = React86__namespace.useRef(0);
26819
26821
  const lastTimeRef = React86__namespace.useRef(0);
26822
+ const imageCache = React86__namespace.useRef(/* @__PURE__ */ new Map());
26820
26823
  const emit = useEmitEvent();
26821
26824
  const onDrawRef = React86__namespace.useRef(onDraw);
26822
26825
  onDrawRef.current = onDraw;
@@ -26828,6 +26831,18 @@ function GameCanvas2D({
26828
26831
  drawEventRef.current = drawEvent;
26829
26832
  const emitRef = React86__namespace.useRef(emit);
26830
26833
  emitRef.current = emit;
26834
+ const loadImage = React86__namespace.useCallback((url) => {
26835
+ const fullUrl = url.startsWith("http") ? url : `${assetBaseUrl}${url}`;
26836
+ const cached = imageCache.current.get(fullUrl);
26837
+ if (cached?.complete && cached.naturalWidth > 0) return cached;
26838
+ if (!cached) {
26839
+ const img = new Image();
26840
+ img.crossOrigin = "anonymous";
26841
+ img.src = fullUrl;
26842
+ imageCache.current.set(fullUrl, img);
26843
+ }
26844
+ return null;
26845
+ }, [assetBaseUrl]);
26831
26846
  React86__namespace.useEffect(() => {
26832
26847
  const canvas = canvasRef.current;
26833
26848
  if (!canvas) return;
@@ -26849,6 +26864,12 @@ function GameCanvas2D({
26849
26864
  if (tickEventRef.current) {
26850
26865
  emitRef.current(tickEventRef.current, { dt, frame });
26851
26866
  }
26867
+ if (backgroundImage) {
26868
+ const bgImg = loadImage(backgroundImage);
26869
+ if (bgImg) {
26870
+ ctx.drawImage(bgImg, 0, 0, width, height);
26871
+ }
26872
+ }
26852
26873
  onDrawRef.current?.(ctx, frame);
26853
26874
  if (drawEventRef.current) {
26854
26875
  emitRef.current(drawEventRef.current, { frame });
@@ -10195,7 +10195,7 @@ var init_MapView = __esm({
10195
10195
  shadowSize: [41, 41]
10196
10196
  });
10197
10197
  L.Marker.prototype.options.icon = defaultIcon;
10198
- const { useEffect: useEffect69, useRef: useRef65, useCallback: useCallback113, useState: useState99 } = React86__default;
10198
+ const { useEffect: useEffect69, useRef: useRef65, useCallback: useCallback114, useState: useState99 } = React86__default;
10199
10199
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
10200
10200
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
10201
10201
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -10241,7 +10241,7 @@ var init_MapView = __esm({
10241
10241
  }) {
10242
10242
  const eventBus = useEventBus2();
10243
10243
  const [clickedPosition, setClickedPosition] = useState99(null);
10244
- const handleMapClick = useCallback113((lat, lng) => {
10244
+ const handleMapClick = useCallback114((lat, lng) => {
10245
10245
  if (showClickedPin) {
10246
10246
  setClickedPosition({ lat, lng });
10247
10247
  }
@@ -10250,7 +10250,7 @@ var init_MapView = __esm({
10250
10250
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
10251
10251
  }
10252
10252
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
10253
- const handleMarkerClick = useCallback113((marker) => {
10253
+ const handleMarkerClick = useCallback114((marker) => {
10254
10254
  onMarkerClick?.(marker);
10255
10255
  if (markerClickEvent) {
10256
10256
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -26762,12 +26762,15 @@ function GameCanvas2D({
26762
26762
  tickEvent,
26763
26763
  drawEvent,
26764
26764
  fps = 60,
26765
+ backgroundImage,
26766
+ assetBaseUrl = "",
26765
26767
  className
26766
26768
  }) {
26767
26769
  const canvasRef = React86.useRef(null);
26768
26770
  const rafRef = React86.useRef(0);
26769
26771
  const frameRef = React86.useRef(0);
26770
26772
  const lastTimeRef = React86.useRef(0);
26773
+ const imageCache = React86.useRef(/* @__PURE__ */ new Map());
26771
26774
  const emit = useEmitEvent();
26772
26775
  const onDrawRef = React86.useRef(onDraw);
26773
26776
  onDrawRef.current = onDraw;
@@ -26779,6 +26782,18 @@ function GameCanvas2D({
26779
26782
  drawEventRef.current = drawEvent;
26780
26783
  const emitRef = React86.useRef(emit);
26781
26784
  emitRef.current = emit;
26785
+ const loadImage = React86.useCallback((url) => {
26786
+ const fullUrl = url.startsWith("http") ? url : `${assetBaseUrl}${url}`;
26787
+ const cached = imageCache.current.get(fullUrl);
26788
+ if (cached?.complete && cached.naturalWidth > 0) return cached;
26789
+ if (!cached) {
26790
+ const img = new Image();
26791
+ img.crossOrigin = "anonymous";
26792
+ img.src = fullUrl;
26793
+ imageCache.current.set(fullUrl, img);
26794
+ }
26795
+ return null;
26796
+ }, [assetBaseUrl]);
26782
26797
  React86.useEffect(() => {
26783
26798
  const canvas = canvasRef.current;
26784
26799
  if (!canvas) return;
@@ -26800,6 +26815,12 @@ function GameCanvas2D({
26800
26815
  if (tickEventRef.current) {
26801
26816
  emitRef.current(tickEventRef.current, { dt, frame });
26802
26817
  }
26818
+ if (backgroundImage) {
26819
+ const bgImg = loadImage(backgroundImage);
26820
+ if (bgImg) {
26821
+ ctx.drawImage(bgImg, 0, 0, width, height);
26822
+ }
26823
+ }
26803
26824
  onDrawRef.current?.(ctx, frame);
26804
26825
  if (drawEventRef.current) {
26805
26826
  emitRef.current(drawEventRef.current, { frame });
@@ -21,7 +21,14 @@
21
21
  *
22
22
  * @packageDocumentation
23
23
  */
24
- import type { OrbitalSchema } from '@almadar/core';
24
+ import type { OrbitalSchema, ResolvedTrait } from '@almadar/core';
25
+ /**
26
+ * Collect the `@trait.X` names referenced by a single resolved trait's
27
+ * render-ui effects (transitions + initial + ticks). Used to pull embed-routed
28
+ * sibling traits into a page's state-machine binding set so their own state
29
+ * machines register + subscribe (otherwise their fetch-success is never heard).
30
+ */
31
+ export declare function collectTraitRefsFromResolvedTrait(trait: ResolvedTrait): Set<string>;
25
32
  /**
26
33
  * Build the flat set of trait names that are referenced via `@trait.X`
27
34
  * by at least one trait's render-ui in the resolved schema.
@@ -10132,7 +10132,7 @@ var init_MapView = __esm({
10132
10132
  shadowSize: [41, 41]
10133
10133
  });
10134
10134
  L.Marker.prototype.options.icon = defaultIcon;
10135
- const { useEffect: useEffect70, useRef: useRef65, useCallback: useCallback113, useState: useState102 } = React85__namespace.default;
10135
+ const { useEffect: useEffect70, useRef: useRef65, useCallback: useCallback114, useState: useState102 } = React85__namespace.default;
10136
10136
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
10137
10137
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
10138
10138
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -10178,7 +10178,7 @@ var init_MapView = __esm({
10178
10178
  }) {
10179
10179
  const eventBus = useEventBus2();
10180
10180
  const [clickedPosition, setClickedPosition] = useState102(null);
10181
- const handleMapClick = useCallback113((lat, lng) => {
10181
+ const handleMapClick = useCallback114((lat, lng) => {
10182
10182
  if (showClickedPin) {
10183
10183
  setClickedPosition({ lat, lng });
10184
10184
  }
@@ -10187,7 +10187,7 @@ var init_MapView = __esm({
10187
10187
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
10188
10188
  }
10189
10189
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
10190
- const handleMarkerClick = useCallback113((marker) => {
10190
+ const handleMarkerClick = useCallback114((marker) => {
10191
10191
  onMarkerClick?.(marker);
10192
10192
  if (markerClickEvent) {
10193
10193
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -26516,12 +26516,15 @@ function GameCanvas2D({
26516
26516
  tickEvent,
26517
26517
  drawEvent,
26518
26518
  fps = 60,
26519
+ backgroundImage,
26520
+ assetBaseUrl = "",
26519
26521
  className
26520
26522
  }) {
26521
26523
  const canvasRef = React85__namespace.useRef(null);
26522
26524
  const rafRef = React85__namespace.useRef(0);
26523
26525
  const frameRef = React85__namespace.useRef(0);
26524
26526
  const lastTimeRef = React85__namespace.useRef(0);
26527
+ const imageCache = React85__namespace.useRef(/* @__PURE__ */ new Map());
26525
26528
  const emit = useEmitEvent();
26526
26529
  const onDrawRef = React85__namespace.useRef(onDraw);
26527
26530
  onDrawRef.current = onDraw;
@@ -26533,6 +26536,18 @@ function GameCanvas2D({
26533
26536
  drawEventRef.current = drawEvent;
26534
26537
  const emitRef = React85__namespace.useRef(emit);
26535
26538
  emitRef.current = emit;
26539
+ const loadImage = React85__namespace.useCallback((url) => {
26540
+ const fullUrl = url.startsWith("http") ? url : `${assetBaseUrl}${url}`;
26541
+ const cached = imageCache.current.get(fullUrl);
26542
+ if (cached?.complete && cached.naturalWidth > 0) return cached;
26543
+ if (!cached) {
26544
+ const img = new Image();
26545
+ img.crossOrigin = "anonymous";
26546
+ img.src = fullUrl;
26547
+ imageCache.current.set(fullUrl, img);
26548
+ }
26549
+ return null;
26550
+ }, [assetBaseUrl]);
26536
26551
  React85__namespace.useEffect(() => {
26537
26552
  const canvas = canvasRef.current;
26538
26553
  if (!canvas) return;
@@ -26554,6 +26569,12 @@ function GameCanvas2D({
26554
26569
  if (tickEventRef.current) {
26555
26570
  emitRef.current(tickEventRef.current, { dt, frame });
26556
26571
  }
26572
+ if (backgroundImage) {
26573
+ const bgImg = loadImage(backgroundImage);
26574
+ if (bgImg) {
26575
+ ctx.drawImage(bgImg, 0, 0, width, height);
26576
+ }
26577
+ }
26557
26578
  onDrawRef.current?.(ctx, frame);
26558
26579
  if (drawEventRef.current) {
26559
26580
  emitRef.current(drawEventRef.current, { frame });
@@ -48512,6 +48533,16 @@ function collectTraitRefsFromEffects(effects, into) {
48512
48533
  }
48513
48534
  }
48514
48535
  }
48536
+ function collectTraitRefsFromResolvedTrait(trait) {
48537
+ const out = /* @__PURE__ */ new Set();
48538
+ for (const transition of trait.transitions ?? []) {
48539
+ collectTraitRefsFromEffects(transition.effects, out);
48540
+ }
48541
+ for (const tick of trait.ticks ?? []) {
48542
+ collectTraitRefsFromEffects(tick.effects, out);
48543
+ }
48544
+ return out;
48545
+ }
48515
48546
  function collectEmbeddedTraits(schema) {
48516
48547
  const out = /* @__PURE__ */ new Set();
48517
48548
  if (!schema?.orbitals) return out;
@@ -49350,23 +49381,48 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFa
49350
49381
  return null;
49351
49382
  }
49352
49383
  function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, onNavigate, onLocalFallback, persistence }) {
49353
- const { traits: traits2, allEntities, ir } = useResolvedSchema(schema, pageName);
49384
+ const { traits: traits2, allEntities, allTraits, ir } = useResolvedSchema(schema, pageName);
49354
49385
  const allPageTraits = React85.useMemo(() => {
49355
- if (pageName && traits2.length > 0) return traits2;
49356
- if (!ir?.pages || ir.pages.size <= 1) return traits2;
49357
- const firstPage = ir.pages.values().next().value;
49358
- if (!firstPage) return traits2;
49359
- const firstPageTraits = [];
49360
- const seen = /* @__PURE__ */ new Set();
49361
- for (const binding of firstPage.traits) {
49362
- const name = binding.trait.name;
49363
- if (name && !seen.has(name)) {
49364
- seen.add(name);
49365
- firstPageTraits.push(binding);
49366
- }
49367
- }
49368
- return firstPageTraits.length > 0 ? firstPageTraits : traits2;
49369
- }, [ir, traits2, pageName]);
49386
+ let base;
49387
+ if (pageName && traits2.length > 0) {
49388
+ base = traits2;
49389
+ } else if (!ir?.pages || ir.pages.size <= 1) {
49390
+ base = traits2;
49391
+ } else {
49392
+ const firstPage = ir.pages.values().next().value;
49393
+ if (!firstPage) {
49394
+ base = traits2;
49395
+ } else {
49396
+ const firstPageTraits = [];
49397
+ const seen = /* @__PURE__ */ new Set();
49398
+ for (const binding of firstPage.traits) {
49399
+ const name = binding.trait.name;
49400
+ if (name && !seen.has(name)) {
49401
+ seen.add(name);
49402
+ firstPageTraits.push(binding);
49403
+ }
49404
+ }
49405
+ base = firstPageTraits.length > 0 ? firstPageTraits : traits2;
49406
+ }
49407
+ }
49408
+ const byName = new Set(base.map((b) => b.trait.name));
49409
+ const extra = [];
49410
+ const queue = [...base];
49411
+ while (queue.length > 0) {
49412
+ const binding = queue.shift();
49413
+ if (!binding) continue;
49414
+ for (const refName of collectTraitRefsFromResolvedTrait(binding.trait)) {
49415
+ if (byName.has(refName)) continue;
49416
+ const rt = allTraits.get(refName);
49417
+ if (!rt) continue;
49418
+ byName.add(refName);
49419
+ const sibling = { trait: rt, linkedEntity: rt.linkedEntity };
49420
+ extra.push(sibling);
49421
+ queue.push(sibling);
49422
+ }
49423
+ }
49424
+ return extra.length > 0 ? [...base, ...extra] : base;
49425
+ }, [ir, traits2, pageName, allTraits]);
49370
49426
  React85.useMemo(() => {
49371
49427
  const parsed = schema;
49372
49428
  const orbitals = parsed?.orbitals;
@@ -49555,11 +49611,13 @@ function OrbPreview({
49555
49611
  return match?.page.name;
49556
49612
  }, [pages, initialPagePath]);
49557
49613
  const [currentPage, setCurrentPage] = React85.useState(initialPageName);
49614
+ const prevInitialPagePathRef = React85.useRef(initialPagePath);
49558
49615
  React85.useEffect(() => {
49559
- if (initialPageName && initialPageName !== currentPage) {
49616
+ if (prevInitialPagePathRef.current !== initialPagePath) {
49617
+ prevInitialPagePathRef.current = initialPagePath;
49560
49618
  setCurrentPage(initialPageName);
49561
49619
  }
49562
- }, [initialPageName, currentPage]);
49620
+ }, [initialPagePath, initialPageName]);
49563
49621
  const handleNavigate = React85.useCallback((path) => {
49564
49622
  const match = pages.find(({ page }) => page.path === path);
49565
49623
  navLog.debug("handleNavigate", () => ({
@@ -10083,7 +10083,7 @@ var init_MapView = __esm({
10083
10083
  shadowSize: [41, 41]
10084
10084
  });
10085
10085
  L.Marker.prototype.options.icon = defaultIcon;
10086
- const { useEffect: useEffect70, useRef: useRef65, useCallback: useCallback113, useState: useState102 } = React85__default;
10086
+ const { useEffect: useEffect70, useRef: useRef65, useCallback: useCallback114, useState: useState102 } = React85__default;
10087
10087
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
10088
10088
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
10089
10089
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -10129,7 +10129,7 @@ var init_MapView = __esm({
10129
10129
  }) {
10130
10130
  const eventBus = useEventBus2();
10131
10131
  const [clickedPosition, setClickedPosition] = useState102(null);
10132
- const handleMapClick = useCallback113((lat, lng) => {
10132
+ const handleMapClick = useCallback114((lat, lng) => {
10133
10133
  if (showClickedPin) {
10134
10134
  setClickedPosition({ lat, lng });
10135
10135
  }
@@ -10138,7 +10138,7 @@ var init_MapView = __esm({
10138
10138
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
10139
10139
  }
10140
10140
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
10141
- const handleMarkerClick = useCallback113((marker) => {
10141
+ const handleMarkerClick = useCallback114((marker) => {
10142
10142
  onMarkerClick?.(marker);
10143
10143
  if (markerClickEvent) {
10144
10144
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -26467,12 +26467,15 @@ function GameCanvas2D({
26467
26467
  tickEvent,
26468
26468
  drawEvent,
26469
26469
  fps = 60,
26470
+ backgroundImage,
26471
+ assetBaseUrl = "",
26470
26472
  className
26471
26473
  }) {
26472
26474
  const canvasRef = React85.useRef(null);
26473
26475
  const rafRef = React85.useRef(0);
26474
26476
  const frameRef = React85.useRef(0);
26475
26477
  const lastTimeRef = React85.useRef(0);
26478
+ const imageCache = React85.useRef(/* @__PURE__ */ new Map());
26476
26479
  const emit = useEmitEvent();
26477
26480
  const onDrawRef = React85.useRef(onDraw);
26478
26481
  onDrawRef.current = onDraw;
@@ -26484,6 +26487,18 @@ function GameCanvas2D({
26484
26487
  drawEventRef.current = drawEvent;
26485
26488
  const emitRef = React85.useRef(emit);
26486
26489
  emitRef.current = emit;
26490
+ const loadImage = React85.useCallback((url) => {
26491
+ const fullUrl = url.startsWith("http") ? url : `${assetBaseUrl}${url}`;
26492
+ const cached = imageCache.current.get(fullUrl);
26493
+ if (cached?.complete && cached.naturalWidth > 0) return cached;
26494
+ if (!cached) {
26495
+ const img = new Image();
26496
+ img.crossOrigin = "anonymous";
26497
+ img.src = fullUrl;
26498
+ imageCache.current.set(fullUrl, img);
26499
+ }
26500
+ return null;
26501
+ }, [assetBaseUrl]);
26487
26502
  React85.useEffect(() => {
26488
26503
  const canvas = canvasRef.current;
26489
26504
  if (!canvas) return;
@@ -26505,6 +26520,12 @@ function GameCanvas2D({
26505
26520
  if (tickEventRef.current) {
26506
26521
  emitRef.current(tickEventRef.current, { dt, frame });
26507
26522
  }
26523
+ if (backgroundImage) {
26524
+ const bgImg = loadImage(backgroundImage);
26525
+ if (bgImg) {
26526
+ ctx.drawImage(bgImg, 0, 0, width, height);
26527
+ }
26528
+ }
26508
26529
  onDrawRef.current?.(ctx, frame);
26509
26530
  if (drawEventRef.current) {
26510
26531
  emitRef.current(drawEventRef.current, { frame });
@@ -48463,6 +48484,16 @@ function collectTraitRefsFromEffects(effects, into) {
48463
48484
  }
48464
48485
  }
48465
48486
  }
48487
+ function collectTraitRefsFromResolvedTrait(trait) {
48488
+ const out = /* @__PURE__ */ new Set();
48489
+ for (const transition of trait.transitions ?? []) {
48490
+ collectTraitRefsFromEffects(transition.effects, out);
48491
+ }
48492
+ for (const tick of trait.ticks ?? []) {
48493
+ collectTraitRefsFromEffects(tick.effects, out);
48494
+ }
48495
+ return out;
48496
+ }
48466
48497
  function collectEmbeddedTraits(schema) {
48467
48498
  const out = /* @__PURE__ */ new Set();
48468
48499
  if (!schema?.orbitals) return out;
@@ -49301,23 +49332,48 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFa
49301
49332
  return null;
49302
49333
  }
49303
49334
  function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, onNavigate, onLocalFallback, persistence }) {
49304
- const { traits: traits2, allEntities, ir } = useResolvedSchema(schema, pageName);
49335
+ const { traits: traits2, allEntities, allTraits, ir } = useResolvedSchema(schema, pageName);
49305
49336
  const allPageTraits = useMemo(() => {
49306
- if (pageName && traits2.length > 0) return traits2;
49307
- if (!ir?.pages || ir.pages.size <= 1) return traits2;
49308
- const firstPage = ir.pages.values().next().value;
49309
- if (!firstPage) return traits2;
49310
- const firstPageTraits = [];
49311
- const seen = /* @__PURE__ */ new Set();
49312
- for (const binding of firstPage.traits) {
49313
- const name = binding.trait.name;
49314
- if (name && !seen.has(name)) {
49315
- seen.add(name);
49316
- firstPageTraits.push(binding);
49317
- }
49318
- }
49319
- return firstPageTraits.length > 0 ? firstPageTraits : traits2;
49320
- }, [ir, traits2, pageName]);
49337
+ let base;
49338
+ if (pageName && traits2.length > 0) {
49339
+ base = traits2;
49340
+ } else if (!ir?.pages || ir.pages.size <= 1) {
49341
+ base = traits2;
49342
+ } else {
49343
+ const firstPage = ir.pages.values().next().value;
49344
+ if (!firstPage) {
49345
+ base = traits2;
49346
+ } else {
49347
+ const firstPageTraits = [];
49348
+ const seen = /* @__PURE__ */ new Set();
49349
+ for (const binding of firstPage.traits) {
49350
+ const name = binding.trait.name;
49351
+ if (name && !seen.has(name)) {
49352
+ seen.add(name);
49353
+ firstPageTraits.push(binding);
49354
+ }
49355
+ }
49356
+ base = firstPageTraits.length > 0 ? firstPageTraits : traits2;
49357
+ }
49358
+ }
49359
+ const byName = new Set(base.map((b) => b.trait.name));
49360
+ const extra = [];
49361
+ const queue = [...base];
49362
+ while (queue.length > 0) {
49363
+ const binding = queue.shift();
49364
+ if (!binding) continue;
49365
+ for (const refName of collectTraitRefsFromResolvedTrait(binding.trait)) {
49366
+ if (byName.has(refName)) continue;
49367
+ const rt = allTraits.get(refName);
49368
+ if (!rt) continue;
49369
+ byName.add(refName);
49370
+ const sibling = { trait: rt, linkedEntity: rt.linkedEntity };
49371
+ extra.push(sibling);
49372
+ queue.push(sibling);
49373
+ }
49374
+ }
49375
+ return extra.length > 0 ? [...base, ...extra] : base;
49376
+ }, [ir, traits2, pageName, allTraits]);
49321
49377
  useMemo(() => {
49322
49378
  const parsed = schema;
49323
49379
  const orbitals = parsed?.orbitals;
@@ -49506,11 +49562,13 @@ function OrbPreview({
49506
49562
  return match?.page.name;
49507
49563
  }, [pages, initialPagePath]);
49508
49564
  const [currentPage, setCurrentPage] = useState(initialPageName);
49565
+ const prevInitialPagePathRef = useRef(initialPagePath);
49509
49566
  useEffect(() => {
49510
- if (initialPageName && initialPageName !== currentPage) {
49567
+ if (prevInitialPagePathRef.current !== initialPagePath) {
49568
+ prevInitialPagePathRef.current = initialPagePath;
49511
49569
  setCurrentPage(initialPageName);
49512
49570
  }
49513
- }, [initialPageName, currentPage]);
49571
+ }, [initialPagePath, initialPageName]);
49514
49572
  const handleNavigate = useCallback((path) => {
49515
49573
  const match = pages.find(({ page }) => page.path === path);
49516
49574
  navLog.debug("handleNavigate", () => ({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/ui",
3
- "version": "5.9.8",
3
+ "version": "5.9.10",
4
4
  "description": "React UI components, hooks, and providers for Almadar",
5
5
  "type": "module",
6
6
  "sideEffects": [