@almadar/ui 5.35.0 → 5.36.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
@@ -19849,61 +19849,53 @@ var init_Breadcrumb = __esm({
19849
19849
  function BuilderBoard({
19850
19850
  entity,
19851
19851
  completeEvent = "PUZZLE_COMPLETE",
19852
+ placeEvent,
19853
+ checkEvent,
19854
+ playAgainEvent,
19852
19855
  className
19853
19856
  }) {
19854
19857
  const { emit } = useEventBus();
19855
19858
  const { t } = useTranslate();
19856
19859
  const resolved = boardEntity(entity);
19857
- const [placements, setPlacements] = useState({});
19858
19860
  const [headerError, setHeaderError] = useState(false);
19859
- const [submitted, setSubmitted] = useState(false);
19860
- const [attempts, setAttempts] = useState(0);
19861
- const [showHint, setShowHint] = useState(false);
19861
+ const [selectedComponent, setSelectedComponent] = useState(null);
19862
19862
  const components = Array.isArray(resolved?.components) ? resolved.components : [];
19863
19863
  const slots = Array.isArray(resolved?.slots) ? resolved.slots : [];
19864
+ const placements = {};
19865
+ for (const slot of slots) {
19866
+ if (slot.placedComponentId) placements[slot.id] = slot.placedComponentId;
19867
+ }
19868
+ const attempts = num(resolved?.attempts);
19869
+ const result = str(resolved?.result) || "none";
19870
+ const submitted = result === "win";
19864
19871
  const usedComponentIds = new Set(Object.values(placements));
19865
19872
  const availableComponents = components.filter((c) => !usedComponentIds.has(c.id));
19866
- const [selectedComponent, setSelectedComponent] = useState(null);
19867
- const allPlaced = Object.keys(placements).length === slots.length;
19873
+ const allPlaced = slots.length > 0 && slots.every((s) => Boolean(placements[s.id]));
19868
19874
  const results = submitted ? slots.map((slot) => ({
19869
19875
  slot,
19870
19876
  placed: placements[slot.id],
19871
- correct: placements[slot.id] === slot.acceptsComponentId
19877
+ correct: placements[slot.id] === slot.requiredComponentId
19872
19878
  })) : [];
19873
- const allCorrect = results.length > 0 && results.every((r2) => r2.correct);
19879
+ const showHint = attempts >= 2 && Boolean(str(resolved?.hint));
19874
19880
  const handlePlaceComponent = (slotId) => {
19875
19881
  if (submitted || !selectedComponent) return;
19876
- setPlacements((prev) => ({ ...prev, [slotId]: selectedComponent }));
19882
+ if (placeEvent) emit(`UI:${placeEvent}`, { slotId, componentId: selectedComponent });
19877
19883
  setSelectedComponent(null);
19878
19884
  };
19879
19885
  const handleRemoveFromSlot = (slotId) => {
19880
19886
  if (submitted) return;
19881
- setPlacements((prev) => {
19882
- const next = { ...prev };
19883
- delete next[slotId];
19884
- return next;
19885
- });
19887
+ if (placeEvent) emit(`UI:${placeEvent}`, { slotId, componentId: "" });
19886
19888
  };
19887
- const handleSubmit = useCallback(() => {
19888
- setSubmitted(true);
19889
- setAttempts((a) => a + 1);
19890
- const correct = slots.every((slot) => placements[slot.id] === slot.acceptsComponentId);
19891
- if (correct) {
19889
+ const handleSubmit = () => {
19890
+ if (checkEvent) emit(`UI:${checkEvent}`, {});
19891
+ const solved = slots.length > 0 && slots.every((s) => placements[s.id] === s.requiredComponentId);
19892
+ if (solved && completeEvent) {
19892
19893
  emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
19893
19894
  }
19894
- }, [slots, placements, attempts, completeEvent, emit]);
19895
- const handleReset = () => {
19896
- setSubmitted(false);
19897
- if (attempts >= 2 && str(resolved?.hint)) {
19898
- setShowHint(true);
19899
- }
19900
19895
  };
19901
- const handleFullReset = () => {
19902
- setPlacements({});
19903
- setSubmitted(false);
19896
+ const handlePlayAgain = () => {
19904
19897
  setSelectedComponent(null);
19905
- setAttempts(0);
19906
- setShowHint(false);
19898
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
19907
19899
  };
19908
19900
  const getComponentById = (id) => components.find((c) => c.id === id);
19909
19901
  if (!resolved) return null;
@@ -19953,13 +19945,13 @@ function BuilderBoard({
19953
19945
  /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-muted-foreground", children: t("builder.blueprint") }),
19954
19946
  /* @__PURE__ */ jsx(VStack, { gap: "sm", children: slots.map((slot) => {
19955
19947
  const placedComp = placements[slot.id] ? getComponentById(placements[slot.id]) : null;
19956
- const result = results.find((r2) => r2.slot.id === slot.id);
19948
+ const result2 = results.find((r2) => r2.slot.id === slot.id);
19957
19949
  return /* @__PURE__ */ jsxs(
19958
19950
  HStack,
19959
19951
  {
19960
19952
  gap: "sm",
19961
19953
  align: "center",
19962
- className: `p-3 border-2 rounded ${result ? result.correct ? "border-success" : "border-error" : selectedComponent ? "border-dashed border-foreground cursor-pointer" : "border-border"}`,
19954
+ className: `p-3 border-2 rounded ${result2 ? result2.correct ? "border-success" : "border-error" : selectedComponent ? "border-dashed border-foreground cursor-pointer" : "border-border"}`,
19963
19955
  onClick: () => handlePlaceComponent(slot.id),
19964
19956
  children: [
19965
19957
  /* @__PURE__ */ jsxs(VStack, { gap: "none", className: "flex-1", children: [
@@ -19974,7 +19966,7 @@ function BuilderBoard({
19974
19966
  ] }) : null,
19975
19967
  placedComp.label
19976
19968
  ] }),
19977
- result && /* @__PURE__ */ jsx(Icon, { icon: result.correct ? CheckCircle : XCircle, size: "sm", className: result.correct ? "text-success" : "text-error" })
19969
+ result2 && /* @__PURE__ */ jsx(Icon, { icon: result2.correct ? CheckCircle : XCircle, size: "sm", className: result2.correct ? "text-success" : "text-error" })
19978
19970
  ] }) : /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("builder.empty") })
19979
19971
  ]
19980
19972
  },
@@ -19983,16 +19975,16 @@ function BuilderBoard({
19983
19975
  }) })
19984
19976
  ] }) }),
