@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.
@@ -16939,61 +16939,53 @@ var init_Breadcrumb = __esm({
16939
16939
  function BuilderBoard({
16940
16940
  entity,
16941
16941
  completeEvent = "PUZZLE_COMPLETE",
16942
+ placeEvent,
16943
+ checkEvent,
16944
+ playAgainEvent,
16942
16945
  className
16943
16946
  }) {
16944
16947
  const { emit } = useEventBus();
16945
16948
  const { t } = useTranslate();
16946
16949
  const resolved = boardEntity(entity);
16947
- const [placements, setPlacements] = useState({});
16948
16950
  const [headerError, setHeaderError] = useState(false);
16949
- const [submitted, setSubmitted] = useState(false);
16950
- const [attempts, setAttempts] = useState(0);
16951
- const [showHint, setShowHint] = useState(false);
16951
+ const [selectedComponent, setSelectedComponent] = useState(null);
16952
16952
  const components = Array.isArray(resolved?.components) ? resolved.components : [];
16953
16953
  const slots = Array.isArray(resolved?.slots) ? resolved.slots : [];
16954
+ const placements = {};
16955
+ for (const slot of slots) {
16956
+ if (slot.placedComponentId) placements[slot.id] = slot.placedComponentId;
16957
+ }
16958
+ const attempts = num(resolved?.attempts);
16959
+ const result = str(resolved?.result) || "none";
16960
+ const submitted = result === "win";
16954
16961
  const usedComponentIds = new Set(Object.values(placements));
16955
16962
  const availableComponents = components.filter((c) => !usedComponentIds.has(c.id));
16956
- const [selectedComponent, setSelectedComponent] = useState(null);
16957
- const allPlaced = Object.keys(placements).length === slots.length;
16963
+ const allPlaced = slots.length > 0 && slots.every((s) => Boolean(placements[s.id]));
16958
16964
  const results = submitted ? slots.map((slot) => ({
16959
16965
  slot,
16960
16966
  placed: placements[slot.id],
16961
- correct: placements[slot.id] === slot.acceptsComponentId
16967
+ correct: placements[slot.id] === slot.requiredComponentId
16962
16968
  })) : [];
16963
- const allCorrect = results.length > 0 && results.every((r) => r.correct);
16969
+ const showHint = attempts >= 2 && Boolean(str(resolved?.hint));
16964
16970
  const handlePlaceComponent = (slotId) => {
16965
16971
  if (submitted || !selectedComponent) return;
16966
- setPlacements((prev) => ({ ...prev, [slotId]: selectedComponent }));
16972
+ if (placeEvent) emit(`UI:${placeEvent}`, { slotId, componentId: selectedComponent });
16967
16973
  setSelectedComponent(null);
16968
16974
  };
16969
16975
  const handleRemoveFromSlot = (slotId) => {
16970
16976
  if (submitted) return;
16971
- setPlacements((prev) => {
16972
- const next = { ...prev };
16973
- delete next[slotId];
16974
- return next;
16975
- });
16977
+ if (placeEvent) emit(`UI:${placeEvent}`, { slotId, componentId: "" });
16976
16978
  };
16977
- const handleSubmit = useCallback(() => {
16978
- setSubmitted(true);
16979
- setAttempts((a) => a + 1);
16980
- const correct = slots.every((slot) => placements[slot.id] === slot.acceptsComponentId);
16981
- if (correct) {
16979
+ const handleSubmit = () => {
16980
+ if (checkEvent) emit(`UI:${checkEvent}`, {});
16981
+ const solved = slots.length > 0 && slots.every((s) => placements[s.id] === s.requiredComponentId);
16982
+ if (solved && completeEvent) {
16982
16983
  emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
16983
16984
  }
16984
- }, [slots, placements, attempts, completeEvent, emit]);
16985
- const handleReset = () => {
16986
- setSubmitted(false);
16987
- if (attempts >= 2 && str(resolved?.hint)) {
16988
- setShowHint(true);
16989
- }
16990
16985
  };
16991
- const handleFullReset = () => {
16992
- setPlacements({});
16993
- setSubmitted(false);
16986
+ const handlePlayAgain = () => {
16994
16987
  setSelectedComponent(null);
16995
- setAttempts(0);
16996
- setShowHint(false);
16988
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
16997
16989
  };
16998
16990
  const getComponentById = (id) => components.find((c) => c.id === id);
16999
16991
  if (!resolved) return null;
@@ -17043,13 +17035,13 @@ function BuilderBoard({
17043
17035
  /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-muted-foreground", children: t("builder.blueprint") }),
17044
17036
  /* @__PURE__ */ jsx(VStack, { gap: "sm", children: slots.map((slot) => {
17045
17037
  const placedComp = placements[slot.id] ? getComponentById(placements[slot.id]) : null;
17046
- const result = results.find((r) => r.slot.id === slot.id);
17038
+ const result2 = results.find((r) => r.slot.id === slot.id);
17047
17039
  return /* @__PURE__ */ jsxs(
17048
17040
  HStack,
17049
17041
  {
17050
17042
  gap: "sm",
17051
17043
  align: "center",
17052
- className: `p-3 border-2 rounded ${result ? result.correct ? "border-success" : "border-error" : selectedComponent ? "border-dashed border-foreground cursor-pointer" : "border-border"}`,
17044
+ className: `p-3 border-2 rounded ${result2 ? result2.correct ? "border-success" : "border-error" : selectedComponent ? "border-dashed border-foreground cursor-pointer" : "border-border"}`,
17053
17045
  onClick: () => handlePlaceComponent(slot.id),
17054
17046
  children: [
17055
17047
  /* @__PURE__ */ jsxs(VStack, { gap: "none", className: "flex-1", children: [
@@ -17064,7 +17056,7 @@ function BuilderBoard({
17064
17056
  ] }) : null,
17065
17057
  placedComp.label
17066
17058
  ] }),
17067
- result && /* @__PURE__ */ jsx(Icon, { icon: result.correct ? CheckCircle : XCircle, size: "sm", className: result.correct ? "text-success" : "text-error" })
17059
+ result2 && /* @__PURE__ */ jsx(Icon, { icon: result2.correct ? CheckCircle : XCircle, size: "sm", className: result2.correct ? "text-success" : "text-error" })
17068
17060
  ] }) : /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("builder.empty") })
17069
17061
  ]
17070
17062
  },
@@ -17073,16 +17065,16 @@ function BuilderBoard({
17073
17065
  }) })
17074
17066
  ] }) }),
