@xom11/whiteboard 0.7.0 → 0.9.1

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.
Files changed (49) hide show
  1. package/README.md +51 -1
  2. package/dist/chunk-74VEEZBV.mjs +619 -0
  3. package/dist/chunk-74VEEZBV.mjs.map +1 -0
  4. package/dist/chunk-DU2NFHRR.mjs +103 -0
  5. package/dist/chunk-DU2NFHRR.mjs.map +1 -0
  6. package/dist/{chunk-SHFOGORM.mjs → chunk-DU3RHKT5.mjs} +4 -4
  7. package/dist/{chunk-SHFOGORM.mjs.map → chunk-DU3RHKT5.mjs.map} +1 -1
  8. package/dist/{chunk-HYXFHEDJ.mjs → chunk-IUVV52HO.mjs} +22 -7
  9. package/dist/chunk-IUVV52HO.mjs.map +1 -0
  10. package/dist/{chunk-BJX4YNA5.mjs → chunk-KEYZ5EZT.mjs} +26 -9
  11. package/dist/chunk-KEYZ5EZT.mjs.map +1 -0
  12. package/dist/{chunk-LPM4MM45.mjs → chunk-SBDMF4NQ.mjs} +3 -2
  13. package/dist/chunk-SBDMF4NQ.mjs.map +1 -0
  14. package/dist/{chunk-3SSQKRRO.mjs → chunk-ZVN356JZ.mjs} +4 -4
  15. package/dist/{chunk-3SSQKRRO.mjs.map → chunk-ZVN356JZ.mjs.map} +1 -1
  16. package/dist/geometry-2d.js +250 -218
  17. package/dist/geometry-2d.js.map +1 -1
  18. package/dist/geometry-2d.mjs +2 -2
  19. package/dist/geometry-3d.d.mts +1 -1
  20. package/dist/geometry-3d.d.ts +1 -1
  21. package/dist/geometry-3d.js +3276 -1201
  22. package/dist/geometry-3d.js.map +1 -1
  23. package/dist/geometry-3d.mjs +3 -2
  24. package/dist/graph-2d.js +360 -66
  25. package/dist/graph-2d.js.map +1 -1
  26. package/dist/graph-2d.mjs +2 -2
  27. package/dist/{host-2QGKMGCT.mjs → host-LZH2FZ2N.mjs} +3 -3
  28. package/dist/{host-2QGKMGCT.mjs.map → host-LZH2FZ2N.mjs.map} +1 -1
  29. package/dist/host-PIIDSMVE.mjs +3187 -0
  30. package/dist/host-PIIDSMVE.mjs.map +1 -0
  31. package/dist/{host-T2W6R6SO.mjs → host-VDNAJMLC.mjs} +221 -216
  32. package/dist/host-VDNAJMLC.mjs.map +1 -0
  33. package/dist/index.d.mts +6 -5
  34. package/dist/index.d.ts +6 -5
  35. package/dist/index.js +4365 -1821
  36. package/dist/index.js.map +1 -1
  37. package/dist/index.mjs +246 -102
  38. package/dist/index.mjs.map +1 -1
  39. package/package.json +6 -6
  40. package/dist/chunk-BJX4YNA5.mjs.map +0 -1
  41. package/dist/chunk-DJTBZEAR.mjs +0 -25
  42. package/dist/chunk-DJTBZEAR.mjs.map +0 -1
  43. package/dist/chunk-HM7RIXJE.mjs +0 -331
  44. package/dist/chunk-HM7RIXJE.mjs.map +0 -1
  45. package/dist/chunk-HYXFHEDJ.mjs.map +0 -1
  46. package/dist/chunk-LPM4MM45.mjs.map +0 -1
  47. package/dist/host-T2W6R6SO.mjs.map +0 -1
  48. package/dist/host-XUFON6CQ.mjs +0 -1422
  49. package/dist/host-XUFON6CQ.mjs.map +0 -1
@@ -139,12 +139,36 @@ var init_serialize = __esm({
139
139
  }
140
140
  });
141
141
 
142
+ // src/stamps/shared/safeJsx.ts
143
+ function safeJsx(label, fn, fallback) {
144
+ try {
145
+ return fn();
146
+ } catch (err) {
147
+ if (isDev) {
148
+ console.warn("[whiteboard:jsxgraph]", label, err);
149
+ }
150
+ return fallback;
151
+ }
152
+ }
153
+ var isDev;
154
+ var init_safeJsx = __esm({
155
+ "src/stamps/shared/safeJsx.ts"() {
156
+ isDev = (() => {
157
+ try {
158
+ return typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
159
+ } catch {
160
+ return false;
161
+ }
162
+ })();
163
+ }
164
+ });
165
+
142
166
  // src/stamps/geometry-2d/render.ts
143
167
  async function renderGeometrySvgFromState(jsonState) {
144
168
  const parsed = JSON.parse(jsonState);
145
169
  const palette = paletteFor(false);
146
170
  const JXG = (await import('jsxgraph')).default;
147
- try {
171
+ safeJsx("render.applyOptions", () => {
148
172
  const opts = JXG.Options;
149
173
  if (opts) {
150
174
  opts.text = opts.text || {};
@@ -161,8 +185,7 @@ async function renderGeometrySvgFromState(jsonState) {
161
185
  opts.grid = opts.grid || {};
162
186
  opts.grid.strokeColor = palette.grid;
163
187
  }
164
- } catch {
165
- }
188
+ });
166
189
  const container = document.createElement("div");
167
190
  const containerId = "jxg_offscreen_" + Date.now() + "_" + Math.random().toString(36).slice(2, 8);
168
191
  container.id = containerId;
@@ -182,10 +205,9 @@ async function renderGeometrySvgFromState(jsonState) {
182
205
  board.update();
183
206
  return renderGeometryToSvg(container);
184
207
  } finally {
185
- try {
208
+ safeJsx("render.freeBoard", () => {
186
209
  if (board) JXG.JSXGraph.freeBoard(board);
187
- } catch {
188
- }
210
+ });
189
211
  if (container.parentNode) container.parentNode.removeChild(container);
190
212
  }
191
213
  }
@@ -194,6 +216,7 @@ var init_render = __esm({
194
216
  init_renderInline();
195
217
  init_serialize();
196
218
  init_theme();
219
+ init_safeJsx();
197
220
  }
198
221
  });
199
222
 