19985
19977
  submitted && /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", align: "center", children: [
19986
- /* @__PURE__ */ jsx(Icon, { icon: allCorrect ? CheckCircle : XCircle, size: "lg", className: allCorrect ? "text-success" : "text-error" }),
19987
- /* @__PURE__ */ jsx(Typography, { variant: "body", weight: "bold", children: allCorrect ? str(resolved.successMessage) || t("builder.success") : str(resolved.failMessage) || t("builder.incorrect") })
19978
+ /* @__PURE__ */ jsx(Icon, { icon: CheckCircle, size: "lg", className: "text-success" }),
19979
+ /* @__PURE__ */ jsx(Typography, { variant: "body", weight: "bold", children: str(resolved.successMessage) || t("builder.success") })
19988
19980
  ] }) }),
19989
19981
  showHint && hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: hint }) }),
19990
19982
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
19991
- !submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allPlaced, children: [
19983
+ !submitted && /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allPlaced, children: [
19992
19984
  /* @__PURE__ */ jsx(Icon, { icon: Wrench, size: "sm" }),
19993
19985
  t("builder.build")
19994
- ] }) : !allCorrect ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleReset, children: t("builder.tryAgain") }) : null,
19995
- /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
19986
+ ] }),
19987
+ /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handlePlayAgain, children: [
19996
19988
  /* @__PURE__ */ jsx(Icon, { icon: RotateCcw, size: "sm" }),
19997
19989
  t("builder.reset")
19998
19990
  ] })