17075
17067
  submitted && /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", align: "center", children: [
17076
- /* @__PURE__ */ jsx(Icon, { icon: allCorrect ? CheckCircle : XCircle, size: "lg", className: allCorrect ? "text-success" : "text-error" }),
17077
- /* @__PURE__ */ jsx(Typography, { variant: "body", weight: "bold", children: allCorrect ? str(resolved.successMessage) || t("builder.success") : str(resolved.failMessage) || t("builder.incorrect") })
17068
+ /* @__PURE__ */ jsx(Icon, { icon: CheckCircle, size: "lg", className: "text-success" }),
17069
+ /* @__PURE__ */ jsx(Typography, { variant: "body", weight: "bold", children: str(resolved.successMessage) || t("builder.success") })
17078
17070
  ] }) }),
17079
17071
  showHint && hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: hint }) }),
17080
17072
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
17081
- !submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allPlaced, children: [
17073
+ !submitted && /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allPlaced, children: [
17082
17074
  /* @__PURE__ */ jsx(Icon, { icon: Wrench, size: "sm" }),
17083
17075
  t("builder.build")
17084
- ] }) : !allCorrect ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleReset, children: t("builder.tryAgain") }) : null,
17085
- /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
17076
+ ] }),
17077
+ /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handlePlayAgain, children: [
17086
17078
  /* @__PURE__ */ jsx(Icon, { icon: RotateCcw, size: "sm" }),
17087
17079
  t("builder.reset")
17088
17080
  ] })