@@ -529,14 +552,8 @@ function handleDown(ctx, e) {
529
552
  const tmp1 = ctx.boardRef.current.create("intersection", [a, b, 1], { visible: false, withLabel: false });
530
553
  const d0 = Math.hypot((tmp0.X?.() ?? 0) - x, (tmp0.Y?.() ?? 0) - y);
531
554
  const d1 = Math.hypot((tmp1.X?.() ?? 0) - x, (tmp1.Y?.() ?? 0) - y);
532
- try {
533
- ctx.boardRef.current.removeObject(tmp0);
534
- } catch {
535
- }
536
- try {
537
- ctx.boardRef.current.removeObject(tmp1);
538
- } catch {
539
- }
555
+ safeJsx("handlers.removeObject(intersect.tmp0)", () => ctx.boardRef.current.removeObject(tmp0));
556
+ safeJsx("handlers.removeObject(intersect.tmp1)", () => ctx.boardRef.current.removeObject(tmp1));
540
557
  const idx = d0 <= d1 ? 0 : 1;
541
558
  ctx.create("intersection", [aId, bId, idx], attrs);
542
559
  }
@@ -573,7 +590,7 @@ function handleDown(ctx, e) {
573
590
  })();
574
591
  if (ctx.pendingRef.current.length > 0 && ctx.boardRef.current) {
575
592
  const prev = ctx.pendingRef.current[ctx.pendingRef.current.length - 1];
576
- try {
593
+ safeJsx("handlers.createPreviewSegment", () => {
577
594
  const seg = ctx.boardRef.current.create("segment", [prev, pick2], {
578
595
  strokeColor: "#3b82f6",
579
596
  strokeWidth: 1.5,
@@ -583,8 +600,7 @@ function handleDown(ctx, e) {
583
600
  withLabel: false
584
601
  });
585
602
  ctx.previewSegRef.current.push(seg);
586
- } catch {
587
- }
603
+ });
588
604
  }
589
605
  ctx.pendingRef.current.push(pick2);
590
606
  ctx.setPendingCount(ctx.pendingRef.current.length);
@@ -695,10 +711,7 @@ function handleUp(ctx, e) {
695
711
  if (!sc2) return;
696
712
  const [ex, ey] = sc2;
697
713
  if (mq.rect) {
698
- try {
699
- ctx.boardRef.current?.removeObject(mq.rect);
700
- } catch {
701
- }
714
+ safeJsx("handlers.removeObject(marquee.rect)", () => ctx.boardRef.current?.removeObject(mq.rect));
702
715
  }
703
716
  if (Math.hypot(ex - mq.startSx, ey - mq.startSy) < 4) return;
704
717
  const x1 = Math.min(mq.startSx, ex), x2 = Math.max(mq.startSx, ex);
@@ -731,10 +744,7 @@ function handleUp(ctx, e) {
731
744
  }
732
745
  }
733
746
  ctx.setSelectionTick((tt) => tt + 1);
734
- try {
735
- board.update();
736
- } catch {
737
- }
747
+ safeJsx("handlers.board.update(marquee)", () => board.update());
738
748
  return;
739
749
  }
740
750
  if (t !== "move") return;
@@ -781,12 +791,9 @@ function handleMove(ctx, e) {
781
791
  const [x2u, y2u] = ux2 && ux2.length >= 2 ? [ux2[0], ux2[1]] : toUsr(Math.max(startSx, sx), Math.max(startSy, sy));
782
792
  const rect = ctx.marqueeRef.current.rect;
783
793
  if (rect) {
784
- try {
785
- ctx.boardRef.current.removeObject(rect);
786
- } catch {
787
- }
794
+ safeJsx("handlers.removeObject(marquee.prevRect)", () => ctx.boardRef.current.removeObject(rect));
788
795
  }
789
- try {
796
+ safeJsx("handlers.createMarqueePolygon", () => {
790
797
  ctx.marqueeRef.current.rect = ctx.boardRef.current.create("polygon", [
791
798
  [x1u, y1u],
792
799
  [x2u, y1u],
@@ -801,8 +808,7 @@ function handleMove(ctx, e) {
801
808
  highlight: false,
802
809
  withLabel: false
803
810
  });
804
- } catch {
805
- }
811
+ });
806
812
  }
807
813
  return;
808
814
  }
@@ -812,20 +818,20 @@ function handleMove(ctx, e) {
812
818
  ctx.previewRafRef.current = requestAnimationFrame(() => {
813
819
  ctx.previewRafRef.current = null;
814
820
  if (!ctx.boardRef.current || !ctx.phantomRef.current) return;
815
- try {
821
+ safeJsx("handlers.phantomMove", () => {
816
822
  const coords = ctx.boardRef.current.getUsrCoordsOfMouse(e);
817
823
  const JXG = ctx.jxgRef.current;
818
824
  if (!JXG) return;
819
825
  ctx.phantomRef.current.setPositionDirectly(JXG.COORDS_BY_USER, [coords[0], coords[1]]);
820
826
  ctx.boardRef.current.update();
821
- } catch {
822
- }
827
+ });
823
828
  });
824
829
  }
825
830
  var init_handlers = __esm({
826
831
  "src/stamps/geometry-2d/editor/handlers.ts"() {
827
832
  init_tools();
828
833
  init_transforms();
834
+ init_safeJsx();
829
835
  }
830
836
  });
831
837
  var JSXGraphMiniBoard;