@@ -23448,57 +23440,84 @@ var init_ChartLegend = __esm({
23448
23440
  function ClassifierBoard({
23449
23441
  entity,
23450
23442
  completeEvent = "PUZZLE_COMPLETE",
23443
+ assignEvent,
23444
+ checkEvent,
23445
+ playAgainEvent,
23451
23446
  className
23452
23447
  }) {
23453
23448
  const { emit } = useEventBus();
23454
23449
  const { t } = useTranslate();
23455
23450
  const resolved = boardEntity(entity);
23456
- const [assignments, setAssignments] = useState({});
23451
+ const [localAssignments, setLocalAssignments] = useState({});
23457
23452
  const [headerError, setHeaderError] = useState(false);
23458
- const [submitted, setSubmitted] = useState(false);
23459
- const [attempts, setAttempts] = useState(0);
23453
+ const [localSubmitted, setLocalSubmitted] = useState(false);
23454
+ const [localAttempts, setLocalAttempts] = useState(0);
23460
23455
  const [showHint, setShowHint] = useState(false);
23461
23456
  const items = Array.isArray(resolved?.items) ? resolved.items : [];
23462
23457
  const categories = Array.isArray(resolved?.categories) ? resolved.categories : [];
23458
+ const entityResult = str(resolved?.result);
23459
+ const entityDrivesResult = entityResult.length > 0;
23460
+ const entityHasAssignments = items.some((item) => item.assignedCategory != null);
23461
+ const assignments = entityHasAssignments ? items.reduce((acc, item) => {
23462
+ if (item.assignedCategory != null) acc[item.id] = item.assignedCategory;
23463
+ return acc;
23464
+ }, {}) : localAssignments;
23465
+ const attempts = entityDrivesResult ? num(resolved?.attempts) : localAttempts;
23466
+ const submitted = entityDrivesResult || localSubmitted;
23463
23467
  const unassignedItems = items.filter((item) => !assignments[item.id]);
23464
- const allAssigned = Object.keys(assignments).length === items.length;
23468
+ const allAssigned = items.length > 0 && Object.keys(assignments).length === items.length;
23465
23469
  const results = submitted ? items.map((item) => ({
23466
23470
  item,
23467
23471
  assigned: assignments[item.id],
23468
23472
  correct: assignments[item.id] === item.correctCategory
23469
23473
  })) : [];
23470
- const allCorrect = results.length > 0 && results.every((r2) => r2.correct);
23474
+ const allCorrect = entityDrivesResult ? entityResult === "success" : results.length > 0 && results.every((r2) => r2.correct);
23471
23475
  const correctCount = results.filter((r2) => r2.correct).length;
23472
23476
  const handleAssign = (itemId, categoryId) => {
23473
23477
  if (submitted) return;
23474
- setAssignments((prev) => ({ ...prev, [itemId]: categoryId }));
23478
+ if (assignEvent) {
23479
+ emit(`UI:${assignEvent}`, { itemId, categoryId });
23480
+ }
23481
+ if (!entityHasAssignments) {
23482
+ setLocalAssignments((prev) => ({ ...prev, [itemId]: categoryId }));
23483
+ }
23475
23484
  };
23476
23485
  const handleUnassign = (itemId) => {
23477
23486
  if (submitted) return;
23478
- setAssignments((prev) => {
23479
- const next = { ...prev };
23480
- delete next[itemId];
23481
- return next;
23482
- });
23487
+ if (!entityHasAssignments) {
23488
+ setLocalAssignments((prev) => {
23489
+ const next = { ...prev };
23490
+ delete next[itemId];
23491
+ return next;
23492
+ });
23493
+ }
23483
23494
  };
23484
23495
  const handleSubmit = useCallback(() => {
23485
- setSubmitted(true);
23486
- setAttempts((a) => a + 1);
23487
- const correct = items.every((item) => assignments[item.id] === item.correctCategory);
23488
- if (correct) {
23489
- emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
23496
+ if (checkEvent) {
23497
+ emit(`UI:${checkEvent}`, {});
23498
+ }
23499
+ if (!entityDrivesResult) {
23500
+ setLocalSubmitted(true);
23501
+ setLocalAttempts((a) => a + 1);
23502
+ const correct = items.every((item) => assignments[item.id] === item.correctCategory);
23503
+ if (correct) {
23504
+ emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
23505
+ }
23490
23506
  }
23491
- }, [items, assignments, attempts, completeEvent, emit]);
23507
+ }, [checkEvent, entityDrivesResult, items, assignments, attempts, completeEvent, emit]);
23492
23508
  const handleReset = () => {
23493
- setSubmitted(false);
23509
+ if (!entityDrivesResult) setLocalSubmitted(false);
23494
23510
  if (attempts >= 2 && str(resolved?.hint)) {
23495
23511
  setShowHint(true);
23496
23512
  }
23497
23513
  };
23498
23514
  const handleFullReset = () => {
23499
- setAssignments({});
23500
- setSubmitted(false);
23501
- setAttempts(0);
23515
+ if (playAgainEvent) {
23516
+ emit(`UI:${playAgainEvent}`, {});
23517
+ }
23518
+ setLocalAssignments({});
23519
+ setLocalSubmitted(false);
23520
+ setLocalAttempts(0);
23502
23521
  setShowHint(false);
23503
23522
  };
23504
23523
  if (!resolved) return null;
@@ -31381,13 +31400,13 @@ var init_MapView = __esm({
31381
31400
  shadowSize: [41, 41]
31382
31401
  });
31383
31402
  L.Marker.prototype.options.icon = defaultIcon;
31384
- const { useEffect: useEffect77, useRef: useRef69, useCallback: useCallback119, useState: useState115 } = React91__default;
31403
+ const { useEffect: useEffect78, useRef: useRef70, useCallback: useCallback117, useState: useState115 } = React91__default;
31385
31404
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
31386
31405
  const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
31387
31406
  function MapUpdater({ centerLat, centerLng, zoom }) {
31388
31407
  const map = useMap();
31389
- const prevRef = useRef69({ centerLat, centerLng, zoom });
31390
- useEffect77(() => {
31408
+ const prevRef = useRef70({ centerLat, centerLng, zoom });
31409
+ useEffect78(() => {
31391
31410
  const prev = prevRef.current;
31392
31411
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
31393
31412
  map.setView([centerLat, centerLng], zoom);
@@ -31398,7 +31417,7 @@ var init_MapView = __esm({
31398
31417
  }
31399
31418
  function MapClickHandler({ onMapClick }) {
31400
31419
  const map = useMap();
31401
- useEffect77(() => {
31420
+ useEffect78(() => {
31402
31421
  if (!onMapClick) return;
31403
31422
  const handler = (e) => {
31404
31423
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -31427,7 +31446,7 @@ var init_MapView = __esm({
31427
31446
  }) {
31428
31447
  const eventBus = useEventBus3();
31429
31448
  const [clickedPosition, setClickedPosition] = useState115(null);
31430
- const handleMapClick = useCallback119((lat, lng) => {
31449
+ const handleMapClick = useCallback117((lat, lng) => {
31431
31450
  if (showClickedPin) {
31432
31451
  setClickedPosition({ lat, lng });
31433
31452
  }
@@ -31436,7 +31455,7 @@ var init_MapView = __esm({
31436
31455
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
31437
31456
  }
31438
31457
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
31439
- const handleMarkerClick = useCallback119((marker) => {
31458
+ const handleMarkerClick = useCallback117((marker) => {
31440
31459
  onMarkerClick?.(marker);
31441
31460
  if (markerClickEvent) {
31442
31461
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -40249,51 +40268,52 @@ var init_DataTable = __esm({
40249
40268
  function DebuggerBoard({
40250
40269
  entity,
40251
40270
  completeEvent = "PUZZLE_COMPLETE",
40271
+ toggleFlagEvent,
40272
+ checkEvent,
40273
+ playAgainEvent,
40252
40274
  className
40253
40275
  }) {
40254
40276
  const { emit } = useEventBus();
40255
40277
  const { t } = useTranslate();
40256
40278
  const resolved = boardEntity(entity);
40257
- const [flaggedLines, setFlaggedLines] = useState(/* @__PURE__ */ new Set());
40258
40279
  const [headerError, setHeaderError] = useState(false);
40259
- const [submitted, setSubmitted] = useState(false);
40260
- const [attempts, setAttempts] = useState(0);
40261
40280
  const [showHint, setShowHint] = useState(false);
40281
+ const lines = Array.isArray(resolved?.lines) ? resolved.lines : [];
40282
+ const result = resolved?.result ?? null;
40283
+ const attempts = num(resolved?.attempts);
40284
+ const submitted = result != null;
40285
+ const bugLines = lines.filter((l) => l.isBug);
40286
+ const flaggedLines = lines.filter((l) => l.isFlagged);
40287
+ const correctFlags = lines.filter((l) => l.isBug && l.isFlagged);
40288
+ const falseFlags = lines.filter((l) => !l.isBug && l.isFlagged);
40289
+ const allCorrect = result === "win";
40262
40290
  const toggleLine = (lineId) => {
40263
40291
  if (submitted) return;
40264
- setFlaggedLines((prev) => {
40265
- const next = new Set(prev);
40266
- if (next.has(lineId)) {
40267
- next.delete(lineId);
40268
- } else {
40269
- next.add(lineId);
40270
- }
40271
- return next;
40272
- });
40292
+ if (toggleFlagEvent) {
40293
+ emit(`UI:${toggleFlagEvent}`, { lineId });
40294
+ }
40273
40295
  };
40274
- const lines = Array.isArray(resolved?.lines) ? resolved.lines : [];
40275
- const bugLines = lines.filter((l) => l.isBug);
40276
- const correctFlags = lines.filter((l) => l.isBug && flaggedLines.has(l.id));
40277
- const falseFlags = lines.filter((l) => !l.isBug && flaggedLines.has(l.id));
40278
- const allCorrect = submitted && correctFlags.length === bugLines.length && falseFlags.length === 0;
40279
40296
  const handleSubmit = useCallback(() => {
40280
- setSubmitted(true);
40281
- setAttempts((a) => a + 1);
40297
+ if (checkEvent) {
40298
+ emit(`UI:${checkEvent}`, {});
40299
+ }
40282
40300
  const correct = correctFlags.length === bugLines.length && falseFlags.length === 0;
40283
40301
  if (correct) {
40284
40302
  emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
40285
40303
  }
40286
- }, [correctFlags.length, bugLines.length, falseFlags.length, attempts, completeEvent, emit]);
40304
+ }, [checkEvent, correctFlags.length, bugLines.length, falseFlags.length, attempts, completeEvent, emit]);
40287
40305
  const handleReset = () => {
40288
- setSubmitted(false);
40306
+ if (playAgainEvent) {
40307
+ emit(`UI:${playAgainEvent}`, {});
40308
+ }
40289
40309
  if (attempts >= 2 && str(resolved?.hint)) {
40290
40310
  setShowHint(true);
40291
40311
  }
40292
40312
  };
40293
40313
  const handleFullReset = () => {
40294
- setFlaggedLines(/* @__PURE__ */ new Set());
40295
- setSubmitted(false);
40296
- setAttempts(0);
40314
+ if (playAgainEvent) {
40315
+ emit(`UI:${playAgainEvent}`, {});
40316
+ }
40297
40317
  setShowHint(false);
40298
40318
  };
40299
40319
  if (!resolved) return null;
@@ -40321,7 +40341,7 @@ function DebuggerBoard({
40321
40341
  /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(num(resolved.bugCount)) }) })
40322
40342
  ] }) }),
40323
40343
  /* @__PURE__ */ jsx(Card, { className: "p-0 overflow-hidden", children: /* @__PURE__ */ jsx(VStack, { gap: "none", children: lines.map((line, i) => {
40324
- const isFlagged = flaggedLines.has(line.id);
40344
+ const isFlagged = !!line.isFlagged;
40325
40345
  let lineStyle = "";
40326
40346
  if (submitted) {
40327
40347
  if (line.isBug && isFlagged) lineStyle = "bg-success/10";
@@ -40355,9 +40375,9 @@ function DebuggerBoard({
40355
40375
  /* @__PURE__ */ jsx(
40356
40376
  Icon,
40357
40377
  {
40358
- icon: flaggedLines.has(line.id) ? CheckCircle : XCircle,
40378
+ icon: line.isFlagged ? CheckCircle : XCircle,
40359
40379
  size: "xs",
40360
- className: flaggedLines.has(line.id) ? "text-success mt-0.5" : "text-warning mt-0.5"
40380
+ className: line.isFlagged ? "text-success mt-0.5" : "text-warning mt-0.5"
40361
40381
  }
40362
40382
  ),
40363
40383
  /* @__PURE__ */ jsxs(VStack, { gap: "none", children: [
@@ -40368,7 +40388,7 @@ function DebuggerBoard({
40368
40388
  ] }) }),
40369
40389
  showHint && hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: hint }) }),
40370
40390
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
40371
- !submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.size === 0, children: [
40391
+ !submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.length === 0, children: [
40372
40392
  /* @__PURE__ */ jsx(Icon, { icon: Send, size: "sm" }),
40373
40393
  t("debugger.submit")
40374
40394
  ] }) : !allCorrect ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleReset, children: t("debugger.tryAgain") }) : null,
@@ -44132,6 +44152,9 @@ function getOpponentAction(strategy, actions, history) {
44132
44152
  function NegotiatorBoard({
44133
44153
  entity,
44134
44154
  completeEvent = "PUZZLE_COMPLETE",
44155
+ playRoundEvent,
44156
+ finishEvent,
44157
+ playAgainEvent,
44135
44158
  className
44136
44159
  }) {
44137
44160
  const { emit } = useEventBus();
@@ -44140,13 +44163,14 @@ function NegotiatorBoard({
44140
44163
  const [history, setHistory] = useState([]);
44141
44164
  const [headerError, setHeaderError] = useState(false);
44142
44165
  const [showHint, setShowHint] = useState(false);
44143
- const totalRounds = num(resolved?.totalRounds);
44166
+ const totalRounds = num(resolved?.maxRounds);
44144
44167
  const targetScore = num(resolved?.targetScore);
44145
- const currentRound = history.length;
44146
- const isComplete = currentRound >= totalRounds;
44147
- const playerTotal = history.reduce((s, r2) => s + r2.playerPayoff, 0);
44168
+ const currentRound = num(resolved?.round);
44169
+ const result = str(resolved?.result) || "none";
44170
+ const playerTotal = num(resolved?.score);
44171
+ const isComplete = result !== "none" || totalRounds > 0 && currentRound >= totalRounds;
44172
+ const won = result === "win";
44148
44173
  const opponentTotal = history.reduce((s, r2) => s + r2.opponentPayoff, 0);
44149
- const won = isComplete && playerTotal >= targetScore;
44150
44174
  const actions = Array.isArray(resolved?.actions) ? resolved.actions : [];
44151
44175
  const payoffMatrix = Array.isArray(resolved?.payoffMatrix) ? resolved.payoffMatrix : [];
44152
44176
  const handleAction = useCallback((actionId) => {
@@ -44155,29 +44179,45 @@ function NegotiatorBoard({
44155
44179
  const payoff = payoffMatrix.find(
44156
44180
  (p2) => p2.playerAction === actionId && p2.opponentAction === opponentAction
44157
44181
  );
44158
- const result = {
44159
- round: currentRound + 1,
44160
- playerAction: actionId,
44161
- opponentAction,
44162
- playerPayoff: payoff?.playerPayoff ?? 0,
44163
- opponentPayoff: payoff?.opponentPayoff ?? 0
44164
- };
44165
- const newHistory = [...history, result];
44166
- setHistory(newHistory);
44167
- if (newHistory.length >= totalRounds) {
44168
- const total = newHistory.reduce((s, r2) => s + r2.playerPayoff, 0);
44169
- if (total >= targetScore) {
44170
- emit(`UI:${completeEvent}`, { success: true, score: total });
44171
- }
44172
- if (newHistory.length >= 3 && str(resolved?.hint)) {
44182
+ const playerPayoff = payoff?.playerPayoff ?? 0;
44183
+ setHistory((prev) => [
44184
+ ...prev,
44185
+ {
44186
+ round: prev.length + 1,
44187
+ playerAction: actionId,
44188
+ opponentAction,
44189
+ playerPayoff,
44190
+ opponentPayoff: payoff?.opponentPayoff ?? 0
44191
+ }
44192
+ ]);
44193
+ if (playRoundEvent) {
44194
+ emit(`UI:${playRoundEvent}`, { playerAction: actionId, payoff: playerPayoff });
44195
+ }
44196
+ if (totalRounds > 0 && currentRound + 1 >= totalRounds) {
44197
+ if (finishEvent) {
44198
+ emit(`UI:${finishEvent}`, {});
44199
+ }
44200
+ if (str(resolved?.hint)) {
44173
44201
  setShowHint(true);
44174
44202
  }
44175
44203
  }
44176
- }, [isComplete, resolved, totalRounds, targetScore, actions, payoffMatrix, history, currentRound, completeEvent, emit]);
44177
- const handleReset = () => {
44204
+ }, [isComplete, resolved, totalRounds, currentRound, actions, payoffMatrix, history, playRoundEvent, finishEvent, emit]);
44205
+ const handleReset = useCallback(() => {
44178
44206
  setHistory([]);
44179
44207
  setShowHint(false);
44180
- };
44208
+ if (playAgainEvent) {
44209
+ emit(`UI:${playAgainEvent}`, {});
44210
+ }
44211
+ }, [playAgainEvent, emit]);
44212
+ const completedRef = useRef(false);
44213
+ useEffect(() => {
44214
+ if (result === "win" && !completedRef.current) {
44215
+ completedRef.current = true;
44216
+ emit(`UI:${completeEvent}`, { success: true, score: playerTotal });
44217
+ } else if (result === "none") {
44218
+ completedRef.current = false;
44219
+ }
44220
+ }, [result, playerTotal, completeEvent, emit]);
44181
44221
  const getActionLabel = (id) => actions.find((a) => a.id === id)?.label ?? id;
44182
44222
  if (!resolved) return null;
44183
44223
  const theme = resolved.theme ?? void 0;
@@ -47143,67 +47183,47 @@ var init_SimulationGraph = __esm({
47143
47183
  function SimulatorBoard({
47144
47184
  entity,
47145
47185
  completeEvent = "PUZZLE_COMPLETE",
47186
+ setAEvent,
47187
+ setBEvent,
47188
+ checkEvent,
47189
+ playAgainEvent,
47146
47190
  className
47147
47191
  }) {
47148
47192
  const { emit } = useEventBus();
47149
47193
  const { t } = useTranslate();
47150
47194
  const resolved = boardEntity(entity);
47151
47195
  const parameters = Array.isArray(resolved?.parameters) ? resolved.parameters : [];
47152
- const [values, setValues] = useState(() => {
47153
- const init = {};
47154
- for (const p2 of parameters) {
47155
- init[p2.id] = p2.initial;
47156
- }
47157
- return init;
47158
- });
47159
47196
  const [headerError, setHeaderError] = useState(false);
47160
- const [submitted, setSubmitted] = useState(false);
47161
- const [attempts, setAttempts] = useState(0);
47162
- const [showHint, setShowHint] = useState(false);
47163
- const computeOutput = useCallback((params) => {
47164
- try {
47165
- const fn = new Function("params", `return (${str(resolved?.computeExpression)})`);
47166
- return fn(params);
47167
- } catch {
47168
- return 0;
47169
- }
47170
- }, [resolved?.computeExpression]);
47171
- const output = useMemo(() => computeOutput(values) ?? 0, [computeOutput, values]);
47172
- const targetValue = num(resolved?.targetValue);
47173
- const targetTolerance = num(resolved?.targetTolerance);
47174
- const isCorrect = Math.abs(output - targetValue) <= targetTolerance;
47175
- const handleParameterChange = (id, value) => {
47176
- if (submitted) return;
47177
- setValues((prev) => ({ ...prev, [id]: value }));
47197
+ if (!resolved) return null;
47198
+ const paramA = num(resolved.paramA);
47199
+ const paramB = num(resolved.paramB);
47200
+ const output = num(resolved.output);
47201
+ const targetValue = num(resolved.target);
47202
+ const targetTolerance = num(resolved.tolerance);
47203
+ const attempts = num(resolved.attempts);
47204
+ const result = str(resolved.result);
47205
+ const isWin = result === "win";
47206
+ const isComplete = result !== "none" && result !== "";
47207
+ const paramAValue = parameters[0];
47208
+ const paramBValue = parameters[1];
47209
+ const sliderValues = [paramA, paramB];
47210
+ const sliderEvents = [setAEvent, setBEvent];
47211
+ const handleParameterChange = (index, value) => {
47212
+ if (isComplete) return;
47213
+ const ev = sliderEvents[index];
47214
+ if (ev) emit(`UI:${ev}`, { value });
47178
47215
  };
47179
- const handleSubmit = () => {
47180
- setSubmitted(true);
47181
- setAttempts((a) => a + 1);
47182
- if (isCorrect) {
47183
- emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
47184
- }
47216
+ const handleCheck = () => {
47217
+ if (checkEvent) emit(`UI:${checkEvent}`, {});
47185
47218
  };
47186
- const handleReset = () => {
47187
- setSubmitted(false);
47188
- if (attempts >= 2 && str(resolved?.hint)) {
47189
- setShowHint(true);
47190
- }
47219
+ const handlePlayAgain = () => {
47220
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
47191
47221
  };
47192
- const handleFullReset = () => {
47193
- const init = {};
47194
- for (const p2 of parameters) {
47195
- init[p2.id] = p2.initial;
47196
- }
47197
- setValues(init);
47198
- setSubmitted(false);
47199
- setAttempts(0);
47200
- setShowHint(false);
47201
- };
47202
- if (!resolved) return null;
47203
47222
  const theme = resolved.theme ?? void 0;
47204
47223
  const themeBackground = theme?.background;
47205
47224
  const headerImage = str(resolved.headerImage);
47206
47225
  const hint = str(resolved.hint);
47226
+ const showHint = isComplete && !isWin && attempts >= 2 && Boolean(hint);
47207
47227
  const outputLabel = str(resolved.outputLabel);
47208
47228
  const outputUnit = str(resolved.outputUnit);
47209
47229
  return /* @__PURE__ */ jsx(
@@ -47223,41 +47243,43 @@ function SimulatorBoard({
47223
47243
  ] }) }),
47224
47244
  /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "md", children: [
47225
47245
  /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-muted-foreground", children: t("simulator.parameters") }),
47226
- parameters.map((param) => /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
47227
- /* @__PURE__ */ jsxs(HStack, { justify: "between", align: "center", children: [
47228
- /* @__PURE__ */ jsx(Typography, { variant: "body", weight: "medium", children: param.label }),
47229
- /* @__PURE__ */ jsxs(Badge, { size: "sm", children: [
47230
- values[param.id],
47231
- " ",
47232
- param.unit
47233
- ] })
47234
- ] }),
47235
- /* @__PURE__ */ jsx(
47236
- "input",
47237
- {
47238
- type: "range",
47239
- min: param.min,
47240
- max: param.max,
47241
- step: param.step,
47242
- value: values[param.id],
47243
- onChange: (e) => handleParameterChange(param.id, Number(e.target.value)),
47244
- disabled: submitted,
47245
- className: "w-full accent-foreground"
47246
- }
47247
- ),
47248
- /* @__PURE__ */ jsxs(HStack, { justify: "between", children: [
47249
- /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
47250
- param.min,
47251
- " ",
47252
- param.unit
47246
+ [paramAValue, paramBValue].map(
47247
+ (param, index) => param ? /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
47248
+ /* @__PURE__ */ jsxs(HStack, { justify: "between", align: "center", children: [
47249
+ /* @__PURE__ */ jsx(Typography, { variant: "body", weight: "medium", children: param.label }),
47250
+ /* @__PURE__ */ jsxs(Badge, { size: "sm", children: [
47251
+ sliderValues[index],
47252
+ " ",
47253
+ param.unit
47254
+ ] })
47253
47255
  ] }),
47254
- /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
47255
- param.max,
47256
- " ",
47257
- param.unit
47256
+ /* @__PURE__ */ jsx(
47257
+ "input",
47258
+ {
47259
+ type: "range",
47260
+ min: param.min,
47261
+ max: param.max,
47262
+ step: param.step,
47263
+ value: sliderValues[index],
47264
+ onChange: (e) => handleParameterChange(index, Number(e.target.value)),
47265
+ disabled: isComplete,
47266
+ className: "w-full accent-foreground"
47267
+ }
47268
+ ),
47269
+ /* @__PURE__ */ jsxs(HStack, { justify: "between", children: [
47270
+ /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
47271
+ param.min,
47272
+ " ",
47273
+ param.unit
47274
+ ] }),
47275
+ /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
47276
+ param.max,
47277
+ " ",
47278
+ param.unit
47279
+ ] })
47258
47280
  ] })
47259
- ] })
47260
- ] }, param.id))
47281
+ ] }, param.id ?? index) : null
47282
+ )
47261
47283
  ] }) }),
47262
47284
  /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", align: "center", children: [
47263
47285
  /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-muted-foreground", children: outputLabel }),
@@ -47266,9 +47288,9 @@ function SimulatorBoard({
47266
47288
  " ",
47267
47289
  outputUnit
47268
47290
  ] }),
47269
- submitted && /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
47270
- /* @__PURE__ */ jsx(Icon, { icon: isCorrect ? CheckCircle : XCircle, size: "sm", className: isCorrect ? "text-success" : "text-error" }),
47271
- /* @__PURE__ */ jsx(Typography, { variant: "body", className: isCorrect ? "text-success" : "text-error", children: isCorrect ? str(resolved.successMessage) || t("simulator.correct") : str(resolved.failMessage) || t("simulator.incorrect") })
47291
+ isComplete && /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
47292
+ /* @__PURE__ */ jsx(Icon, { icon: isWin ? CheckCircle : XCircle, size: "sm", className: isWin ? "text-success" : "text-error" }),
47293
+ /* @__PURE__ */ jsx(Typography, { variant: "body", className: isWin ? "text-success" : "text-error", children: isWin ? str(resolved.successMessage) || t("simulator.correct") : str(resolved.failMessage) || t("simulator.incorrect") })
47272
47294
  ] }),
47273
47295
  /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
47274
47296
  t("simulator.target"),
@@ -47283,11 +47305,11 @@ function SimulatorBoard({
47283
47305
  ] }) }),
47284
47306
  showHint && hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: hint }) }),
47285
47307
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
47286
- !submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, children: [
47308
+ !isComplete ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleCheck, children: [
47287
47309
  /* @__PURE__ */ jsx(Icon, { icon: Play, size: "sm" }),
47288
47310
  t("simulator.simulate")
47289
- ] }) : !isCorrect ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleReset, children: t("simulator.tryAgain") }) : null,
47290
- /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
47311
+ ] }) : null,
47312
+ /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handlePlayAgain, children: [
47291
47313
  /* @__PURE__ */ jsx(Icon, { icon: RotateCcw, size: "sm" }),
47292
47314
  t("simulator.reset")
47293
47315
  ] })