@@ -20538,57 +20530,84 @@ var init_ChartLegend = __esm({
20538
20530
  function ClassifierBoard({
20539
20531
  entity,
20540
20532
  completeEvent = "PUZZLE_COMPLETE",
20533
+ assignEvent,
20534
+ checkEvent,
20535
+ playAgainEvent,
20541
20536
  className
20542
20537
  }) {
20543
20538
  const { emit } = useEventBus();
20544
20539
  const { t } = useTranslate();
20545
20540
  const resolved = boardEntity(entity);
20546
- const [assignments, setAssignments] = useState({});
20541
+ const [localAssignments, setLocalAssignments] = useState({});
20547
20542
  const [headerError, setHeaderError] = useState(false);
20548
- const [submitted, setSubmitted] = useState(false);
20549
- const [attempts, setAttempts] = useState(0);
20543
+ const [localSubmitted, setLocalSubmitted] = useState(false);
20544
+ const [localAttempts, setLocalAttempts] = useState(0);
20550
20545
  const [showHint, setShowHint] = useState(false);
20551
20546
  const items = Array.isArray(resolved?.items) ? resolved.items : [];
20552
20547
  const categories = Array.isArray(resolved?.categories) ? resolved.categories : [];
20548
+ const entityResult = str(resolved?.result);
20549
+ const entityDrivesResult = entityResult.length > 0;
20550
+ const entityHasAssignments = items.some((item) => item.assignedCategory != null);
20551
+ const assignments = entityHasAssignments ? items.reduce((acc, item) => {
20552
+ if (item.assignedCategory != null) acc[item.id] = item.assignedCategory;
20553
+ return acc;
20554
+ }, {}) : localAssignments;
20555
+ const attempts = entityDrivesResult ? num(resolved?.attempts) : localAttempts;
20556
+ const submitted = entityDrivesResult || localSubmitted;
20553
20557
  const unassignedItems = items.filter((item) => !assignments[item.id]);
20554
- const allAssigned = Object.keys(assignments).length === items.length;
20558
+ const allAssigned = items.length > 0 && Object.keys(assignments).length === items.length;
20555
20559
  const results = submitted ? items.map((item) => ({
20556
20560
  item,
20557
20561
  assigned: assignments[item.id],
20558
20562
  correct: assignments[item.id] === item.correctCategory
20559
20563
  })) : [];
20560
- const allCorrect = results.length > 0 && results.every((r) => r.correct);
20564
+ const allCorrect = entityDrivesResult ? entityResult === "success" : results.length > 0 && results.every((r) => r.correct);
20561
20565
  const correctCount = results.filter((r) => r.correct).length;
20562
20566
  const handleAssign = (itemId, categoryId) => {
20563
20567
  if (submitted) return;
20564
- setAssignments((prev) => ({ ...prev, [itemId]: categoryId }));
20568
+ if (assignEvent) {
20569
+ emit(`UI:${assignEvent}`, { itemId, categoryId });
20570
+ }
20571
+ if (!entityHasAssignments) {
20572
+ setLocalAssignments((prev) => ({ ...prev, [itemId]: categoryId }));
20573
+ }
20565
20574
  };
20566
20575
  const handleUnassign = (itemId) => {
20567
20576
  if (submitted) return;
20568
- setAssignments((prev) => {
20569
- const next = { ...prev };
20570
- delete next[itemId];
20571
- return next;
20572
- });
20577
+ if (!entityHasAssignments) {
20578
+ setLocalAssignments((prev) => {
20579
+ const next = { ...prev };
20580
+ delete next[itemId];
20581
+ return next;
20582
+ });
20583
+ }
20573
20584
  };
20574
20585
  const handleSubmit = useCallback(() => {
20575
- setSubmitted(true);
20576
- setAttempts((a) => a + 1);
20577
- const correct = items.every((item) => assignments[item.id] === item.correctCategory);
20578
- if (correct) {
20579
- emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
20586
+ if (checkEvent) {
20587
+ emit(`UI:${checkEvent}`, {});
20588
+ }
20589
+ if (!entityDrivesResult) {
20590
+ setLocalSubmitted(true);
20591
+ setLocalAttempts((a) => a + 1);
20592
+ const correct = items.every((item) => assignments[item.id] === item.correctCategory);
20593
+ if (correct) {
20594
+ emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
20595
+ }
20580
20596
  }
20581
- }, [items, assignments, attempts, completeEvent, emit]);
20597
+ }, [checkEvent, entityDrivesResult, items, assignments, attempts, completeEvent, emit]);
20582
20598
  const handleReset = () => {
20583
- setSubmitted(false);
20599
+ if (!entityDrivesResult) setLocalSubmitted(false);
20584
20600
  if (attempts >= 2 && str(resolved?.hint)) {
20585
20601
  setShowHint(true);
20586
20602
  }
20587
20603
  };
20588
20604
  const handleFullReset = () => {
20589
- setAssignments({});
20590
- setSubmitted(false);
20591
- setAttempts(0);
20605
+ if (playAgainEvent) {
20606
+ emit(`UI:${playAgainEvent}`, {});
20607
+ }
20608
+ setLocalAssignments({});
20609
+ setLocalSubmitted(false);
20610
+ setLocalAttempts(0);
20592
20611
  setShowHint(false);
20593
20612
  };
20594
20613
  if (!resolved) return null;
@@ -28471,13 +28490,13 @@ var init_MapView = __esm({
28471
28490
  shadowSize: [41, 41]
28472
28491
  });
28473
28492
  L.Marker.prototype.options.icon = defaultIcon;
28474
- const { useEffect: useEffect73, useRef: useRef67, useCallback: useCallback113, useState: useState107 } = React82__default;
28493
+ const { useEffect: useEffect74, useRef: useRef68, useCallback: useCallback111, useState: useState107 } = React82__default;
28475
28494
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
28476
28495
  const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
28477
28496
  function MapUpdater({ centerLat, centerLng, zoom }) {
28478
28497
  const map = useMap();
28479
- const prevRef = useRef67({ centerLat, centerLng, zoom });
28480
- useEffect73(() => {
28498
+ const prevRef = useRef68({ centerLat, centerLng, zoom });
28499
+ useEffect74(() => {
28481
28500
  const prev = prevRef.current;
28482
28501
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
28483
28502
  map.setView([centerLat, centerLng], zoom);
@@ -28488,7 +28507,7 @@ var init_MapView = __esm({
28488
28507
  }
28489
28508
  function MapClickHandler({ onMapClick }) {
28490
28509
  const map = useMap();
28491
- useEffect73(() => {
28510
+ useEffect74(() => {
28492
28511
  if (!onMapClick) return;
28493
28512
  const handler = (e) => {
28494
28513
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -28517,7 +28536,7 @@ var init_MapView = __esm({
28517
28536
  }) {
28518
28537
  const eventBus = useEventBus3();
28519
28538
  const [clickedPosition, setClickedPosition] = useState107(null);
28520
- const handleMapClick = useCallback113((lat, lng) => {
28539
+ const handleMapClick = useCallback111((lat, lng) => {
28521
28540
  if (showClickedPin) {
28522
28541
  setClickedPosition({ lat, lng });
28523
28542
  }
@@ -28526,7 +28545,7 @@ var init_MapView = __esm({
28526
28545
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
28527
28546
  }
28528
28547
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
28529
- const handleMarkerClick = useCallback113((marker) => {
28548
+ const handleMarkerClick = useCallback111((marker) => {
28530
28549
  onMarkerClick?.(marker);
28531
28550
  if (markerClickEvent) {
28532
28551
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -37748,51 +37767,52 @@ var init_DataTable = __esm({
37748
37767
  function DebuggerBoard({
37749
37768
  entity,
37750
37769
  completeEvent = "PUZZLE_COMPLETE",
37770
+ toggleFlagEvent,
37771
+ checkEvent,
37772
+ playAgainEvent,
37751
37773
  className
37752
37774
  }) {
37753
37775
  const { emit } = useEventBus();
37754
37776
  const { t } = useTranslate();
37755
37777
  const resolved = boardEntity(entity);
37756
- const [flaggedLines, setFlaggedLines] = useState(/* @__PURE__ */ new Set());
37757
37778
  const [headerError, setHeaderError] = useState(false);
37758
- const [submitted, setSubmitted] = useState(false);
37759
- const [attempts, setAttempts] = useState(0);
37760
37779
  const [showHint, setShowHint] = useState(false);
37780
+ const lines = Array.isArray(resolved?.lines) ? resolved.lines : [];
37781
+ const result = resolved?.result ?? null;
37782
+ const attempts = num(resolved?.attempts);
37783
+ const submitted = result != null;
37784
+ const bugLines = lines.filter((l) => l.isBug);
37785
+ const flaggedLines = lines.filter((l) => l.isFlagged);
37786
+ const correctFlags = lines.filter((l) => l.isBug && l.isFlagged);
37787
+ const falseFlags = lines.filter((l) => !l.isBug && l.isFlagged);
37788
+ const allCorrect = result === "win";
37761
37789
  const toggleLine = (lineId) => {
37762
37790
  if (submitted) return;
37763
- setFlaggedLines((prev) => {
37764
- const next = new Set(prev);
37765
- if (next.has(lineId)) {
37766
- next.delete(lineId);
37767
- } else {
37768
- next.add(lineId);
37769
- }
37770
- return next;
37771
- });
37791
+ if (toggleFlagEvent) {
37792
+ emit(`UI:${toggleFlagEvent}`, { lineId });
37793
+ }
37772
37794
  };
37773
- const lines = Array.isArray(resolved?.lines) ? resolved.lines : [];
37774
- const bugLines = lines.filter((l) => l.isBug);
37775
- const correctFlags = lines.filter((l) => l.isBug && flaggedLines.has(l.id));
37776
- const falseFlags = lines.filter((l) => !l.isBug && flaggedLines.has(l.id));
37777
- const allCorrect = submitted && correctFlags.length === bugLines.length && falseFlags.length === 0;
37778
37795
  const handleSubmit = useCallback(() => {
37779
- setSubmitted(true);
37780
- setAttempts((a) => a + 1);
37796
+ if (checkEvent) {
37797
+ emit(`UI:${checkEvent}`, {});
37798
+ }
37781
37799
  const correct = correctFlags.length === bugLines.length && falseFlags.length === 0;
37782
37800
  if (correct) {
37783
37801
  emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
37784
37802
  }
37785
- }, [correctFlags.length, bugLines.length, falseFlags.length, attempts, completeEvent, emit]);
37803
+ }, [checkEvent, correctFlags.length, bugLines.length, falseFlags.length, attempts, completeEvent, emit]);
37786
37804
  const handleReset = () => {
37787
- setSubmitted(false);
37805
+ if (playAgainEvent) {
37806
+ emit(`UI:${playAgainEvent}`, {});
37807
+ }
37788
37808
  if (attempts >= 2 && str(resolved?.hint)) {
37789
37809
  setShowHint(true);
37790
37810
  }
37791
37811
  };
37792
37812
  const handleFullReset = () => {
37793
- setFlaggedLines(/* @__PURE__ */ new Set());
37794
- setSubmitted(false);
37795
- setAttempts(0);
37813
+ if (playAgainEvent) {
37814
+ emit(`UI:${playAgainEvent}`, {});
37815
+ }
37796
37816
  setShowHint(false);
37797
37817
  };
37798
37818
  if (!resolved) return null;
@@ -37820,7 +37840,7 @@ function DebuggerBoard({
37820
37840
  /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(num(resolved.bugCount)) }) })
37821
37841
  ] }) }),
37822
37842
  /* @__PURE__ */ jsx(Card, { className: "p-0 overflow-hidden", children: /* @__PURE__ */ jsx(VStack, { gap: "none", children: lines.map((line, i) => {
37823
- const isFlagged = flaggedLines.has(line.id);
37843
+ const isFlagged = !!line.isFlagged;
37824
37844
  let lineStyle = "";
37825
37845
  if (submitted) {
37826
37846
  if (line.isBug && isFlagged) lineStyle = "bg-success/10";
@@ -37854,9 +37874,9 @@ function DebuggerBoard({
37854
37874
  /* @__PURE__ */ jsx(
37855
37875
  Icon,
37856
37876
  {
37857
- icon: flaggedLines.has(line.id) ? CheckCircle : XCircle,
37877
+ icon: line.isFlagged ? CheckCircle : XCircle,
37858
37878
  size: "xs",
37859
- className: flaggedLines.has(line.id) ? "text-success mt-0.5" : "text-warning mt-0.5"
37879
+ className: line.isFlagged ? "text-success mt-0.5" : "text-warning mt-0.5"
37860
37880
  }
37861
37881
  ),
37862
37882
  /* @__PURE__ */ jsxs(VStack, { gap: "none", children: [
@@ -37867,7 +37887,7 @@ function DebuggerBoard({
37867
37887
  ] }) }),
37868
37888
  showHint && hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: hint }) }),
37869
37889
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
37870
- !submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.size === 0, children: [
37890
+ !submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.length === 0, children: [
37871
37891
  /* @__PURE__ */ jsx(Icon, { icon: Send, size: "sm" }),
37872
37892
  t("debugger.submit")
37873
37893
  ] }) : !allCorrect ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleReset, children: t("debugger.tryAgain") }) : null,
@@ -41631,6 +41651,9 @@ function getOpponentAction(strategy, actions, history) {
41631
41651
  function NegotiatorBoard({
41632
41652
  entity,
41633
41653
  completeEvent = "PUZZLE_COMPLETE",
41654
+ playRoundEvent,
41655
+ finishEvent,
41656
+ playAgainEvent,
41634
41657
  className
41635
41658
  }) {
41636
41659
  const { emit } = useEventBus();
@@ -41639,13 +41662,14 @@ function NegotiatorBoard({
41639
41662
  const [history, setHistory] = useState([]);
41640
41663
  const [headerError, setHeaderError] = useState(false);
41641
41664
  const [showHint, setShowHint] = useState(false);
41642
- const totalRounds = num(resolved?.totalRounds);
41665
+ const totalRounds = num(resolved?.maxRounds);
41643
41666
  const targetScore = num(resolved?.targetScore);
41644
- const currentRound = history.length;
41645
- const isComplete = currentRound >= totalRounds;
41646
- const playerTotal = history.reduce((s, r) => s + r.playerPayoff, 0);
41667
+ const currentRound = num(resolved?.round);
41668
+ const result = str(resolved?.result) || "none";
41669
+ const playerTotal = num(resolved?.score);
41670
+ const isComplete = result !== "none" || totalRounds > 0 && currentRound >= totalRounds;
41671
+ const won = result === "win";
41647
41672
  const opponentTotal = history.reduce((s, r) => s + r.opponentPayoff, 0);
41648
- const won = isComplete && playerTotal >= targetScore;
41649
41673
  const actions = Array.isArray(resolved?.actions) ? resolved.actions : [];
41650
41674
  const payoffMatrix = Array.isArray(resolved?.payoffMatrix) ? resolved.payoffMatrix : [];
41651
41675
  const handleAction = useCallback((actionId) => {
@@ -41654,29 +41678,45 @@ function NegotiatorBoard({
41654
41678
  const payoff = payoffMatrix.find(
41655
41679
  (p2) => p2.playerAction === actionId && p2.opponentAction === opponentAction
41656
41680
  );
41657
- const result = {
41658
- round: currentRound + 1,
41659
- playerAction: actionId,
41660
- opponentAction,
41661
- playerPayoff: payoff?.playerPayoff ?? 0,
41662
- opponentPayoff: payoff?.opponentPayoff ?? 0
41663
- };
41664
- const newHistory = [...history, result];
41665
- setHistory(newHistory);
41666
- if (newHistory.length >= totalRounds) {
41667
- const total = newHistory.reduce((s, r) => s + r.playerPayoff, 0);
41668
- if (total >= targetScore) {
41669
- emit(`UI:${completeEvent}`, { success: true, score: total });
41670
- }
41671
- if (newHistory.length >= 3 && str(resolved?.hint)) {
41681
+ const playerPayoff = payoff?.playerPayoff ?? 0;
41682
+ setHistory((prev) => [
41683
+ ...prev,
41684
+ {
41685
+ round: prev.length + 1,
41686
+ playerAction: actionId,
41687
+ opponentAction,
41688
+ playerPayoff,
41689
+ opponentPayoff: payoff?.opponentPayoff ?? 0
41690
+ }
41691
+ ]);
41692
+ if (playRoundEvent) {
41693
+ emit(`UI:${playRoundEvent}`, { playerAction: actionId, payoff: playerPayoff });
41694
+ }
41695
+ if (totalRounds > 0 && currentRound + 1 >= totalRounds) {
41696
+ if (finishEvent) {
41697
+ emit(`UI:${finishEvent}`, {});
41698
+ }
41699
+ if (str(resolved?.hint)) {
41672
41700
  setShowHint(true);
41673
41701
  }
41674
41702
  }
41675
- }, [isComplete, resolved, totalRounds, targetScore, actions, payoffMatrix, history, currentRound, completeEvent, emit]);
41676
- const handleReset = () => {
41703
+ }, [isComplete, resolved, totalRounds, currentRound, actions, payoffMatrix, history, playRoundEvent, finishEvent, emit]);
41704
+ const handleReset = useCallback(() => {
41677
41705
  setHistory([]);
41678
41706
  setShowHint(false);
41679
- };
41707
+ if (playAgainEvent) {
41708
+ emit(`UI:${playAgainEvent}`, {});
41709
+ }
41710
+ }, [playAgainEvent, emit]);
41711
+ const completedRef = useRef(false);
41712
+ useEffect(() => {
41713
+ if (result === "win" && !completedRef.current) {
41714
+ completedRef.current = true;
41715
+ emit(`UI:${completeEvent}`, { success: true, score: playerTotal });
41716
+ } else if (result === "none") {
41717
+ completedRef.current = false;
41718
+ }
41719
+ }, [result, playerTotal, completeEvent, emit]);
41680
41720
  const getActionLabel = (id) => actions.find((a) => a.id === id)?.label ?? id;
41681
41721
  if (!resolved) return null;
41682
41722
  const theme = resolved.theme ?? void 0;
@@ -44642,67 +44682,47 @@ var init_SimulationGraph = __esm({
44642
44682
  function SimulatorBoard({
44643
44683
  entity,
44644
44684
  completeEvent = "PUZZLE_COMPLETE",
44685
+ setAEvent,
44686
+ setBEvent,
44687
+ checkEvent,
44688
+ playAgainEvent,
44645
44689
  className
44646
44690
  }) {
44647
44691
  const { emit } = useEventBus();
44648
44692
  const { t } = useTranslate();
44649
44693
  const resolved = boardEntity(entity);
44650
44694
  const parameters = Array.isArray(resolved?.parameters) ? resolved.parameters : [];
44651
- const [values, setValues] = useState(() => {
44652
- const init = {};
44653
- for (const p2 of parameters) {
44654
- init[p2.id] = p2.initial;
44655
- }
44656
- return init;
44657
- });
44658
44695
  const [headerError, setHeaderError] = useState(false);
44659
- const [submitted, setSubmitted] = useState(false);
44660
- const [attempts, setAttempts] = useState(0);
44661
- const [showHint, setShowHint] = useState(false);
44662
- const computeOutput = useCallback((params) => {
44663
- try {
44664
- const fn = new Function("params", `return (${str(resolved?.computeExpression)})`);
44665
- return fn(params);
44666
- } catch {
44667
- return 0;
44668
- }
44669
- }, [resolved?.computeExpression]);
44670
- const output = useMemo(() => computeOutput(values) ?? 0, [computeOutput, values]);
44671
- const targetValue = num(resolved?.targetValue);
44672
- const targetTolerance = num(resolved?.targetTolerance);
44673
- const isCorrect = Math.abs(output - targetValue) <= targetTolerance;
44674
- const handleParameterChange = (id, value) => {
44675
- if (submitted) return;
44676
- setValues((prev) => ({ ...prev, [id]: value }));
44696
+ if (!resolved) return null;
44697
+ const paramA = num(resolved.paramA);
44698
+ const paramB = num(resolved.paramB);
44699
+ const output = num(resolved.output);
44700
+ const targetValue = num(resolved.target);
44701
+ const targetTolerance = num(resolved.tolerance);
44702
+ const attempts = num(resolved.attempts);
44703
+ const result = str(resolved.result);
44704
+ const isWin = result === "win";
44705
+ const isComplete = result !== "none" && result !== "";
44706
+ const paramAValue = parameters[0];
44707
+ const paramBValue = parameters[1];
44708
+ const sliderValues = [paramA, paramB];
44709
+ const sliderEvents = [setAEvent, setBEvent];
44710
+ const handleParameterChange = (index, value) => {
44711
+ if (isComplete) return;
44712
+ const ev = sliderEvents[index];
44713
+ if (ev) emit(`UI:${ev}`, { value });
44677
44714
  };
44678
- const handleSubmit = () => {
44679
- setSubmitted(true);
44680
- setAttempts((a) => a + 1);
44681
- if (isCorrect) {
44682
- emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
44683
- }
44715
+ const handleCheck = () => {
44716
+ if (checkEvent) emit(`UI:${checkEvent}`, {});
44684
44717
  };
44685
- const handleReset = () => {
44686
- setSubmitted(false);
44687
- if (attempts >= 2 && str(resolved?.hint)) {
44688
- setShowHint(true);
44689
- }
44718
+ const handlePlayAgain = () => {
44719
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
44690
44720
  };
44691
- const handleFullReset = () => {
44692
- const init = {};
44693
- for (const p2 of parameters) {
44694
- init[p2.id] = p2.initial;
44695
- }
44696
- setValues(init);
44697
- setSubmitted(false);
44698
- setAttempts(0);
44699
- setShowHint(false);
44700
- };
44701
- if (!resolved) return null;
44702
44721
  const theme = resolved.theme ?? void 0;
44703
44722
  const themeBackground = theme?.background;
44704
44723
  const headerImage = str(resolved.headerImage);
44705
44724
  const hint = str(resolved.hint);
44725
+ const showHint = isComplete && !isWin && attempts >= 2 && Boolean(hint);
44706
44726
  const outputLabel = str(resolved.outputLabel);
44707
44727
  const outputUnit = str(resolved.outputUnit);
44708
44728
  return /* @__PURE__ */ jsx(
@@ -44722,41 +44742,43 @@ function SimulatorBoard({
44722
44742
  ] }) }),
44723
44743
  /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "md", children: [
44724
44744
  /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-muted-foreground", children: t("simulator.parameters") }),
44725
- parameters.map((param) => /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
44726
- /* @__PURE__ */ jsxs(HStack, { justify: "between", align: "center", children: [
44727
- /* @__PURE__ */ jsx(Typography, { variant: "body", weight: "medium", children: param.label }),
44728
- /* @__PURE__ */ jsxs(Badge, { size: "sm", children: [
44729
- values[param.id],
44730
- " ",
44731
- param.unit
44732
- ] })
44733
- ] }),
44734
- /* @__PURE__ */ jsx(
44735
- "input",
44736
- {
44737
- type: "range",
44738
- min: param.min,
44739
- max: param.max,
44740
- step: param.step,
44741
- value: values[param.id],
44742
- onChange: (e) => handleParameterChange(param.id, Number(e.target.value)),
44743
- disabled: submitted,
44744
- className: "w-full accent-foreground"
44745
- }
44746
- ),
44747
- /* @__PURE__ */ jsxs(HStack, { justify: "between", children: [
44748
- /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
44749
- param.min,
44750
- " ",
44751
- param.unit
44745
+ [paramAValue, paramBValue].map(
44746
+ (param, index) => param ? /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
44747
+ /* @__PURE__ */ jsxs(HStack, { justify: "between", align: "center", children: [
44748
+ /* @__PURE__ */ jsx(Typography, { variant: "body", weight: "medium", children: param.label }),
44749
+ /* @__PURE__ */ jsxs(Badge, { size: "sm", children: [
44750
+ sliderValues[index],
44751
+ " ",
44752
+ param.unit
44753
+ ] })
44752
44754
  ] }),
44753
- /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
44754
- param.max,
44755
- " ",
44756
- param.unit
44755
+ /* @__PURE__ */ jsx(
44756
+ "input",
44757
+ {
44758
+ type: "range",
44759
+ min: param.min,
44760
+ max: param.max,
44761
+ step: param.step,
44762
+ value: sliderValues[index],
44763
+ onChange: (e) => handleParameterChange(index, Number(e.target.value)),
44764
+ disabled: isComplete,
44765
+ className: "w-full accent-foreground"
44766
+ }
44767
+ ),
44768
+ /* @__PURE__ */ jsxs(HStack, { justify: "between", children: [
44769
+ /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
44770
+ param.min,
44771
+ " ",
44772
+ param.unit
44773
+ ] }),
44774
+ /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
44775
+ param.max,
44776
+ " ",
44777
+ param.unit
44778
+ ] })
44757
44779
  ] })
44758
- ] })
44759
- ] }, param.id))
44780
+ ] }, param.id ?? index) : null
44781
+ )
44760
44782
  ] }) }),
44761
44783
  /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", align: "center", children: [
44762
44784
  /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-muted-foreground", children: outputLabel }),
@@ -44765,9 +44787,9 @@ function SimulatorBoard({
44765
44787
  " ",
44766
44788
  outputUnit
44767
44789
  ] }),
44768
- submitted && /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
44769
- /* @__PURE__ */ jsx(Icon, { icon: isCorrect ? CheckCircle : XCircle, size: "sm", className: isCorrect ? "text-success" : "text-error" }),
44770
- /* @__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") })
44790
+ isComplete && /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
44791
+ /* @__PURE__ */ jsx(Icon, { icon: isWin ? CheckCircle : XCircle, size: "sm", className: isWin ? "text-success" : "text-error" }),
44792
+ /* @__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") })
44771
44793
  ] }),
44772
44794
  /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
44773
44795
  t("simulator.target"),
@@ -44782,11 +44804,11 @@ function SimulatorBoard({
44782
44804
  ] }) }),
44783
44805
  showHint && hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: hint }) }),
44784
44806
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
44785
- !submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, children: [
44807
+ !isComplete ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleCheck, children: [
44786
44808
  /* @__PURE__ */ jsx(Icon, { icon: Play, size: "sm" }),
44787
44809
  t("simulator.simulate")
44788
- ] }) : !isCorrect ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleReset, children: t("simulator.tryAgain") }) : null,
44789
- /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
44810
+ ] }) : null,
44811
+ /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handlePlayAgain, children: [
44790
44812
  /* @__PURE__ */ jsx(Icon, { icon: RotateCcw, size: "sm" }),
44791
44813
  t("simulator.reset")
44792
44814
  ] })