@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.
@@ -19898,61 +19898,53 @@ var init_Breadcrumb = __esm({
19898
19898
  function BuilderBoard({
19899
19899
  entity,
19900
19900
  completeEvent = "PUZZLE_COMPLETE",
19901
+ placeEvent,
19902
+ checkEvent,
19903
+ playAgainEvent,
19901
19904
  className
19902
19905
  }) {
19903
19906
  const { emit } = useEventBus();
19904
19907
  const { t } = hooks.useTranslate();
19905
19908
  const resolved = boardEntity(entity);
19906
- const [placements, setPlacements] = React91.useState({});
19907
19909
  const [headerError, setHeaderError] = React91.useState(false);
19908
- const [submitted, setSubmitted] = React91.useState(false);
19909
- const [attempts, setAttempts] = React91.useState(0);
19910
- const [showHint, setShowHint] = React91.useState(false);
19910
+ const [selectedComponent, setSelectedComponent] = React91.useState(null);
19911
19911
  const components = Array.isArray(resolved?.components) ? resolved.components : [];
19912
19912
  const slots = Array.isArray(resolved?.slots) ? resolved.slots : [];
19913
+ const placements = {};
19914
+ for (const slot of slots) {
19915
+ if (slot.placedComponentId) placements[slot.id] = slot.placedComponentId;
19916
+ }
19917
+ const attempts = num(resolved?.attempts);
19918
+ const result = str(resolved?.result) || "none";
19919
+ const submitted = result === "win";
19913
19920
  const usedComponentIds = new Set(Object.values(placements));
19914
19921
  const availableComponents = components.filter((c) => !usedComponentIds.has(c.id));
19915
- const [selectedComponent, setSelectedComponent] = React91.useState(null);
19916
- const allPlaced = Object.keys(placements).length === slots.length;
19922
+ const allPlaced = slots.length > 0 && slots.every((s) => Boolean(placements[s.id]));
19917
19923
  const results = submitted ? slots.map((slot) => ({
19918
19924
  slot,
19919
19925
  placed: placements[slot.id],
19920
- correct: placements[slot.id] === slot.acceptsComponentId
19926
+ correct: placements[slot.id] === slot.requiredComponentId
19921
19927
  })) : [];
19922
- const allCorrect = results.length > 0 && results.every((r2) => r2.correct);
19928
+ const showHint = attempts >= 2 && Boolean(str(resolved?.hint));
19923
19929
  const handlePlaceComponent = (slotId) => {
19924
19930
  if (submitted || !selectedComponent) return;
19925
- setPlacements((prev) => ({ ...prev, [slotId]: selectedComponent }));
19931
+ if (placeEvent) emit(`UI:${placeEvent}`, { slotId, componentId: selectedComponent });
19926
19932
  setSelectedComponent(null);
19927
19933
  };
19928
19934
  const handleRemoveFromSlot = (slotId) => {
19929
19935
  if (submitted) return;
19930
- setPlacements((prev) => {
19931
- const next = { ...prev };
19932
- delete next[slotId];
19933
- return next;
19934
- });
19936
+ if (placeEvent) emit(`UI:${placeEvent}`, { slotId, componentId: "" });
19935
19937
  };
19936
- const handleSubmit = React91.useCallback(() => {
19937
- setSubmitted(true);
19938
- setAttempts((a) => a + 1);
19939
- const correct = slots.every((slot) => placements[slot.id] === slot.acceptsComponentId);
19940
- if (correct) {
19938
+ const handleSubmit = () => {
19939
+ if (checkEvent) emit(`UI:${checkEvent}`, {});
19940
+ const solved = slots.length > 0 && slots.every((s) => placements[s.id] === s.requiredComponentId);
19941
+ if (solved && completeEvent) {
19941
19942
  emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
19942
19943
  }
19943
- }, [slots, placements, attempts, completeEvent, emit]);
19944
- const handleReset = () => {
19945
- setSubmitted(false);
19946
- if (attempts >= 2 && str(resolved?.hint)) {
19947
- setShowHint(true);
19948
- }
19949
19944
  };
19950
- const handleFullReset = () => {
19951
- setPlacements({});
19952
- setSubmitted(false);
19945
+ const handlePlayAgain = () => {
19953
19946
  setSelectedComponent(null);
19954
- setAttempts(0);
19955
- setShowHint(false);
19947
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
19956
19948
  };
19957
19949
  const getComponentById = (id) => components.find((c) => c.id === id);
19958
19950
  if (!resolved) return null;
@@ -20002,13 +19994,13 @@ function BuilderBoard({
20002
19994
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-muted-foreground", children: t("builder.blueprint") }),
20003
19995
  /* @__PURE__ */ jsxRuntime.jsx(VStack, { gap: "sm", children: slots.map((slot) => {
20004
19996
  const placedComp = placements[slot.id] ? getComponentById(placements[slot.id]) : null;
20005
- const result = results.find((r2) => r2.slot.id === slot.id);
19997
+ const result2 = results.find((r2) => r2.slot.id === slot.id);
20006
19998
  return /* @__PURE__ */ jsxRuntime.jsxs(
20007
19999
  HStack,
20008
20000
  {
20009
20001
  gap: "sm",
20010
20002
  align: "center",
20011
- className: `p-3 border-2 rounded ${result ? result.correct ? "border-success" : "border-error" : selectedComponent ? "border-dashed border-foreground cursor-pointer" : "border-border"}`,
20003
+ className: `p-3 border-2 rounded ${result2 ? result2.correct ? "border-success" : "border-error" : selectedComponent ? "border-dashed border-foreground cursor-pointer" : "border-border"}`,
20012
20004
  onClick: () => handlePlaceComponent(slot.id),
20013
20005
  children: [
20014
20006
  /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "none", className: "flex-1", children: [
@@ -20023,7 +20015,7 @@ function BuilderBoard({
20023
20015
  ] }) : null,
20024
20016
  placedComp.label
20025
20017
  ] }),
20026
- result && /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: result.correct ? LucideIcons2.CheckCircle : LucideIcons2.XCircle, size: "sm", className: result.correct ? "text-success" : "text-error" })
20018
+ result2 && /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: result2.correct ? LucideIcons2.CheckCircle : LucideIcons2.XCircle, size: "sm", className: result2.correct ? "text-success" : "text-error" })
20027
20019
  ] }) : /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("builder.empty") })
20028
20020
  ]
20029
20021
  },
@@ -20032,16 +20024,16 @@ function BuilderBoard({
20032
20024
  }) })
