@almadar/ui 4.6.14 → 4.7.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.
package/dist/avl/index.js CHANGED
@@ -5328,13 +5328,23 @@ function SlotsProvider({ children }) {
5328
5328
  const setSlotPatterns = useCallback((slot, patterns, source) => {
5329
5329
  const sourceKey = source?.trait ?? DEFAULT_SOURCE_KEY2;
5330
5330
  const entityProp = patterns[0]?.pattern && typeof patterns[0].pattern === "object" ? patterns[0].pattern.entity : void 0;
5331
+ const firstPatternType = patterns[0]?.pattern && typeof patterns[0].pattern === "object" ? patterns[0].pattern.type : void 0;
5331
5332
  slotLog.debug("setSlotPatterns", {
5332
5333
  slot,
5333
5334
  sourceKey,
5334
5335
  patternCount: patterns.length,
5335
- firstPatternType: patterns[0]?.pattern && typeof patterns[0].pattern === "object" ? patterns[0].pattern.type : void 0,
5336
+ firstPatternType,
5336
5337
  entityRefId: refId(entityProp)
5337
5338
  });
5339
+ if (source?.trait) {
5340
+ xOrbitalLog.info("slot-set", {
5341
+ slot,
5342
+ sourceTrait: source.trait,
5343
+ patternCount: patterns.length,
5344
+ firstPatternType,
5345
+ state: source.state
5346
+ });
5347
+ }
5338
5348
  setSlots((prev) => {
5339
5349
  const prevSlot = prev[slot] ?? {};
5340
5350
  return {
@@ -5356,6 +5366,7 @@ function SlotsProvider({ children }) {
5356
5366
  });
5357
5367
  }, []);
5358
5368
  const clearSlotForSource = useCallback((slot, sourceTrait) => {
5369
+ xOrbitalLog.info("slot-clear-source", { slot, sourceTrait });
5359
5370
  setSlots((prev) => {
5360
5371
  const existing = prev[slot];
5361
5372
  if (!existing || !(sourceTrait in existing)) return prev;
@@ -5392,11 +5403,12 @@ function useSlotsActions() {
5392
5403
  }
5393
5404
  return actions;
5394
5405
  }
5395
- var slotLog, refIds, nextRefId, DEFAULT_SOURCE_KEY2, SlotsStateContext, SlotsActionsContext;
5406
+ var slotLog, xOrbitalLog, refIds, nextRefId, DEFAULT_SOURCE_KEY2, SlotsStateContext, SlotsActionsContext;
5396
5407
  var init_SlotsContext = __esm({
5397
5408
  "runtime/ui/SlotsContext.tsx"() {
5398
5409
  init_logger();
5399
5410
  slotLog = createLogger("almadar:ui:slot-render");
5411
+ xOrbitalLog = createLogger("almadar:runtime:cross-orbital");
5400
5412
  refIds = /* @__PURE__ */ new WeakMap();
5401
5413
  nextRefId = 1;
5402
5414
  DEFAULT_SOURCE_KEY2 = "__default__";
@@ -11132,7 +11144,7 @@ var init_MapView = __esm({
11132
11144
  shadowSize: [41, 41]
11133
11145
  });
11134
11146
  L.Marker.prototype.options.icon = defaultIcon;
11135
- const { useEffect: useEffect87, useRef: useRef87, useCallback: useCallback125, useState: useState125 } = React127__default;
11147
+ const { useEffect: useEffect87, useRef: useRef87, useCallback: useCallback125, useState: useState124 } = React127__default;
11136
11148
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
11137
11149
  const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
11138
11150
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -11176,7 +11188,7 @@ var init_MapView = __esm({
11176
11188
  showAttribution = true
11177
11189
  }) {
11178
11190
  const eventBus = useEventBus3();
11179
- const [clickedPosition, setClickedPosition] = useState125(null);
11191
+ const [clickedPosition, setClickedPosition] = useState124(null);
11180
11192
  const handleMapClick = useCallback125((lat, lng) => {
11181
11193
  if (showClickedPin) {
11182
11194
  setClickedPosition({ lat, lng });
@@ -12277,9 +12289,9 @@ function bindEventBus(eventBus) {
12277
12289
  log2.info("bindEventBus", { hasOnAny: !!eventBus.onAny });
12278
12290
  exposeOnWindow();
12279
12291
  if (window.__orbitalVerification) {
12280
- window.__orbitalVerification.sendEvent = (event, payload) => {
12281
- const prefixed = event.startsWith("UI:") ? event : `UI:${event}`;
12282
- log2.debug("sendEvent", { event: prefixed, payloadKeys: payload ? Object.keys(payload) : [] });
12292
+ window.__orbitalVerification.sendEvent = (event, payload, traitScope) => {
12293
+ const prefixed = event.startsWith("UI:") ? event : traitScope ? `UI:${traitScope}.${event}` : `UI:${event}`;
12294
+ log2.debug("sendEvent", { event: prefixed, traitScope, payloadKeys: payload ? Object.keys(payload) : [] });
12283
12295
  eventBus.emit(prefixed, payload);
12284
12296
  };
12285
12297
  const eventLog = [];
@@ -51454,6 +51466,7 @@ function useTraitStateMachine(traitBindings, slotsActions, options) {
51454
51466
  const eventBus = useEventBus();
51455
51467
  const { entities } = useEntitySchema();
51456
51468
  const traitConfigsByName = options?.traitConfigsByName;
51469
+ const orbitalsByTrait = options?.orbitalsByTrait;
51457
51470
  const manager = useMemo(() => {
51458
51471
  const traitDefs = traitBindings.map(toTraitDefinition);
51459
51472
  const m = new StateMachineManager(traitDefs);
@@ -51902,7 +51915,10 @@ function useTraitStateMachine(traitBindings, slotsActions, options) {
51902
51915
  if (!LIFECYCLE_EVENTS.has(normalizedEvent)) {
51903
51916
  for (const { traitName, result } of results) {
51904
51917
  if (!result.executed) continue;
51905
- eventBus.emit(normalizedEvent, payload, {
51918
+ const orbitalName = orbitalsByTrait?.[traitName];
51919
+ if (!orbitalName) continue;
51920
+ eventBus.emit(`UI:${orbitalName}.${traitName}.${normalizedEvent}`, payload, {
51921
+ orbital: orbitalName,
51906
51922
  trait: traitName,
51907
51923
  fromBridge: true
51908
51924
  });
@@ -51913,7 +51929,13 @@ function useTraitStateMachine(traitBindings, slotsActions, options) {
51913
51929
  }
51914
51930
  const onEventProcessed = optionsRef.current?.onEventProcessed;
51915
51931
  if (onEventProcessed) {
51916
- await onEventProcessed(normalizedEvent, payload);
51932
+ const dispatchedOrbitals = /* @__PURE__ */ new Set();
51933
+ for (const { traitName, result } of results) {
51934
+ if (!result.executed) continue;
51935
+ const orbital = orbitalsByTrait?.[traitName];
51936
+ if (orbital) dispatchedOrbitals.add(orbital);
51937
+ }
51938
+ await onEventProcessed(normalizedEvent, payload, dispatchedOrbitals);
51917
51939
  }
51918
51940
  }, [entities, eventBus]);
51919
51941
  const drainEventQueue = useCallback(async () => {
@@ -51957,26 +51979,34 @@ function useTraitStateMachine(traitBindings, slotsActions, options) {
51957
51979
  }
51958
51980
  console.log("[TraitStateMachine] Subscribing to events:", Array.from(allEvents));
51959
51981
  const unsubscribes = [];
51960
- for (const eventKey of allEvents) {
51961
- if (eventKey === "INIT" || eventKey === "LOAD" || eventKey === "$MOUNT") {
51962
- continue;
51982
+ for (const binding of traitBindings) {
51983
+ const traitName = binding.trait.name;
51984
+ const orbitalName = orbitalsByTrait?.[traitName];
51985
+ if (!orbitalName) continue;
51986
+ for (const transition of binding.trait.transitions) {
51987
+ const eventKey = transition.event;
51988
+ if (eventKey === "INIT" || eventKey === "LOAD" || eventKey === "$MOUNT") {
51989
+ continue;
51990
+ }
51991
+ const unsub = eventBus.on(`UI:${orbitalName}.${traitName}.${eventKey}`, (event) => {
51992
+ if (event.source && event.source.fromBridge) {
51993
+ return;
51994
+ }
51995
+ enqueueAndDrain(eventKey, event.payload);
51996
+ });
51997
+ unsubscribes.push(unsub);
51963
51998
  }
51964
- const unsub = eventBus.on(`UI:${eventKey}`, (event) => {
51965
- console.log("[TraitStateMachine] Received event:", `UI:${eventKey}`, event);
51966
- enqueueAndDrain(eventKey, event.payload);
51967
- });
51968
- unsubscribes.push(unsub);
51969
51999
  }
51970
52000
  for (const binding of traitBindings) {
52001
+ const ownOrbital = orbitalsByTrait?.[binding.trait.name];
51971
52002
  const listens = binding.trait.listens ?? [];
51972
52003
  for (const listen of listens) {
51973
- const expectedTrait = listen.source?.trait;
51974
- const unsub = eventBus.on(listen.event, (event) => {
51975
- if (expectedTrait) {
51976
- const emitTrait = event.source?.trait;
51977
- if (emitTrait !== expectedTrait) return;
51978
- }
51979
- console.log("[TraitStateMachine] listens", binding.trait.name, listen.event, "\u2192", listen.triggers, "from", event.source?.trait);
52004
+ const sourceTrait = listen.source?.trait;
52005
+ if (!sourceTrait) continue;
52006
+ const sourceOrbital = listen.source?.orbital ?? ownOrbital;
52007
+ if (!sourceOrbital) continue;
52008
+ const busKey = `UI:${sourceOrbital}.${sourceTrait}.${listen.event}`;
52009
+ const unsub = eventBus.on(busKey, (event) => {
51980
52010
  enqueueAndDrain(listen.triggers, event.payload);
51981
52011
  });
51982
52012
  unsubscribes.push(unsub);
@@ -52002,6 +52032,8 @@ init_EntitySchemaContext();
52002
52032
 
52003
52033
  // runtime/ServerBridge.tsx
52004
52034
  init_useEventBus();
52035
+ init_logger();
52036
+ var xOrbitalLog2 = createLogger("almadar:runtime:cross-orbital");
52005
52037
  var ServerBridgeContext = createContext(null);
52006
52038
  function useServerBridge() {
52007
52039
  const ctx = useContext(ServerBridgeContext);
@@ -52079,8 +52111,22 @@ function ServerBridgeProvider({
52079
52111
  }
52080
52112
  if (result.emittedEvents) {
52081
52113
  for (const emitted of result.emittedEvents) {
52082
- eventBus.emit(`UI:${emitted.event}`, emitted.payload);
52083
- eventBus.emit(emitted.event, emitted.payload);
52114
+ const evTrait = emitted.source?.trait;
52115
+ if (!evTrait) {
52116
+ xOrbitalLog2.warn("emit:dropped-no-source", {
52117
+ event: emitted.event,
52118
+ dispatchOrbital: orbitalName
52119
+ });
52120
+ continue;
52121
+ }
52122
+ const key = emitted.source?.orbital ? `UI:${emitted.source.orbital}.${evTrait}.${emitted.event}` : `UI:${evTrait}.${emitted.event}`;
52123
+ xOrbitalLog2.info("emit:rebroadcast", {
52124
+ busKey: key,
52125
+ sourceOrbital: emitted.source?.orbital,
52126
+ sourceTrait: evTrait,
52127
+ dispatchOrbital: orbitalName
52128
+ });
52129
+ eventBus.emit(key, emitted.payload);
52084
52130
  }
52085
52131
  }
52086
52132
  } else if (result.error) {
@@ -52201,6 +52247,10 @@ function prepareSchemaForPreview(input) {
52201
52247
  const schema = adjustSchemaForMockData(parsed, mockData);
52202
52248
  return { schema, mockData };
52203
52249
  }
52250
+
52251
+ // runtime/OrbPreview.tsx
52252
+ init_logger();
52253
+ var xOrbitalLog3 = createLogger("almadar:runtime:cross-orbital");
52204
52254
  function normalizeChild(child) {
52205
52255
  if (typeof child === "string") return child;
52206
52256
  if (child === null || typeof child !== "object" || Array.isArray(child)) {
@@ -52280,6 +52330,11 @@ function applyServerEffects(effects, uiSlots, onNavigate) {
52280
52330
  const patternRecord = eff.pattern;
52281
52331
  const { type: patternType, children, ...inlineProps } = patternRecord;
52282
52332
  const normalizedChildren = Array.isArray(children) ? children.map((c) => normalizeChild(c)) : children;
52333
+ xOrbitalLog3.info("slot-write", {
52334
+ slot: eff.slot,
52335
+ sourceTrait: eff.traitName ?? "server",
52336
+ patternType: typeof patternType === "string" ? patternType : void 0
52337
+ });
52283
52338
  uiSlots.render({
52284
52339
  target: eff.slot,
52285
52340
  pattern: patternType,
@@ -52294,19 +52349,26 @@ function applyServerEffects(effects, uiSlots, onNavigate) {
52294
52349
  }
52295
52350
  }
52296
52351
  }
52297
- function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFallback, persistence, traitConfigsByName }) {
52352
+ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFallback, persistence, traitConfigsByName, orbitalsByTrait }) {
52298
52353
  const slotsActions = useSlotsActions();
52299
52354
  const bridge = useServerBridge();
52300
52355
  const uiSlots = useUISlots();
52301
- const onEventProcessed = useCallback(async (event, payload) => {
52356
+ const onEventProcessed = useCallback(async (event, payload, dispatchedOrbitals) => {
52302
52357
  if (!bridge.connected || !orbitalNames?.length) return;
52303
- for (const name of orbitalNames) {
52358
+ const targets = dispatchedOrbitals && dispatchedOrbitals.size > 0 ? orbitalNames.filter((n) => dispatchedOrbitals.has(n)) : orbitalNames;
52359
+ xOrbitalLog3.info("TraitInitializer:fanout", {
52360
+ event,
52361
+ sentTo: targets,
52362
+ skipped: orbitalNames.filter((n) => !targets.includes(n)),
52363
+ dispatchedOrbitalsSize: dispatchedOrbitals?.size ?? 0
52364
+ });
52365
+ for (const name of targets) {
52304
52366
  const { effects, meta } = await bridge.sendEvent(name, event, payload);
52305
52367
  recordServerResponse(name, event, meta);
52306
52368
  applyServerEffects(effects, uiSlots, onNavigate);
52307
52369
  }
52308
52370
  }, [bridge.connected, bridge.sendEvent, orbitalNames, uiSlots, onNavigate]);
52309
- const opts = orbitalNames ? { onEventProcessed, navigate: onNavigate, traitConfigsByName } : { navigate: onNavigate, persistence, traitConfigsByName };
52371
+ const opts = orbitalNames ? { onEventProcessed, navigate: onNavigate, traitConfigsByName, orbitalsByTrait } : { navigate: onNavigate, persistence, traitConfigsByName, orbitalsByTrait };
52310
52372
  const { sendEvent } = useTraitStateMachine(traits2, slotsActions, opts);
52311
52373
  const initSentRef = useRef(false);
52312
52374
  useEffect(() => {
@@ -52356,27 +52418,68 @@ function SchemaRunner({ schema, serverUrl, mockData, pageName, onNavigate, onLoc
52356
52418
  const allPageTraits = useMemo(() => {
52357
52419
  if (pageName && traits2.length > 0) return traits2;
52358
52420
  if (!ir?.pages || ir.pages.size <= 1) return traits2;
52359
- const combined = [];
52421
+ const firstPage = ir.pages.values().next().value;
52422
+ if (!firstPage) return traits2;
52423
+ const firstPageTraits = [];
52360
52424
  const seen = /* @__PURE__ */ new Set();
52361
- for (const page of ir.pages.values()) {
52362
- for (const t of page.traits) {
52363
- const binding = t;
52364
- const traitObj = binding.trait;
52365
- const name = traitObj?.name ?? binding.name ?? "";
52366
- if (name && !seen.has(name)) {
52367
- seen.add(name);
52368
- combined.push(t);
52369
- }
52425
+ for (const binding of firstPage.traits) {
52426
+ const name = binding.trait.name;
52427
+ if (name && !seen.has(name)) {
52428
+ seen.add(name);
52429
+ firstPageTraits.push(binding);
52370
52430
  }
52371
52431
  }
52372
- return combined.length > 0 ? combined : traits2;
52432
+ return firstPageTraits.length > 0 ? firstPageTraits : traits2;
52373
52433
  }, [ir, traits2, pageName]);
52374
- const orbitalNames = useMemo(() => {
52434
+ useMemo(() => {
52375
52435
  const parsed = schema;
52376
52436
  const orbitals = parsed?.orbitals;
52377
52437
  if (!orbitals) return [];
52378
52438
  return orbitals.filter((o) => typeof o.name === "string").map((o) => o.name);
52379
52439
  }, [schema]);
52440
+ const orbitalsByTrait = useMemo(() => {
52441
+ const map = {};
52442
+ const parsed = schema;
52443
+ if (!parsed?.orbitals) return map;
52444
+ for (const orb of parsed.orbitals) {
52445
+ for (const traitRef of orb.traits) {
52446
+ let traitName;
52447
+ if (typeof traitRef === "string") {
52448
+ const parts = traitRef.split(".");
52449
+ traitName = parts[parts.length - 1];
52450
+ } else if ("ref" in traitRef && typeof traitRef.ref === "string") {
52451
+ const parts = traitRef.ref.split(".");
52452
+ traitName = traitRef.name ?? parts[parts.length - 1];
52453
+ } else if ("name" in traitRef && typeof traitRef.name === "string") {
52454
+ traitName = traitRef.name;
52455
+ }
52456
+ if (traitName) map[traitName] = orb.name;
52457
+ }
52458
+ }
52459
+ return map;
52460
+ }, [schema]);
52461
+ const pageOrbitalNames = useMemo(() => {
52462
+ const set = /* @__PURE__ */ new Set();
52463
+ for (const binding of allPageTraits) {
52464
+ const orb = orbitalsByTrait[binding.trait.name];
52465
+ if (orb) set.add(orb);
52466
+ }
52467
+ return Array.from(set);
52468
+ }, [allPageTraits, orbitalsByTrait]);
52469
+ useEffect(() => {
52470
+ const traitNames = allPageTraits.map((b) => b.trait.name);
52471
+ const orbitalsByTraitForPage = {};
52472
+ for (const name of traitNames) {
52473
+ const orb = orbitalsByTrait[name];
52474
+ if (orb) orbitalsByTraitForPage[name] = orb;
52475
+ }
52476
+ xOrbitalLog3.info("SchemaRunner:mount", {
52477
+ pageName,
52478
+ traitNames,
52479
+ orbitalsByTraitForPage,
52480
+ pageOrbitalNames: pageOrbitalNames.join(",")
52481
+ });
52482
+ }, [pageName, allPageTraits, orbitalsByTrait, pageOrbitalNames]);
52380
52483
  const traitConfigsByName = useMemo(() => {
52381
52484
  const map = {};
52382
52485
  const parsed = schema;
@@ -52417,8 +52520,9 @@ function SchemaRunner({ schema, serverUrl, mockData, pageName, onNavigate, onLoc
52417
52520
  TraitInitializer,
52418
52521
  {
52419
52522
  traits: allPageTraits,
52420
- orbitalNames: serverUrl ? orbitalNames : void 0,
52523
+ orbitalNames: serverUrl ? pageOrbitalNames : void 0,
52421
52524
  traitConfigsByName,
52525
+ orbitalsByTrait,
52422
52526
  onNavigate,
52423
52527
  onLocalFallback,
52424
52528
  persistence
@@ -52440,7 +52544,8 @@ function OrbPreview({
52440
52544
  autoMock = false,
52441
52545
  height = "400px",
52442
52546
  className,
52443
- serverUrl
52547
+ serverUrl,
52548
+ initialPagePath
52444
52549
  }) {
52445
52550
  const [localFallback, setLocalFallback] = useState(false);
52446
52551
  const eventBus = useEventBus();
@@ -52486,7 +52591,17 @@ function OrbPreview({
52486
52591
  return [];
52487
52592
  }
52488
52593
  }, [parsedSchema]);
52489
- const [currentPage, setCurrentPage] = useState(void 0);
52594
+ const initialPageName = useMemo(() => {
52595
+ if (!initialPagePath) return void 0;
52596
+ const match = pages.find(({ page }) => page.path === initialPagePath);
52597
+ return match?.page.name;
52598
+ }, [pages, initialPagePath]);
52599
+ const [currentPage, setCurrentPage] = useState(initialPageName);
52600
+ useEffect(() => {
52601
+ if (initialPageName && initialPageName !== currentPage) {
52602
+ setCurrentPage(initialPageName);
52603
+ }
52604
+ }, [initialPageName, currentPage]);
52490
52605
  const handleNavigate = useCallback((path) => {
52491
52606
  const match = pages.find(({ page }) => page.path === path);
52492
52607
  if (match) {
@@ -9,6 +9,8 @@ export interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
9
9
  subtitle?: string;
10
10
  /** Shadow size override */
11
11
  shadow?: CardShadow;
12
+ /** Card content */
13
+ children?: React.ReactNode;
12
14
  }
13
15
  export declare const Card: React.ForwardRefExoticComponent<CardProps & React.RefAttributes<HTMLDivElement>>;
14
16
  export declare const CardHeader: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
@@ -4897,6 +4897,7 @@ var init_SlotsContext = __esm({
4897
4897
  "runtime/ui/SlotsContext.tsx"() {
4898
4898
  init_logger();
4899
4899
  slotLog = createLogger("almadar:ui:slot-render");
4900
+ createLogger("almadar:runtime:cross-orbital");
4900
4901
  refIds = /* @__PURE__ */ new WeakMap();
4901
4902
  nextRefId = 1;
4902
4903
  React111.createContext({});
@@ -6311,7 +6312,7 @@ var init_MapView = __esm({
6311
6312
  shadowSize: [41, 41]
6312
6313
  });
6313
6314
  L.Marker.prototype.options.icon = defaultIcon;
6314
- const { useEffect: useEffect66, useRef: useRef65, useCallback: useCallback115, useState: useState100 } = React111__namespace.default;
6315
+ const { useEffect: useEffect66, useRef: useRef65, useCallback: useCallback115, useState: useState99 } = React111__namespace.default;
6315
6316
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
6316
6317
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
6317
6318
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -6355,7 +6356,7 @@ var init_MapView = __esm({
6355
6356
  showAttribution = true
6356
6357
  }) {
6357
6358
  const eventBus = useEventBus2();
6358
- const [clickedPosition, setClickedPosition] = useState100(null);
6359
+ const [clickedPosition, setClickedPosition] = useState99(null);
6359
6360
  const handleMapClick = useCallback115((lat, lng) => {
6360
6361
  if (showClickedPin) {
6361
6362
  setClickedPosition({ lat, lng });
@@ -41123,87 +41124,58 @@ function useUISlotManager() {
41123
41124
  // hooks/useUIEvents.ts
41124
41125
  init_useEventBus();
41125
41126
  var UI_PREFIX = "UI:";
41126
- function useUIEvents(dispatch, validEvents, eventBusInstance) {
41127
+ function useUIEvents(dispatch, traitName, validEvents, eventBusInstance) {
41127
41128
  const defaultEventBus = useEventBus();
41128
41129
  const eventBus = eventBusInstance ?? defaultEventBus;
41129
- const validEventsKey = validEvents ? validEvents.slice().sort().join(",") : "";
41130
+ const validEventsKey = validEvents.slice().sort().join(",");
41130
41131
  const stableValidEvents = React111.useMemo(
41131
41132
  () => validEvents,
41132
41133
  [validEventsKey]
41133
- // intentional — validEventsKey is the stable dep, not validEvents array ref
41134
+ // intentional — validEventsKey is the stable dep
41134
41135
  );
41135
41136
  React111.useEffect(() => {
41136
41137
  const unsubscribes = [];
41137
- if (stableValidEvents) {
41138
- for (const smEvent of stableValidEvents) {
41139
- const prefixedHandler = (event) => {
41140
- dispatch(smEvent, event.payload);
41141
- };
41142
- unsubscribes.push(eventBus.on(`${UI_PREFIX}${smEvent}`, prefixedHandler));
41143
- const directHandler = (event) => {
41144
- if (event.source && event.source.fromBridge) {
41145
- return;
41146
- }
41147
- dispatch(smEvent, event.payload);
41148
- };
41149
- unsubscribes.push(eventBus.on(smEvent, directHandler));
41150
- }
41151
- }
41152
- const genericHandler = (event) => {
41153
- const eventName = event.payload?.event;
41154
- if (eventName) {
41155
- const smEvent = eventName;
41156
- if (!stableValidEvents || stableValidEvents.includes(smEvent)) {
41157
- dispatch(smEvent, event.payload);
41138
+ for (const smEvent of stableValidEvents) {
41139
+ const handler = (event) => {
41140
+ if (event.source && event.source.fromBridge) {
41141
+ return;
41158
41142
  }
41159
- }
41160
- };
41161
- unsubscribes.push(eventBus.on(`${UI_PREFIX}DISPATCH`, genericHandler));
41143
+ dispatch(smEvent, event.payload);
41144
+ };
41145
+ unsubscribes.push(
41146
+ eventBus.on(`${UI_PREFIX}${traitName}.${smEvent}`, handler)
41147
+ );
41148
+ }
41162
41149
  return () => {
41163
41150
  for (const unsub of unsubscribes) {
41164
41151
  if (typeof unsub === "function") unsub();
41165
41152
  }
41166
41153
  };
41167
- }, [eventBus, dispatch, stableValidEvents]);
41154
+ }, [eventBus, dispatch, traitName, stableValidEvents]);
41168
41155
  }
41169
- function useSelectedEntity(eventBusInstance) {
41156
+ function useTraitListens(dispatch, listens, eventBusInstance) {
41170
41157
  const defaultEventBus = useEventBus();
41171
41158
  const eventBus = eventBusInstance ?? defaultEventBus;
41172
- const selectionContext = useSelectionContext();
41173
- const [localSelected, setLocalSelected] = React111.useState(null);
41174
- const usingContext = selectionContext !== null;
41159
+ const stableKey = listens.map((l) => `${l.sourceKey}->${l.trigger}`).sort().join("|");
41160
+ const stableListens = React111.useMemo(
41161
+ () => listens,
41162
+ [stableKey]
41163
+ // intentional
41164
+ );
41175
41165
  React111.useEffect(() => {
41176
- if (usingContext) return;
41177
- const handleSelect = (event) => {
41178
- const row = event.payload?.row;
41179
- if (row) {
41180
- setLocalSelected(row);
41181
- }
41182
- };
41183
- const handleDeselect = () => {
41184
- setLocalSelected(null);
41185
- };
41186
- const unsubSelect = eventBus.on("UI:SELECT", handleSelect);
41187
- const unsubView = eventBus.on("UI:VIEW", handleSelect);
41188
- const unsubDeselect = eventBus.on("UI:DESELECT", handleDeselect);
41189
- const unsubClose = eventBus.on("UI:CLOSE", handleDeselect);
41190
- const unsubCancel = eventBus.on("UI:CANCEL", handleDeselect);
41166
+ const unsubscribes = [];
41167
+ for (const spec of stableListens) {
41168
+ const handler = (event) => {
41169
+ dispatch(spec.trigger, event.payload);
41170
+ };
41171
+ unsubscribes.push(eventBus.on(`${UI_PREFIX}${spec.sourceKey}`, handler));
41172
+ }
41191
41173
  return () => {
41192
- [unsubSelect, unsubView, unsubDeselect, unsubClose, unsubCancel].forEach(
41193
- (unsub) => {
41194
- if (typeof unsub === "function") unsub();
41195
- }
41196
- );
41174
+ for (const unsub of unsubscribes) {
41175
+ if (typeof unsub === "function") unsub();
41176
+ }
41197
41177
  };
41198
- }, [eventBus, usingContext]);
41199
- if (selectionContext) {
41200
- return [selectionContext.selected, selectionContext.setSelected];
41201
- }
41202
- return [localSelected, setLocalSelected];
41203
- }
41204
- function useSelectionContext() {
41205
- const context = React111.useContext(providers.SelectionContext);
41206
- return context;
41178
+ }, [eventBus, dispatch, stableListens]);
41207
41179
  }
41208
41180
 
41209
41181
  // hooks/index.ts
@@ -41738,10 +41710,10 @@ exports.usePlayer = usePlayer;
41738
41710
  exports.usePreview = usePreview;
41739
41711
  exports.usePullToRefresh = usePullToRefresh;
41740
41712
  exports.useQuerySingleton = useQuerySingleton;
41741
- exports.useSelectedEntity = useSelectedEntity;
41742
41713
  exports.useSingletonEntity = useSingletonEntity;
41743
41714
  exports.useSpriteAnimations = useSpriteAnimations;
41744
41715
  exports.useSwipeGesture = useSwipeGesture;
41716
+ exports.useTraitListens = useTraitListens;
41745
41717
  exports.useTranslate = useTranslate;
41746
41718
  exports.useUIEvents = useUIEvents;
41747
41719
  exports.useUISlotManager = useUISlotManager;