@@ -836,6 +842,7 @@ var init_MiniBoard = __esm({
836
842
  init_tools();
837
843
  init_theme();
838
844
  init_handlers();
845
+ init_safeJsx();
839
846
  JSXGraphMiniBoard = ({ onReady, initialState, isDark }) => {
840
847
  const isDarkRef = react.useRef(!!isDark);
841
848
  isDarkRef.current = !!isDark;
@@ -845,6 +852,7 @@ var init_MiniBoard = __esm({
845
852
  const jxgRef = react.useRef(null);
846
853
  const axisObjsRef = react.useRef({});
847
854
  const creationLogRef = react.useRef([]);
855
+ const redoStackRef = react.useRef([]);
848
856
  const [tool, setTool] = react.useState("move");
849
857
  const toolRef = react.useRef("move");
850
858
  toolRef.current = tool;
@@ -894,13 +902,17 @@ var init_MiniBoard = __esm({
894
902
  return a;
895
903
  });
896
904
  }, []);
905
+ const pushCreationLog = react.useCallback((entry) => {
906
+ creationLogRef.current.push(entry);
907
+ redoStackRef.current = [];
908
+ }, []);
897
909
  const pushLog = react.useCallback(
898
910
  (id, type, args, attrs, obj) => {
899
- creationLogRef.current.push({ id, type, args, attrs });
911
+ pushCreationLog({ id, type, args, attrs });
900
912
  objMapRef.current.set(id, obj);
901
913
  setHistoryTick((t) => t + 1);
902
914
  },
903
- []
915
+ [pushCreationLog]
904
916
  );
905
917
  const create = react.useCallback(
906
918
  (type, args, attrs = {}) => {
@@ -983,16 +995,10 @@ var init_MiniBoard = __esm({
983
995
  if (patch.remove) {
984
996
  const vl = valueLabelsRef.current.get(o);
985
997
  if (vl) {
986
- try {
987
- boardRef.current.removeObject(vl);
988
- } catch {
989
- }
998
+ safeJsx("MiniBoard.removeObject(valueLabel)", () => boardRef.current.removeObject(vl));
990
999
  valueLabelsRef.current.delete(o);
991
1000
  }
992
- try {
993
- boardRef.current.removeObject(o);
994
- } catch {
995
- }
1001
+ safeJsx("MiniBoard.removeObject(target)", () => boardRef.current.removeObject(o));
996
1002
  const board = boardRef.current;
997
1003
  const aliveIds = /* @__PURE__ */ new Set();
998
1004
  for (const [id, obj2] of objMapRef.current.entries()) {
@@ -1017,7 +1023,7 @@ var init_MiniBoard = __esm({
1017
1023
  const targetId = localIdOf(o);
1018
1024
  if (targetId) {
1019
1025
  const id = nextLocalId();
1020
- creationLogRef.current.push({ id, type: "valueLabel", args: [targetId], attrs: {} });
1026
+ pushCreationLog({ id, type: "valueLabel", args: [targetId], attrs: {} });
1021
1027
  objMapRef.current.set(id, txt);
1022
1028
  setHistoryTick((t) => t + 1);
1023
1029
  }
@@ -1026,10 +1032,7 @@ var init_MiniBoard = __esm({
1026
1032
  const txt = valueLabelsRef.current.get(o);
1027
1033
  valueLabelsRef.current.delete(o);
1028
1034
  if (txt) {
1029
- try {
1030
- boardRef.current.removeObject(txt);
1031
- } catch {
1032
- }
1035
+ safeJsx("MiniBoard.removeObject(valueLabel.text)", () => boardRef.current.removeObject(txt));
1033
1036
  const txtId = localIdOf(txt);
1034
1037
  if (txtId) {
1035
1038
  creationLogRef.current = creationLogRef.current.filter((e) => e.id !== txtId);
@@ -1040,10 +1043,7 @@ var init_MiniBoard = __esm({
1040
1043
  }
1041
1044
  }
1042
1045
  if (patch.attrs) {
1043
- try {
1044
- o.setAttribute(patch.attrs);
1045
- } catch {
1046
- }
1046
+ safeJsx("MiniBoard.setAttribute", () => o.setAttribute(patch.attrs));
1047
1047
  const id = localIdOf(o);
1048
1048
  if (id) {
1049
1049
  const entry = creationLogRef.current.find((e) => e.id === id);
@@ -1051,19 +1051,13 @@ var init_MiniBoard = __esm({
1051
1051
  setHistoryTick((t) => t + 1);
1052
1052
  }
1053
1053
  }
1054
- try {
1055
- boardRef.current.update();
1056
- } catch {
1057
- }
1054
+ safeJsx("MiniBoard.board.update(mutate)", () => boardRef.current.update());
1058
1055
  }, [createValueLabelFor, localIdOf, nextLocalId]);
1059
1056
  const clearPreviewSegs = react.useCallback(() => {
1060
1057
  const b = boardRef.current;
1061
1058
  if (!b) return;
1062
1059
  for (const s of previewSegRef.current) {
1063
- try {
1064
- b.removeObject(s);
1065
- } catch {
1066
- }
1060
+ safeJsx("MiniBoard.removeObject(previewSeg)", () => b.removeObject(s));
1067
1061
  }
1068
1062
  previewSegRef.current = [];
1069
1063
  }, []);
@@ -1071,17 +1065,11 @@ var init_MiniBoard = __esm({
1071
1065
  const b = boardRef.current;
1072
1066
  if (!b) return;
1073
1067
  if (previewShapeRef.current) {
1074
- try {
1075
- b.removeObject(previewShapeRef.current);
1076
- } catch {
1077
- }
1068
+ safeJsx("MiniBoard.removeObject(previewShape)", () => b.removeObject(previewShapeRef.current));
1078
1069
  previewShapeRef.current = null;
1079
1070
  }
1080
1071
  if (phantomRef.current) {
1081
- try {
1082
- b.removeObject(phantomRef.current);
1083
- } catch {
1084
- }
1072
+ safeJsx("MiniBoard.removeObject(phantom)", () => b.removeObject(phantomRef.current));
1085
1073
  phantomRef.current = null;
1086
1074
  }
1087
1075
  }, []);
@@ -1093,7 +1081,7 @@ var init_MiniBoard = __esm({
1093
1081
  }, [clearPreviewSegs, removePhantom]);
1094
1082
  const applySelectionStyle = react.useCallback((obj) => {
1095
1083
  if (!obj || selOriginalRef.current.has(obj)) return;
1096
- try {
1084
+ safeJsx("MiniBoard.applySelectionStyle", () => {
1097
1085
  const visProp = obj.visProp ?? {};
1098
1086
  selOriginalRef.current.set(obj, {
1099
1087
  strokeColor: visProp.strokecolor,
@@ -1105,19 +1093,17 @@ var init_MiniBoard = __esm({
1105
1093
  } else {
1106
1094
  obj.setAttribute({ strokeColor: "#06b6d4", strokeWidth: 3 });
1107
1095
  }
1108
- } catch {
1109
- }
1096
+ });
1110
1097
  }, []);
1111
1098
  const restoreSelectionStyle = react.useCallback((obj) => {
1112
1099
  const orig = selOriginalRef.current.get(obj);
1113
1100
  if (!orig) return;
1114
- try {
1101
+ safeJsx("MiniBoard.restoreSelectionStyle", () => {
1115
1102
  const attrs = {};
1116
1103
  if (orig.strokeColor !== void 0) attrs.strokeColor = orig.strokeColor;
1117
1104
  if (orig.strokeWidth !== void 0) attrs.strokeWidth = orig.strokeWidth;
1118
1105
  obj.setAttribute(attrs);
1119
- } catch {
1120
- }
1106
+ });
1121
1107
  selOriginalRef.current.delete(obj);
1122
1108
  }, []);
1123
1109
  const clearSelection = react.useCallback(() => {
@@ -1126,10 +1112,7 @@ var init_MiniBoard = __esm({
1126
1112
  }
1127
1113
  selectedSetRef.current.clear();
1128
1114
  setSelectionTick((t) => t + 1);
1129
- try {
1130
- boardRef.current?.update();
1131
- } catch {
1132
- }
1115
+ safeJsx("MiniBoard.board.update(clearSelection)", () => boardRef.current?.update());
1133
1116
  }, [restoreSelectionStyle]);
1134
1117
  const toggleSelect = react.useCallback((obj, additive) => {
1135
1118
  if (!obj) return;
@@ -1149,10 +1132,7 @@ var init_MiniBoard = __esm({
1149
1132
  }
1150
1133
  }
1151
1134
  setSelectionTick((t) => t + 1);
1152
- try {
1153
- boardRef.current?.update();
1154
- } catch {
1155
- }
1135
+ safeJsx("MiniBoard.board.update(toggleSelect)", () => boardRef.current?.update());
1156
1136
  }, [applySelectionStyle, restoreSelectionStyle]);
1157
1137
  const deleteSelected = react.useCallback(() => {
1158
1138
  const board = boardRef.current;
@@ -1160,10 +1140,7 @@ var init_MiniBoard = __esm({
1160
1140
  if (selectedSetRef.current.size === 0) return;
1161
1141
  for (const o of selectedSetRef.current) selOriginalRef.current.delete(o);
1162
1142
  for (const o of selectedSetRef.current) {
1163
- try {
1164
- board.removeObject(o);
1165
- } catch {
1166
- }
1143
+ safeJsx("MiniBoard.removeObject(selected)", () => board.removeObject(o));
1167
1144
  }
1168
1145
  selectedSetRef.current.clear();
1169
1146
  const aliveIds = /* @__PURE__ */ new Set();
@@ -1236,10 +1213,7 @@ var init_MiniBoard = __esm({
1236
1213
  const b = boardRef.current;
1237
1214
  if (!b) return;
1238
1215
  if (previewShapeRef.current) {
1239
- try {
1240
- b.removeObject(previewShapeRef.current);
1241
- } catch {
1242
- }
1216
+ safeJsx("MiniBoard.removeObject(refreshPreview)", () => b.removeObject(previewShapeRef.current));
1243
1217
  previewShapeRef.current = null;
1244
1218
  }
1245
1219
  const t = toolRef.current;
@@ -1358,7 +1332,7 @@ var init_MiniBoard = __esm({
1358
1332
  }
1359
1333
  case "toggleLabel": {
1360
1334
  const obj = picks[0];
1361
- try {
1335
+ safeJsx("MiniBoard.toggleLabel", () => {
1362
1336
  if (obj.label) {
1363
1337
  const visible = obj.label.visProp.visible !== false;
1364
1338
  obj.label.setAttribute({ visible: !visible });
@@ -1367,23 +1341,21 @@ var init_MiniBoard = __esm({
1367
1341
  obj.setAttribute({ withLabel: !cur });
1368
1342
  }
1369
1343
  boardRef.current.update();
1370
- } catch {
1371
- }
1344
+ });
1372
1345
  break;
1373
1346
  }
1374
1347
  case "toggleVisible": {
1375
1348
  const obj = picks[0];
1376
- try {
1349
+ safeJsx("MiniBoard.toggleVisible", () => {
1377
1350
  const visible = obj.visProp.visible !== false;
1378
1351
  obj.setAttribute({ visible: !visible });
1379
1352
  boardRef.current.update();
1380
- } catch {
1381
- }
1353
+ });
1382
1354
  break;
1383
1355
  }
1384
1356
  case "delete": {
1385
1357
  const obj = picks[0];
1386
- try {
1358
+ safeJsx("MiniBoard.deleteOne", () => {
1387
1359
  boardRef.current.removeObject(obj);
1388
1360
  const board = boardRef.current;
1389
1361
  const aliveIds = /* @__PURE__ */ new Set();
@@ -1398,8 +1370,7 @@ var init_MiniBoard = __esm({
1398
1370
  if (!aliveIds.has(id)) objMapRef.current.delete(id);
1399
1371
  }
1400
1372
  setHistoryTick((t) => t + 1);
1401
- } catch {
1402
- }
1373
+ });
1403
1374
  break;
1404
1375
  }
1405
1376
  }
@@ -1434,7 +1405,7 @@ var init_MiniBoard = __esm({
1434
1405
  }
1435
1406
  const stepId = nextLocalId();
1436
1407
  const stepObj = boardRef.current.create("transform", step.params, step.attrs);
1437
- creationLogRef.current.push({ id: stepId, type: "transform", args: stepLogArgs, attrs: step.attrs });
1408
+ pushCreationLog({ id: stepId, type: "transform", args: stepLogArgs, attrs: step.attrs });
1438
1409
  objMapRef.current.set(stepId, stepObj);
1439
1410
  transformObjs.push(stepObj);
1440
1411
  transformIds.push(stepId);
@@ -1448,7 +1419,7 @@ var init_MiniBoard = __esm({
1448
1419
  const newName = srcName ? `${srcName}'` : nextLabel();
1449
1420
  const attrs = { name: newName, size: 3, color: "#0ea5e9", strokeColor: "#0ea5e9", fillColor: "#0ea5e9" };
1450
1421
  const obj = boardRef.current.create("point", [src, transformParent], attrs);
1451
- creationLogRef.current.push({ id, type: "point", args: [srcId ?? src, transformLogRef], attrs });
1422
+ pushCreationLog({ id, type: "point", args: [srcId ?? src, transformLogRef], attrs });
1452
1423
  objMapRef.current.set(id, obj);
1453
1424
  return obj;
1454
1425
  });
@@ -1479,6 +1450,30 @@ var init_MiniBoard = __esm({
1479
1450
  }
1480
1451
  setHistoryTick((t) => t + 1);
1481
1452
  }, [create, flashWarn, localIdOf, nextLabel, nextLocalId]);
1453
+ const recreateFromLogEntry = react.useCallback((el) => {
1454
+ const board = boardRef.current;
1455
+ if (!board) return false;
1456
+ const idMap = objMapRef.current;
1457
+ const resolved = el.args.map((a) => typeof a === "string" && idMap.has(a) ? idMap.get(a) : a);
1458
+ try {
1459
+ if (el.type === "valueLabel") {
1460
+ const target = resolved[0];
1461
+ if (!target) return false;
1462
+ const txt = createValueLabelFor(target);
1463
+ if (!txt) return false;
1464
+ idMap.set(el.id, txt);
1465
+ valueLabelsRef.current.set(target, txt);
1466
+ return true;
1467
+ }
1468
+ const themedAttrs = resolveAttrColors({ ...el.attrs }, paletteFor(isDarkRef.current));
1469
+ const obj = board.create(el.type, resolved, themedAttrs);
1470
+ idMap.set(el.id, obj);
1471
+ return true;
1472
+ } catch (err) {
1473
+ console.warn("Recreate failed for", el.type, err);
1474
+ return false;
1475
+ }
1476
+ }, [createValueLabelFor]);
1482
1477
  const undoLast = react.useCallback(() => {
1483
1478
  const b = boardRef.current;
1484
1479
  if (!b) return;
@@ -1488,21 +1483,31 @@ var init_MiniBoard = __esm({
1488
1483
  const obj = objMapRef.current.get(last.id);
1489
1484
  objMapRef.current.delete(last.id);
1490
1485
  if (obj) {
1491
- try {
1492
- b.removeObject(obj);
1493
- } catch {
1494
- }
1486
+ safeJsx("MiniBoard.removeObject(undo)", () => b.removeObject(obj));
1495
1487
  clearPending();
1488
+ redoStackRef.current.push(last);
1496
1489
  setHistoryTick((t) => t + 1);
1497
- try {
1498
- b.update();
1499
- } catch {
1500
- }
1490
+ safeJsx("MiniBoard.board.update(undo)", () => b.update());
1501
1491
  return;
1502
1492
  }
1503
1493
  }
1504
1494
  setHistoryTick((t) => t + 1);
1505
1495
  }, [clearPending]);
1496
+ const redoNext = react.useCallback(() => {
1497
+ const b = boardRef.current;
1498
+ if (!b) return;
1499
+ const entry = redoStackRef.current.pop();
1500
+ if (!entry) {
1501
+ setHistoryTick((t) => t + 1);
1502
+ return;
1503
+ }
1504
+ const ok = recreateFromLogEntry(entry);
1505
+ if (ok) {
1506
+ creationLogRef.current.push(entry);
1507
+ }
1508
+ setHistoryTick((t) => t + 1);
1509
+ safeJsx("MiniBoard.board.update(redo)", () => b.update());
1510
+ }, [recreateFromLogEntry]);
1506
1511
  react.useEffect(() => {
1507
1512
  const onKey = (e) => {
1508
1513
  const ae = document.activeElement;
@@ -1514,6 +1519,13 @@ var init_MiniBoard = __esm({
1514
1519
  undoLastRef.current();
1515
1520
  return;
1516
1521
  }
1522
+ if ((e.metaKey || e.ctrlKey) && (e.key.toLowerCase() === "z" && e.shiftKey || e.key.toLowerCase() === "y" && !e.shiftKey)) {
1523
+ if (inField) return;
1524
+ e.preventDefault();
1525
+ e.stopPropagation();
1526
+ redoNextRef.current();
1527
+ return;
1528
+ }
1517
1529
  if (e.key === "Escape" && !inField) {
1518
1530
  if (pendingRef.current.length > 0) {
1519
1531
  e.preventDefault();
@@ -1560,16 +1572,14 @@ var init_MiniBoard = __esm({
1560
1572
  if (!sc) return [];
1561
1573
  const [sx, sy] = sc;
1562
1574
  const list = [];
1563
- try {
1575
+ safeJsx("MiniBoard.objectsAt.loop", () => {
1564
1576
  const objs = b.objectsList || [];
1565
1577
  for (const o of objs) {
1566
- try {
1578
+ safeJsx("MiniBoard.objectsAt.hasPoint", () => {
1567
1579
  if (o.hasPoint && o.hasPoint(sx, sy)) list.push(o);
1568
- } catch {
1569
- }
1580
+ });
1570
1581
  }
1571
- } catch {
1572
- }
1582
+ });
1573
1583
  return list;
1574
1584
  }, [screenCoordsOf]);
1575
1585
  const findNearestPoint = react.useCallback((evt, tolPx = 12) => {
@@ -1579,24 +1589,23 @@ var init_MiniBoard = __esm({
1579
1589
  if (!sc) return null;
1580
1590
  const [sx, sy] = sc;
1581
1591
  const tol2 = tolPx * tolPx;
1582
- let best = null;
1583
- try {
1592
+ const bestRef = { current: null };
1593
+ safeJsx("MiniBoard.findNearestPoint.loop", () => {
1584
1594
  const objs = b.objectsList || [];
1585
1595
  for (const o of objs) {
1586
- try {
1587
- if (objKind(o) !== "point") continue;
1596
+ safeJsx("MiniBoard.findNearestPoint.iter", () => {
1597
+ if (objKind(o) !== "point") return;
1588
1598
  const pc = o.coords?.scrCoords;
1589
- if (!pc) continue;
1599
+ if (!pc) return;
1590
1600
  const dx = pc[1] - sx;
1591
1601
  const dy = pc[2] - sy;
1592
1602
  const d2 = dx * dx + dy * dy;
1593
- if (d2 <= tol2 && (!best || d2 < best.d2)) best = { obj: o, d2 };
1594
- } catch {
1595
- }
1603
+ const cur = bestRef.current;
1604
+ if (d2 <= tol2 && (!cur || d2 < cur.d2)) bestRef.current = { obj: o, d2 };
1605
+ });
1596
1606
  }
1597
- } catch {
1598
- }
1599
- return best ? best.obj : null;
1607
+ });
1608
+ return bestRef.current ? bestRef.current.obj : null;
1600
1609
  }, [screenCoordsOf]);
1601
1610
  const promoteLabel = react.useCallback((o) => {
1602
1611
  if (!o) return o;
@@ -1604,31 +1613,25 @@ var init_MiniBoard = __esm({
1604
1613
  if (t !== "text") return o;
1605
1614
  const b = boardRef.current;
1606
1615
  if (!b) return o;
1607
- try {
1616
+ const promoted = safeJsx("MiniBoard.promoteLabel", () => {
1608
1617
  for (const c of b.objectsList || []) {
1609
1618
  if (c.label === o) return c;
1610
1619
  }
1611
- } catch {
1612
- }
1613
- return o;
1620
+ return null;
1621
+ }, null);
1622
+ return promoted ?? o;
1614
1623
  }, []);
1615
1624
  const pendingTransformRef = react.useRef(null);
1616
1625
  const transformSubsRef = react.useRef(/* @__PURE__ */ new Set());
1617
1626
  const emitTransform = react.useCallback((info) => {
1618
1627
  transformSubsRef.current.forEach((cb) => {
1619
- try {
1620
- cb(info);
1621
- } catch {
1622
- }
1628
+ safeJsx("MiniBoard.emitTransform.cb", () => cb(info));
1623
1629
  });
1624
1630
  }, []);
1625
1631
  const selectSubsRef = react.useRef(/* @__PURE__ */ new Set());
1626
1632
  const emitSelect = react.useCallback((snap) => {
1627
1633
  selectSubsRef.current.forEach((cb) => {
1628
- try {
1629
- cb(snap);
1630
- } catch {
1631
- }
1634
+ safeJsx("MiniBoard.emitSelect.cb", () => cb(snap));
1632
1635
  });
1633
1636
  }, []);
1634
1637
  const moveDownRef = react.useRef(null);
@@ -1640,7 +1643,7 @@ var init_MiniBoard = __esm({
1640
1643
  const JXG = (await import('jsxgraph')).default;
1641
1644
  if (cancelled || !containerRef.current) return;
1642
1645
  jxgRef.current = JXG;
1643
- try {
1646
+ safeJsx("MiniBoard.applyJxgOptions", () => {
1644
1647
  const opts = JXG.Options;
1645
1648
  if (opts) {
1646
1649
  opts.text = opts.text || {};
@@ -1653,8 +1656,7 @@ var init_MiniBoard = __esm({
1653
1656
  opts.label.strokeColor = themeLabel(isDarkRef.current);
1654
1657
  opts.text.strokeColor = themeLabel(isDarkRef.current);
1655
1658
  }
1656
- } catch {
1657
- }
1659
+ });
1658
1660
  const board = JXG.JSXGraph.initBoard(containerId, {
1659
1661
  boundingbox: initialState?.bbox ?? [-10, 10, 10, -10],
1660
1662
  axis: false,
@@ -1675,43 +1677,20 @@ var init_MiniBoard = __esm({
1675
1677
  });
1676
1678
  boardRef.current = board;
1677
1679
  if (initialState && initialState.elements.length > 0) {
1678
- const idMap = objMapRef.current;
1679
1680
  for (const el of initialState.elements) {
1680
- const resolved = el.args.map((a) => typeof a === "string" && idMap.has(a) ? idMap.get(a) : a);
1681
- try {
1682
- if (el.type === "valueLabel") {
1683
- const target = resolved[0];
1684
- if (target) {
1685
- const txt = createValueLabelFor(target);
1686
- if (txt) {
1687
- idMap.set(el.id, txt);
1688
- valueLabelsRef.current.set(target, txt);
1689
- }
1690
- }
1691
- continue;
1692
- }
1693
- const themedAttrs = resolveAttrColors({ ...el.attrs }, paletteFor(isDarkRef.current));
1694
- const obj = board.create(el.type, resolved, themedAttrs);
1695
- idMap.set(el.id, obj);
1696
- } catch (err) {
1697
- console.warn("Replay failed for", el.type, err);
1698
- }
1681
+ recreateFromLogEntry(el);
1699
1682
  }
1700
1683
  creationLogRef.current = [...initialState.elements];
1701
1684
  labelIdxRef.current = initialState.elements.filter((e) => e.type === "point").length;
1702
1685
  }
1703
1686
  if (showAxisRef.current) {
1704
- try {
1687
+ safeJsx("MiniBoard.initAxes", () => {
1705
1688
  axisObjsRef.current.x = board.create("axis", [[0, 0], [1, 0]], { strokeColor: themeAxis(isDarkRef.current), name: "", withLabel: false });
1706
1689
  axisObjsRef.current.y = board.create("axis", [[0, 0], [0, 1]], { strokeColor: themeAxis(isDarkRef.current), name: "", withLabel: false });
1707
- } catch {
1708
- }
1690
+ });
1709
1691
  }
1710
1692
  if (showGridRef.current) {
1711
- try {
1712
- board.create("grid", [], { strokeColor: themeGrid(isDarkRef.current), strokeOpacity: 1 });
1713
- } catch {
1714
- }
1693
+ safeJsx("MiniBoard.initGrid", () => board.create("grid", [], { strokeColor: themeGrid(isDarkRef.current), strokeOpacity: 1 }));
1715
1694
  }
1716
1695
  board.on("down", (e) => {
1717
1696
  const ctx = {
@@ -1862,6 +1841,8 @@ var init_MiniBoard = __esm({
1862
1841
  setShowGrid: (b) => setShowGridRef.current(b),
1863
1842
  undo: () => undoLastRef.current(),
1864
1843
  canUndo: () => creationLogRef.current.length > 0,
1844
+ redo: () => redoNextRef.current(),
1845
+ canRedo: () => redoStackRef.current.length > 0,
1865
1846
  subscribe: (cb) => {
1866
1847
  subscribersRef.current.add(cb);
1867
1848
  return () => {
@@ -1874,15 +1855,14 @@ var init_MiniBoard = __esm({
1874
1855
  const b = boardRef.current;
1875
1856
  if (!b) return [];
1876
1857
  const out = [];
1877
- try {
1858
+ safeJsx("MiniBoard.getAllPointNames", () => {
1878
1859
  const objs = b.objectsList || [];
1879
1860
  for (const o of objs) {
1880
1861
  if (objKind(o) === "point" && typeof o.name === "string" && o.name) {
1881
1862
  out.push(o.name);
1882
1863
  }
1883
1864
  }
1884
- } catch {
1885
- }
1865
+ });
1886
1866
  return out;
1887
1867
  },
1888
1868
  onSelect: (cb) => {
@@ -1943,10 +1923,7 @@ var init_MiniBoard = __esm({
1943
1923
  previewRafRef.current = null;
1944
1924
  }
1945
1925
  if (boardRef.current && jxgRef.current) {
1946
- try {
1947
- jxgRef.current.JSXGraph.freeBoard(boardRef.current);
1948
- } catch {
1949
- }
1926
+ safeJsx("MiniBoard.freeBoard", () => jxgRef.current.JSXGraph.freeBoard(boardRef.current));
1950
1927
  boardRef.current = null;
1951
1928
  }
1952
1929
  };
@@ -1954,19 +1931,13 @@ var init_MiniBoard = __esm({
1954
1931
  react.useEffect(() => {
1955
1932
  const b = boardRef.current;
1956
1933
  if (!b) return;
1957
- try {
1934
+ safeJsx("MiniBoard.toggleAxis", () => {
1958
1935
  if (axisObjsRef.current.x) {
1959
- try {
1960
- b.removeObject(axisObjsRef.current.x);
1961
- } catch {
1962
- }
1936
+ safeJsx("MiniBoard.removeObject(axisX)", () => b.removeObject(axisObjsRef.current.x));
1963
1937
  axisObjsRef.current.x = void 0;
1964
1938
  }
1965
1939
  if (axisObjsRef.current.y) {
1966
- try {
1967
- b.removeObject(axisObjsRef.current.y);
1968
- } catch {
1969
- }
1940
+ safeJsx("MiniBoard.removeObject(axisY)", () => b.removeObject(axisObjsRef.current.y));
1970
1941
  axisObjsRef.current.y = void 0;
1971
1942
  }
1972
1943
  if (showAxis) {
@@ -1974,28 +1945,23 @@ var init_MiniBoard = __esm({
1974
1945
  axisObjsRef.current.y = b.create("axis", [[0, 0], [0, 1]], { strokeColor: themeAxis(isDarkRef.current), name: "", withLabel: false });
1975
1946
  }
1976
1947
  b.update();
1977
- } catch {
1978
- }
1948
+ });
1979
1949
  }, [showAxis]);
1980
1950
  react.useEffect(() => {
1981
1951
  const b = boardRef.current;
1982
1952
  if (!b) return;
1983
- try {
1953
+ safeJsx("MiniBoard.toggleGrid", () => {
1984
1954
  const objs = Object.values(b.objects || {});
1985
1955
  for (const o of objs) {
1986
1956
  if (o && (o.elType === "grid" || o.type === "grid" || o.visProp && o.visProp.type === "grid")) {
1987
- try {
1988
- b.removeObject(o);
1989
- } catch {
1990
- }
1957
+ safeJsx("MiniBoard.removeObject(grid)", () => b.removeObject(o));
1991
1958
  }
1992
1959
  }
1993
1960
  if (showGrid) {
1994
1961
  b.create("grid", [], { strokeColor: themeGrid(isDarkRef.current), strokeOpacity: 1 });
1995
1962
  }
1996
1963
  b.update();
1997
- } catch {
1998
- }
1964
+ });
1999
1965
  }, [showGrid]);
2000
1966
  const handleToolChange = react.useCallback((t) => {
2001
1967
  clearPending();
@@ -2003,10 +1969,9 @@ var init_MiniBoard = __esm({
2003
1969
  setTool(t);
2004
1970
  const b = boardRef.current;
2005
1971
  if (b) {
2006
- try {
1972
+ safeJsx("MiniBoard.setPanForTool", () => {
2007
1973
  if (b.attr?.pan) b.attr.pan.enabled = t !== "select";
2008
- } catch {
2009
- }
1974
+ });
2010
1975
  }
2011
1976
  }, [clearPending]);
2012
1977
  const handleToolChangeRef = react.useRef(handleToolChange);
@@ -2014,10 +1979,7 @@ var init_MiniBoard = __esm({
2014
1979
  const subscribersRef = react.useRef(/* @__PURE__ */ new Set());
2015
1980
  const notifySubscribers = react.useCallback(() => {
2016
1981
  subscribersRef.current.forEach((cb) => {
2017
- try {
2018
- cb();
2019
- } catch {
2020
- }
1982
+ safeJsx("MiniBoard.notifySubscriber.cb", () => cb());
2021
1983
  });
2022
1984
  }, []);
2023
1985
  react.useEffect(() => {
@@ -2025,6 +1987,8 @@ var init_MiniBoard = __esm({
2025
1987
  }, [tool, showAxis, showGrid, historyTick, notifySubscribers]);
2026
1988
  const undoLastRef = react.useRef(undoLast);
2027
1989
  undoLastRef.current = undoLast;
1990
+ const redoNextRef = react.useRef(redoNext);
1991
+ redoNextRef.current = redoNext;
2028
1992
  const clearPendingRef = react.useRef(clearPending);
2029
1993
  clearPendingRef.current = clearPending;
2030
1994
  const finalizeTransformCreateRef = react.useRef(finalizeTransformCreate);
@@ -2135,6 +2099,7 @@ function MobileToolDrawer({
2135
2099
  disabled: a.disabled,
2136
2100
  "aria-label": a.label,
2137
2101
  title: a.title ?? a.label,
2102
+ "data-testid": a.testId,
2138
2103
  className: "inline-flex h-9 w-9 items-center justify-center rounded-full text-slate-600 transition hover:bg-slate-100 hover:text-slate-900 disabled:cursor-not-allowed disabled:text-slate-300 disabled:hover:bg-transparent",
2139
2104
  children: a.icon
2140
2105
  },
@@ -2240,6 +2205,12 @@ function UndoIcon() {
2240
2205
  /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3.51 13a9 9 0 1 0 2.13-9.36L3 7" })
2241
2206
  ] });
2242
2207
  }
2208
+ function RedoIcon() {
2209
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2210
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "21 7 21 13 15 13" }),
2211
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M20.49 13a9 9 0 1 1-2.13-9.36L21 7" })
2212
+ ] });
2213
+ }
2243
2214
  function AxisIcon() {
2244
2215
  return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", children: [
2245
2216
  /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "4", y1: "20", x2: "20", y2: "20" }),
@@ -2284,7 +2255,7 @@ function useToolHoverTooltip() {
2284
2255
  return { hover, portalReady, showHover, hideHover };
2285
2256
  }
2286
2257
  function DesktopGeometryPanel(props) {
2287
- const { activeTool, onToolChange, showAxis, showGrid, onShowAxisChange, onShowGridChange, onUndo, canUndo, onClose, isDark, chordGroup } = props;
2258
+ const { activeTool, onToolChange, showAxis, showGrid, onShowAxisChange, onShowGridChange, onUndo, canUndo, onRedo, canRedo, onClose, isDark, chordGroup } = props;
2288
2259
  const grouped = react.useMemo(() => {
2289
2260
  return TOOLS.reduce((acc, t) => {
2290
2261
  var _a;
@@ -2333,9 +2304,23 @@ function DesktopGeometryPanel(props) {
2333
2304
  disabled: !canUndo,
2334
2305
  title: "Ho\xE0n t\xE1c (Ctrl/Cmd+Z)",
2335
2306
  "aria-label": "Ho\xE0n t\xE1c",
2307
+ "data-testid": "undo-btn",
2336
2308
  className: "ml-auto inline-flex items-center justify-center rounded p-1 text-slate-600 transition hover:bg-slate-100 hover:text-slate-900 disabled:cursor-not-allowed disabled:text-slate-300 disabled:hover:bg-transparent",
2337
2309
  children: /* @__PURE__ */ jsxRuntime.jsx(UndoIcon, {})
2338
2310
  }
2311
+ ),
2312
+ /* @__PURE__ */ jsxRuntime.jsx(
2313
+ "button",
2314
+ {
2315
+ type: "button",
2316
+ onClick: onRedo,
2317
+ disabled: !canRedo,
2318
+ title: "L\xE0m l\u1EA1i (Ctrl/Cmd+Shift+Z)",
2319
+ "aria-label": "L\xE0m l\u1EA1i",
2320
+ "data-testid": "redo-btn",
2321
+ className: "inline-flex items-center justify-center rounded p-1 text-slate-600 transition hover:bg-slate-100 hover:text-slate-900 disabled:cursor-not-allowed disabled:text-slate-300 disabled:hover:bg-transparent",
2322
+ children: /* @__PURE__ */ jsxRuntime.jsx(RedoIcon, {})
2323
+ }
2339
2324
  )
2340
2325
  ] }) }),
2341
2326
  groupKeys.map((group) => {
@@ -2456,6 +2441,8 @@ function MobileGeometryPanel(props) {
2456
2441
  onShowGridChange,
2457
2442
  onUndo,
2458
2443
  canUndo,
2444
+ onRedo,
2445
+ canRedo,
2459
2446
  isDark,
2460
2447
  drawerOpen,
2461
2448
  onDrawerClose
@@ -2504,6 +2491,13 @@ function MobileGeometryPanel(props) {
2504
2491
  icon: /* @__PURE__ */ jsxRuntime.jsx(UndoIcon, {}),
2505
2492
  onClick: onUndo,
2506
2493
  disabled: !canUndo
2494
+ },
2495
+ {
2496
+ label: "L\xE0m l\u1EA1i",
2497
+ title: "L\xE0m l\u1EA1i (Ctrl/Cmd+Shift+Z)",
2498
+ icon: /* @__PURE__ */ jsxRuntime.jsx(RedoIcon, {}),
2499
+ onClick: onRedo,
2500
+ disabled: !canRedo
2507
2501
  }
2508
2502
  ],
2509
2503
  groups,
@@ -2969,8 +2963,9 @@ var init_EditorPanel = __esm({
2969
2963
  init_render();
2970
2964
  init_PropertiesPopover();
2971
2965
  init_TransformParamPopover();
2966
+ init_LeftPanel();
2972
2967
  GeometryEditorPanel = react.forwardRef(
2973
- function GeometryEditorPanel2({ initialState, onInsert, onClose, withLeftPanel = false, onStateChange, isDark, isMobile = false, onOpenDrawer }, ref) {
2968
+ function GeometryEditorPanel2({ initialState, onInsert, onClose, withLeftPanel = false, onStateChange, isDark, isMobile = false, onOpenDrawer, onUndo, onRedo, canUndo, canRedo }, ref) {
2974
2969
  const handleRef = react.useRef(null);
2975
2970
  const [ready, setReady] = react.useState(false);
2976
2971
  const [propsPopover, setPropsPopover] = react.useState(null);
@@ -2987,7 +2982,8 @@ var init_EditorPanel = __esm({
2987
2982
  tool: h.getTool(),
2988
2983
  showAxis: h.getShowAxis(),
2989
2984
  showGrid: h.getShowGrid(),
2990
- canUndo: h.canUndo()
2985
+ canUndo: h.canUndo(),
2986
+ canRedo: h.canRedo()
2991
2987
  });
2992
2988
  }, []);
2993
2989
  const handleReady = react.useCallback((h) => {
@@ -3029,6 +3025,7 @@ var init_EditorPanel = __esm({
3029
3025
  setShowAxis: (b) => handleRef.current?.setShowAxis(b),
3030
3026
  setShowGrid: (b) => handleRef.current?.setShowGrid(b),
3031
3027
  undo: () => handleRef.current?.undo(),
3028
+ redo: () => handleRef.current?.redo(),
3032
3029
  insert: performInsert,
3033
3030
  hasContent: () => (handleRef.current?.getCreationLog().length ?? 0) > 0
3034
3031
  }), [performInsert]);
@@ -3078,17 +3075,45 @@ var init_EditorPanel = __esm({
3078
3075
  ] }),
3079
3076
  "D\u1EF1ng h\xECnh h\u1ECDc"
3080
3077
  ] }),
3081
- isMobile && /* @__PURE__ */ jsxRuntime.jsx(
3082
- "button",
3083
- {
3084
- type: "button",
3085
- onClick: handleInsert,
3086
- disabled: !ready,
3087
- "data-testid": "geometry-insert-btn-mobile",
3088
- className: "rounded bg-white/15 px-3 py-1.5 text-xs font-semibold transition hover:bg-white/25 disabled:opacity-50",
3089
- children: "Ch\xE8n"
3090
- }
3091
- ),
3078
+ isMobile && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3079
+ /* @__PURE__ */ jsxRuntime.jsx(
3080
+ "button",
3081
+ {
3082
+ type: "button",
3083
+ onClick: onUndo,
3084
+ disabled: !canUndo,
3085
+ "aria-label": "Ho\xE0n t\xE1c",
3086
+ title: "Ho\xE0n t\xE1c (Ctrl/Cmd+Z)",
3087
+ "data-testid": "undo-btn-mobile",
3088
+ className: "inline-flex h-9 w-9 items-center justify-center rounded transition hover:bg-white/15 disabled:opacity-40",
3089
+ children: /* @__PURE__ */ jsxRuntime.jsx(UndoIcon, {})
3090
+ }
3091
+ ),
3092
+ /* @__PURE__ */ jsxRuntime.jsx(
3093
+ "button",
3094
+ {
3095
+ type: "button",
3096
+ onClick: onRedo,
3097
+ disabled: !canRedo,
3098
+ "aria-label": "L\xE0m l\u1EA1i",
3099
+ title: "L\xE0m l\u1EA1i (Ctrl/Cmd+Shift+Z)",
3100
+ "data-testid": "redo-btn-mobile",
3101
+ className: "inline-flex h-9 w-9 items-center justify-center rounded transition hover:bg-white/15 disabled:opacity-40",
3102
+ children: /* @__PURE__ */ jsxRuntime.jsx(RedoIcon, {})
3103
+ }
3104
+ ),
3105
+ /* @__PURE__ */ jsxRuntime.jsx(
3106
+ "button",
3107
+ {
3108
+ type: "button",
3109
+ onClick: handleInsert,
3110
+ disabled: !ready,
3111
+ "data-testid": "geometry-insert-btn-mobile",
3112
+ className: "rounded bg-white/15 px-3 py-1.5 text-xs font-semibold transition hover:bg-white/25 disabled:opacity-50",
3113
+ children: "Ch\xE8n"
3114
+ }
3115
+ )
3116
+ ] }),
3092
3117
  /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onClose, "aria-label": "\u0110\xF3ng", className: "inline-flex h-9 w-9 items-center justify-center rounded transition hover:bg-white/15", children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
3093
3118
  /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" }),
3094
3119
  /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" })
@@ -3410,7 +3435,8 @@ var init_host = __esm({
3410
3435
  tool: "move",
3411
3436
  showAxis: false,
3412
3437
  showGrid: false,
3413
- canUndo: false
3438
+ canUndo: false,
3439
+ canRedo: false
3414
3440
  };
3415
3441
  GeometryStampHost = react.forwardRef(
3416
3442
  function GeometryStampHost2({ api, editingElement, onClose, isDark }, ref) {
@@ -3476,6 +3502,8 @@ var init_host = __esm({
3476
3502
  onShowGridChange: (b) => panelRef.current?.setShowGrid(b),
3477
3503
  onUndo: () => panelRef.current?.undo(),
3478
3504
  canUndo: geomState.canUndo,
3505
+ onRedo: () => panelRef.current?.redo(),
3506
+ canRedo: geomState.canRedo,
3479
3507
  onClose,
3480
3508
  isDark,
3481
3509
  isMobile,
@@ -3495,7 +3523,11 @@ var init_host = __esm({
3495
3523
  withLeftPanel: !isMobile,
3496
3524
  isDark,
3497
3525
  isMobile,
3498
- onOpenDrawer: () => setDrawerOpen(true)
3526
+ onOpenDrawer: () => setDrawerOpen(true),
3527
+ onUndo: () => panelRef.current?.undo(),
3528
+ onRedo: () => panelRef.current?.redo(),
3529
+ canUndo: geomState.canUndo,
3530
+ canRedo: geomState.canRedo
3499
3531
  }
3500
3532
  )
3501
3533
  ] });