20033
20025
  ] }) }),
20034
20026
  submitted && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", align: "center", children: [
20035
- /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: allCorrect ? LucideIcons2.CheckCircle : LucideIcons2.XCircle, size: "lg", className: allCorrect ? "text-success" : "text-error" }),
20036
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", weight: "bold", children: allCorrect ? str(resolved.successMessage) || t("builder.success") : str(resolved.failMessage) || t("builder.incorrect") })
20027
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.CheckCircle, size: "lg", className: "text-success" }),
20028
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", weight: "bold", children: str(resolved.successMessage) || t("builder.success") })
20037
20029
  ] }) }),
20038
20030
  showHint && hint && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: hint }) }),
20039
20031
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", justify: "center", children: [
20040
- !submitted ? /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allPlaced, children: [
20032
+ !submitted && /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allPlaced, children: [
20041
20033
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.Wrench, size: "sm" }),
20042
20034
  t("builder.build")
20043
- ] }) : !allCorrect ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleReset, children: t("builder.tryAgain") }) : null,
20044
- /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
20035
+ ] }),
20036
+ /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "secondary", onClick: handlePlayAgain, children: [
20045
20037
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.RotateCcw, size: "sm" }),
20046
20038
  t("builder.reset")
20047
20039
  ] })
@@ -23497,57 +23489,84 @@ var init_ChartLegend = __esm({
23497
23489
  function ClassifierBoard({
23498
23490
  entity,
23499
23491
  completeEvent = "PUZZLE_COMPLETE",
23492
+ assignEvent,
23493
+ checkEvent,
23494
+ playAgainEvent,
23500
23495
  className
23501
23496
  }) {
23502
23497
  const { emit } = useEventBus();
23503
23498
  const { t } = hooks.useTranslate();
23504
23499
  const resolved = boardEntity(entity);
23505
- const [assignments, setAssignments] = React91.useState({});
23500
+ const [localAssignments, setLocalAssignments] = React91.useState({});
23506
23501
  const [headerError, setHeaderError] = React91.useState(false);
23507
- const [submitted, setSubmitted] = React91.useState(false);
23508
- const [attempts, setAttempts] = React91.useState(0);
23502
+ const [localSubmitted, setLocalSubmitted] = React91.useState(false);
23503
+ const [localAttempts, setLocalAttempts] = React91.useState(0);
23509
23504
  const [showHint, setShowHint] = React91.useState(false);
23510
23505
  const items = Array.isArray(resolved?.items) ? resolved.items : [];
23511
23506
  const categories = Array.isArray(resolved?.categories) ? resolved.categories : [];
23507
+ const entityResult = str(resolved?.result);
23508
+ const entityDrivesResult = entityResult.length > 0;
23509
+ const entityHasAssignments = items.some((item) => item.assignedCategory != null);
23510
+ const assignments = entityHasAssignments ? items.reduce((acc, item) => {
23511
+ if (item.assignedCategory != null) acc[item.id] = item.assignedCategory;
23512
+ return acc;
23513
+ }, {}) : localAssignments;
23514
+ const attempts = entityDrivesResult ? num(resolved?.attempts) : localAttempts;
23515
+ const submitted = entityDrivesResult || localSubmitted;
23512
23516
  const unassignedItems = items.filter((item) => !assignments[item.id]);
23513
- const allAssigned = Object.keys(assignments).length === items.length;
23517
+ const allAssigned = items.length > 0 && Object.keys(assignments).length === items.length;
23514
23518
  const results = submitted ? items.map((item) => ({
23515
23519
  item,
23516
23520
  assigned: assignments[item.id],
23517
23521
  correct: assignments[item.id] === item.correctCategory
23518
23522
  })) : [];
23519
- const allCorrect = results.length > 0 && results.every((r2) => r2.correct);
23523
+ const allCorrect = entityDrivesResult ? entityResult === "success" : results.length > 0 && results.every((r2) => r2.correct);
23520
23524
  const correctCount = results.filter((r2) => r2.correct).length;
23521
23525
  const handleAssign = (itemId, categoryId) => {
23522
23526
  if (submitted) return;
23523
- setAssignments((prev) => ({ ...prev, [itemId]: categoryId }));
23527
+ if (assignEvent) {
23528
+ emit(`UI:${assignEvent}`, { itemId, categoryId });
23529
+ }
23530
+ if (!entityHasAssignments) {
23531
+ setLocalAssignments((prev) => ({ ...prev, [itemId]: categoryId }));
23532
+ }
23524
23533
  };
23525
23534
  const handleUnassign = (itemId) => {
23526
23535
  if (submitted) return;
23527
- setAssignments((prev) => {
23528
- const next = { ...prev };
23529
- delete next[itemId];
23530
- return next;
23531
- });
23536
+ if (!entityHasAssignments) {
23537
+ setLocalAssignments((prev) => {
23538
+ const next = { ...prev };
23539
+ delete next[itemId];
23540
+ return next;
23541
+ });
23542
+ }
23532
23543
  };
23533
23544
  const handleSubmit = React91.useCallback(() => {
23534
- setSubmitted(true);
23535
- setAttempts((a) => a + 1);
23536
- const correct = items.every((item) => assignments[item.id] === item.correctCategory);
23537
- if (correct) {
23538
- emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
23545
+ if (checkEvent) {
23546
+ emit(`UI:${checkEvent}`, {});
23547
+ }
23548
+ if (!entityDrivesResult) {
23549
+ setLocalSubmitted(true);
23550
+ setLocalAttempts((a) => a + 1);
23551
+ const correct = items.every((item) => assignments[item.id] === item.correctCategory);
23552
+ if (correct) {
23553
+ emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
23554
+ }
23539
23555
  }
23540
- }, [items, assignments, attempts, completeEvent, emit]);
23556
+ }, [checkEvent, entityDrivesResult, items, assignments, attempts, completeEvent, emit]);
23541
23557
  const handleReset = () => {
23542
- setSubmitted(false);
23558
+ if (!entityDrivesResult) setLocalSubmitted(false);
23543
23559
  if (attempts >= 2 && str(resolved?.hint)) {
23544
23560
  setShowHint(true);
23545
23561
  }
23546
23562
  };
23547
23563
  const handleFullReset = () => {
23548
- setAssignments({});
23549
- setSubmitted(false);
23550
- setAttempts(0);
23564
+ if (playAgainEvent) {
23565
+ emit(`UI:${playAgainEvent}`, {});
23566
+ }
23567
+ setLocalAssignments({});
23568
+ setLocalSubmitted(false);
23569
+ setLocalAttempts(0);
23551
23570
  setShowHint(false);
23552
23571
  };
23553
23572
  if (!resolved) return null;
@@ -31430,13 +31449,13 @@ var init_MapView = __esm({
31430
31449
  shadowSize: [41, 41]
31431
31450
  });
31432
31451
  L.Marker.prototype.options.icon = defaultIcon;
31433
- const { useEffect: useEffect77, useRef: useRef69, useCallback: useCallback119, useState: useState115 } = React91__namespace.default;
31452
+ const { useEffect: useEffect78, useRef: useRef70, useCallback: useCallback117, useState: useState115 } = React91__namespace.default;
31434
31453
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
31435
31454
  const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
31436
31455
  function MapUpdater({ centerLat, centerLng, zoom }) {
31437
31456
  const map = useMap();
31438
- const prevRef = useRef69({ centerLat, centerLng, zoom });
31439
- useEffect77(() => {
31457
+ const prevRef = useRef70({ centerLat, centerLng, zoom });
31458
+ useEffect78(() => {
31440
31459
  const prev = prevRef.current;
31441
31460
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
31442
31461
  map.setView([centerLat, centerLng], zoom);
@@ -31447,7 +31466,7 @@ var init_MapView = __esm({
31447
31466
  }
31448
31467
  function MapClickHandler({ onMapClick }) {
31449
31468
  const map = useMap();
31450
- useEffect77(() => {
31469
+ useEffect78(() => {
31451
31470
  if (!onMapClick) return;
31452
31471
  const handler = (e) => {
31453
31472
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -31476,7 +31495,7 @@ var init_MapView = __esm({
31476
31495
  }) {
31477
31496
  const eventBus = useEventBus3();
31478
31497
  const [clickedPosition, setClickedPosition] = useState115(null);
31479
- const handleMapClick = useCallback119((lat, lng) => {
31498
+ const handleMapClick = useCallback117((lat, lng) => {
31480
31499
  if (showClickedPin) {
31481
31500
  setClickedPosition({ lat, lng });
31482
31501
  }
@@ -31485,7 +31504,7 @@ var init_MapView = __esm({
31485
31504
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
31486
31505
  }
31487
31506
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
31488
- const handleMarkerClick = useCallback119((marker) => {
31507
+ const handleMarkerClick = useCallback117((marker) => {
31489
31508
  onMarkerClick?.(marker);
31490
31509
  if (markerClickEvent) {
31491
31510
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -40298,51 +40317,52 @@ var init_DataTable = __esm({
40298
40317
  function DebuggerBoard({
40299
40318
  entity,
40300
40319
  completeEvent = "PUZZLE_COMPLETE",
40320
+ toggleFlagEvent,
40321
+ checkEvent,
40322
+ playAgainEvent,
40301
40323
  className
40302
40324
  }) {
40303
40325
  const { emit } = useEventBus();
40304
40326
  const { t } = hooks.useTranslate();
40305
40327
  const resolved = boardEntity(entity);
40306
- const [flaggedLines, setFlaggedLines] = React91.useState(/* @__PURE__ */ new Set());
40307
40328
  const [headerError, setHeaderError] = React91.useState(false);
40308
- const [submitted, setSubmitted] = React91.useState(false);
40309
- const [attempts, setAttempts] = React91.useState(0);
40310
40329
  const [showHint, setShowHint] = React91.useState(false);
40330
+ const lines = Array.isArray(resolved?.lines) ? resolved.lines : [];
40331
+ const result = resolved?.result ?? null;
40332
+ const attempts = num(resolved?.attempts);
40333
+ const submitted = result != null;
40334
+ const bugLines = lines.filter((l) => l.isBug);
40335
+ const flaggedLines = lines.filter((l) => l.isFlagged);
40336
+ const correctFlags = lines.filter((l) => l.isBug && l.isFlagged);
40337
+ const falseFlags = lines.filter((l) => !l.isBug && l.isFlagged);
40338
+ const allCorrect = result === "win";
40311
40339
  const toggleLine = (lineId) => {
40312
40340
  if (submitted) return;
40313
- setFlaggedLines((prev) => {
40314
- const next = new Set(prev);
40315
- if (next.has(lineId)) {
40316
- next.delete(lineId);
40317
- } else {
40318
- next.add(lineId);
40319
- }
40320
- return next;
40321
- });
40341
+ if (toggleFlagEvent) {
40342
+ emit(`UI:${toggleFlagEvent}`, { lineId });
40343
+ }
40322
40344
  };
40323
- const lines = Array.isArray(resolved?.lines) ? resolved.lines : [];
40324
- const bugLines = lines.filter((l) => l.isBug);
40325
- const correctFlags = lines.filter((l) => l.isBug && flaggedLines.has(l.id));
40326
- const falseFlags = lines.filter((l) => !l.isBug && flaggedLines.has(l.id));
40327
- const allCorrect = submitted && correctFlags.length === bugLines.length && falseFlags.length === 0;
40328
40345
  const handleSubmit = React91.useCallback(() => {
40329
- setSubmitted(true);
40330
- setAttempts((a) => a + 1);
40346
+ if (checkEvent) {
40347
+ emit(`UI:${checkEvent}`, {});
40348
+ }
40331
40349
  const correct = correctFlags.length === bugLines.length && falseFlags.length === 0;
40332
40350
  if (correct) {
40333
40351
  emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
40334
40352
  }
40335
- }, [correctFlags.length, bugLines.length, falseFlags.length, attempts, completeEvent, emit]);
40353
+ }, [checkEvent, correctFlags.length, bugLines.length, falseFlags.length, attempts, completeEvent, emit]);
40336
40354
  const handleReset = () => {
40337
- setSubmitted(false);
40355
+ if (playAgainEvent) {
40356
+ emit(`UI:${playAgainEvent}`, {});
40357
+ }
40338
40358
  if (attempts >= 2 && str(resolved?.hint)) {
40339
40359
  setShowHint(true);
40340
40360
  }
40341
40361
  };
40342
40362
  const handleFullReset = () => {
40343
- setFlaggedLines(/* @__PURE__ */ new Set());
40344
- setSubmitted(false);
40345
- setAttempts(0);
40363
+ if (playAgainEvent) {
40364
+ emit(`UI:${playAgainEvent}`, {});
40365
+ }
40346
40366
  setShowHint(false);
40347
40367
  };
40348
40368
  if (!resolved) return null;
@@ -40370,7 +40390,7 @@ function DebuggerBoard({
40370
40390
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(num(resolved.bugCount)) }) })
40371
40391
  ] }) }),
40372
40392
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-0 overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(VStack, { gap: "none", children: lines.map((line, i) => {
40373
- const isFlagged = flaggedLines.has(line.id);
40393
+ const isFlagged = !!line.isFlagged;
40374
40394
  let lineStyle = "";
40375
40395
  if (submitted) {
40376
40396
  if (line.isBug && isFlagged) lineStyle = "bg-success/10";
@@ -40404,9 +40424,9 @@ function DebuggerBoard({
40404
40424
  /* @__PURE__ */ jsxRuntime.jsx(
40405
40425
  Icon,
40406
40426
  {
40407
- icon: flaggedLines.has(line.id) ? LucideIcons2.CheckCircle : LucideIcons2.XCircle,
40427
+ icon: line.isFlagged ? LucideIcons2.CheckCircle : LucideIcons2.XCircle,
40408
40428
  size: "xs",
40409
- className: flaggedLines.has(line.id) ? "text-success mt-0.5" : "text-warning mt-0.5"
40429
+ className: line.isFlagged ? "text-success mt-0.5" : "text-warning mt-0.5"
40410
40430
  }
40411
40431
  ),
40412
40432
  /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "none", children: [
@@ -40417,7 +40437,7 @@ function DebuggerBoard({
40417
40437
  ] }) }),
40418
40438
  showHint && hint && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: hint }) }),
40419
40439
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", justify: "center", children: [
40420
- !submitted ? /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.size === 0, children: [
40440
+ !submitted ? /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.length === 0, children: [
40421
40441
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.Send, size: "sm" }),
40422
40442
  t("debugger.submit")
40423
40443
  ] }) : !allCorrect ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleReset, children: t("debugger.tryAgain") }) : null,
@@ -44181,6 +44201,9 @@ function getOpponentAction(strategy, actions, history) {
44181
44201
  function NegotiatorBoard({
44182
44202
  entity,
44183
44203
  completeEvent = "PUZZLE_COMPLETE",
44204
+ playRoundEvent,
44205
+ finishEvent,
44206
+ playAgainEvent,
44184
44207
  className
44185
44208
  }) {
44186
44209
  const { emit } = useEventBus();
@@ -44189,13 +44212,14 @@ function NegotiatorBoard({
44189
44212
  const [history, setHistory] = React91.useState([]);
44190
44213
  const [headerError, setHeaderError] = React91.useState(false);
44191
44214
  const [showHint, setShowHint] = React91.useState(false);
44192
- const totalRounds = num(resolved?.totalRounds);
44215
+ const totalRounds = num(resolved?.maxRounds);
44193
44216
  const targetScore = num(resolved?.targetScore);
44194
- const currentRound = history.length;
44195
- const isComplete = currentRound >= totalRounds;
44196
- const playerTotal = history.reduce((s, r2) => s + r2.playerPayoff, 0);
44217
+ const currentRound = num(resolved?.round);
44218
+ const result = str(resolved?.result) || "none";
44219
+ const playerTotal = num(resolved?.score);
44220
+ const isComplete = result !== "none" || totalRounds > 0 && currentRound >= totalRounds;
44221
+ const won = result === "win";
44197
44222
  const opponentTotal = history.reduce((s, r2) => s + r2.opponentPayoff, 0);
44198
- const won = isComplete && playerTotal >= targetScore;
44199
44223
  const actions = Array.isArray(resolved?.actions) ? resolved.actions : [];
44200
44224
  const payoffMatrix = Array.isArray(resolved?.payoffMatrix) ? resolved.payoffMatrix : [];
44201
44225
  const handleAction = React91.useCallback((actionId) => {
@@ -44204,29 +44228,45 @@ function NegotiatorBoard({
44204
44228
  const payoff = payoffMatrix.find(
44205
44229
  (p2) => p2.playerAction === actionId && p2.opponentAction === opponentAction
44206
44230
  );
44207
- const result = {
44208
- round: currentRound + 1,
44209
- playerAction: actionId,
44210
- opponentAction,
44211
- playerPayoff: payoff?.playerPayoff ?? 0,
44212
- opponentPayoff: payoff?.opponentPayoff ?? 0
44213
- };
44214
- const newHistory = [...history, result];
44215
- setHistory(newHistory);
44216
- if (newHistory.length >= totalRounds) {
44217
- const total = newHistory.reduce((s, r2) => s + r2.playerPayoff, 0);
44218
- if (total >= targetScore) {
44219
- emit(`UI:${completeEvent}`, { success: true, score: total });
44220
- }
44221
- if (newHistory.length >= 3 && str(resolved?.hint)) {
44231
+ const playerPayoff = payoff?.playerPayoff ?? 0;
44232
+ setHistory((prev) => [
44233
+ ...prev,
44234
+ {
44235
+ round: prev.length + 1,
44236
+ playerAction: actionId,
44237
+ opponentAction,
44238
+ playerPayoff,
44239
+ opponentPayoff: payoff?.opponentPayoff ?? 0
44240
+ }
44241
+ ]);
44242
+ if (playRoundEvent) {
44243
+ emit(`UI:${playRoundEvent}`, { playerAction: actionId, payoff: playerPayoff });
44244
+ }
44245
+ if (totalRounds > 0 && currentRound + 1 >= totalRounds) {
44246
+ if (finishEvent) {
44247
+ emit(`UI:${finishEvent}`, {});
44248
+ }
44249
+ if (str(resolved?.hint)) {
44222
44250
  setShowHint(true);
44223
44251
  }
44224
44252
  }
44225
- }, [isComplete, resolved, totalRounds, targetScore, actions, payoffMatrix, history, currentRound, completeEvent, emit]);
44226
- const handleReset = () => {
44253
+ }, [isComplete, resolved, totalRounds, currentRound, actions, payoffMatrix, history, playRoundEvent, finishEvent, emit]);
44254
+ const handleReset = React91.useCallback(() => {
44227
44255
  setHistory([]);
44228
44256
  setShowHint(false);
44229
- };
44257
+ if (playAgainEvent) {
44258
+ emit(`UI:${playAgainEvent}`, {});
44259
+ }
44260
+ }, [playAgainEvent, emit]);
44261
+ const completedRef = React91.useRef(false);
44262
+ React91.useEffect(() => {
44263
+ if (result === "win" && !completedRef.current) {
44264
+ completedRef.current = true;
44265
+ emit(`UI:${completeEvent}`, { success: true, score: playerTotal });
44266
+ } else if (result === "none") {
44267
+ completedRef.current = false;
44268
+ }
44269
+ }, [result, playerTotal, completeEvent, emit]);
44230
44270
  const getActionLabel = (id) => actions.find((a) => a.id === id)?.label ?? id;
44231
44271
  if (!resolved) return null;
44232
44272
  const theme = resolved.theme ?? void 0;
@@ -47192,67 +47232,47 @@ var init_SimulationGraph = __esm({
47192
47232
  function SimulatorBoard({
47193
47233
  entity,
47194
47234
  completeEvent = "PUZZLE_COMPLETE",
47235
+ setAEvent,
47236
+ setBEvent,
47237
+ checkEvent,
47238
+ playAgainEvent,
47195
47239
  className
47196
47240
  }) {
47197
47241
  const { emit } = useEventBus();
47198
47242
  const { t } = hooks.useTranslate();
47199
47243
  const resolved = boardEntity(entity);
47200
47244
  const parameters = Array.isArray(resolved?.parameters) ? resolved.parameters : [];
47201
- const [values, setValues] = React91.useState(() => {
47202
- const init = {};
47203
- for (const p2 of parameters) {
47204
- init[p2.id] = p2.initial;
47205
- }
47206
- return init;
47207
- });
47208
47245
  const [headerError, setHeaderError] = React91.useState(false);
47209
- const [submitted, setSubmitted] = React91.useState(false);
47210
- const [attempts, setAttempts] = React91.useState(0);
47211
- const [showHint, setShowHint] = React91.useState(false);
47212
- const computeOutput = React91.useCallback((params) => {
47213
- try {
47214
- const fn = new Function("params", `return (${str(resolved?.computeExpression)})`);
47215
- return fn(params);
47216
- } catch {
47217
- return 0;
47218
- }
47219
- }, [resolved?.computeExpression]);
47220
- const output = React91.useMemo(() => computeOutput(values) ?? 0, [computeOutput, values]);
47221
- const targetValue = num(resolved?.targetValue);
47222
- const targetTolerance = num(resolved?.targetTolerance);
47223
- const isCorrect = Math.abs(output - targetValue) <= targetTolerance;
47224
- const handleParameterChange = (id, value) => {
47225
- if (submitted) return;
47226
- setValues((prev) => ({ ...prev, [id]: value }));
47246
+ if (!resolved) return null;
47247
+ const paramA = num(resolved.paramA);
47248
+ const paramB = num(resolved.paramB);
47249
+ const output = num(resolved.output);
47250
+ const targetValue = num(resolved.target);
47251
+ const targetTolerance = num(resolved.tolerance);
47252
+ const attempts = num(resolved.attempts);
47253
+ const result = str(resolved.result);
47254
+ const isWin = result === "win";
47255
+ const isComplete = result !== "none" && result !== "";
47256
+ const paramAValue = parameters[0];
47257
+ const paramBValue = parameters[1];
47258
+ const sliderValues = [paramA, paramB];
47259
+ const sliderEvents = [setAEvent, setBEvent];
47260
+ const handleParameterChange = (index, value) => {
47261
+ if (isComplete) return;
47262
+ const ev = sliderEvents[index];
47263
+ if (ev) emit(`UI:${ev}`, { value });
47227
47264
  };
47228
- const handleSubmit = () => {
47229
- setSubmitted(true);
47230
- setAttempts((a) => a + 1);
47231
- if (isCorrect) {
47232
- emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
47233
- }
47265
+ const handleCheck = () => {
47266
+ if (checkEvent) emit(`UI:${checkEvent}`, {});
47234
47267
  };
47235
- const handleReset = () => {
47236
- setSubmitted(false);
47237
- if (attempts >= 2 && str(resolved?.hint)) {
47238
- setShowHint(true);
47239
- }
47268
+ const handlePlayAgain = () => {
47269
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
47240
47270
  };
47241
- const handleFullReset = () => {
47242
- const init = {};
47243
- for (const p2 of parameters) {
47244
- init[p2.id] = p2.initial;
47245
- }
47246
- setValues(init);
47247
- setSubmitted(false);
47248
- setAttempts(0);
47249
- setShowHint(false);
47250
- };
47251
- if (!resolved) return null;
47252
47271
  const theme = resolved.theme ?? void 0;
47253
47272
  const themeBackground = theme?.background;
47254
47273
  const headerImage = str(resolved.headerImage);
47255
47274
  const hint = str(resolved.hint);
47275
+ const showHint = isComplete && !isWin && attempts >= 2 && Boolean(hint);
47256
47276
  const outputLabel = str(resolved.outputLabel);
47257
47277
  const outputUnit = str(resolved.outputUnit);
47258
47278
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -47272,41 +47292,43 @@ function SimulatorBoard({
47272
47292
  ] }) }),
47273
47293
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "md", children: [
47274
47294
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-muted-foreground", children: t("simulator.parameters") }),
47275
- parameters.map((param) => /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", children: [
47276
- /* @__PURE__ */ jsxRuntime.jsxs(HStack, { justify: "between", align: "center", children: [
47277
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", weight: "medium", children: param.label }),
47278
- /* @__PURE__ */ jsxRuntime.jsxs(Badge, { size: "sm", children: [
47279
- values[param.id],
47280
- " ",
47281
- param.unit
47282
- ] })
47283
- ] }),
47284
- /* @__PURE__ */ jsxRuntime.jsx(
47285
- "input",
47286
- {
47287
- type: "range",
47288
- min: param.min,
47289
- max: param.max,
47290
- step: param.step,
47291
- value: values[param.id],
47292
- onChange: (e) => handleParameterChange(param.id, Number(e.target.value)),
47293
- disabled: submitted,
47294
- className: "w-full accent-foreground"
47295
- }
47296
- ),
47297
- /* @__PURE__ */ jsxRuntime.jsxs(HStack, { justify: "between", children: [
47298
- /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
47299
- param.min,
47300
- " ",
47301
- param.unit
47295
+ [paramAValue, paramBValue].map(
47296
+ (param, index) => param ? /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", children: [
47297
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { justify: "between", align: "center", children: [
47298
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", weight: "medium", children: param.label }),
47299
+ /* @__PURE__ */ jsxRuntime.jsxs(Badge, { size: "sm", children: [
47300
+ sliderValues[index],
47301
+ " ",
47302
+ param.unit
47303
+ ] })
47302
47304
  ] }),
47303
- /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
47304
- param.max,
47305
- " ",
47306
- param.unit
47305
+ /* @__PURE__ */ jsxRuntime.jsx(
47306
+ "input",
47307
+ {
47308
+ type: "range",
47309
+ min: param.min,
47310
+ max: param.max,
47311
+ step: param.step,
47312
+ value: sliderValues[index],
47313
+ onChange: (e) => handleParameterChange(index, Number(e.target.value)),
47314
+ disabled: isComplete,
47315
+ className: "w-full accent-foreground"
47316
+ }
47317
+ ),
47318
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { justify: "between", children: [
47319
+ /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
47320
+ param.min,
47321
+ " ",
47322
+ param.unit
47323
+ ] }),
47324
+ /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
47325
+ param.max,
47326
+ " ",
47327
+ param.unit
47328
+ ] })
47307
47329
  ] })
47308
- ] })
47309
- ] }, param.id))
47330
+ ] }, param.id ?? index) : null
47331
+ )
47310
47332
  ] }) }),
47311
47333
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", align: "center", children: [
47312
47334
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-muted-foreground", children: outputLabel }),
@@ -47315,9 +47337,9 @@ function SimulatorBoard({
47315
47337
  " ",
47316
47338
  outputUnit
47317
47339
  ] }),
47318
- submitted && /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "center", children: [
47319
- /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: isCorrect ? LucideIcons2.CheckCircle : LucideIcons2.XCircle, size: "sm", className: isCorrect ? "text-success" : "text-error" }),
47320
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", className: isCorrect ? "text-success" : "text-error", children: isCorrect ? str(resolved.successMessage) || t("simulator.correct") : str(resolved.failMessage) || t("simulator.incorrect") })
47340
+ isComplete && /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "center", children: [
47341
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: isWin ? LucideIcons2.CheckCircle : LucideIcons2.XCircle, size: "sm", className: isWin ? "text-success" : "text-error" }),
47342
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", className: isWin ? "text-success" : "text-error", children: isWin ? str(resolved.successMessage) || t("simulator.correct") : str(resolved.failMessage) || t("simulator.incorrect") })
47321
47343
  ] }),
47322
47344
  /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
47323
47345
  t("simulator.target"),
@@ -47332,11 +47354,11 @@ function SimulatorBoard({
47332
47354
  ] }) }),
47333
47355
  showHint && hint && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: hint }) }),
47334
47356
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", justify: "center", children: [
47335
- !submitted ? /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, children: [
47357
+ !isComplete ? /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleCheck, children: [
47336
47358
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.Play, size: "sm" }),
47337
47359
  t("simulator.simulate")
47338
- ] }) : !isCorrect ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleReset, children: t("simulator.tryAgain") }) : null,
47339
- /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
47360
+ ] }) : null,
47361
+ /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "secondary", onClick: handlePlayAgain, children: [
47340
47362
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.RotateCcw, size: "sm" }),
47341
47363
  t("simulator.reset")
47342
47364
  ] })