@xom11/whiteboard 0.10.0 → 0.11.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.
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- export { geometryStamp } from './chunk-PDKKDZ4H.mjs';
2
+ export { geometryStamp } from './chunk-YVJP7NRG.mjs';
3
3
  export { isGeometryCustomData } from './chunk-G7FR3AIV.mjs';
4
4
  import './chunk-HTBLO5JO.mjs';
5
5
  import './chunk-BJTO5JO5.mjs';
package/dist/graph-2d.js CHANGED
@@ -907,9 +907,15 @@ function CloseIcon() {
907
907
  ] });
908
908
  }
909
909
  function UndoIcon() {
910
- 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: [
911
- /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "3 7 3 13 9 13" }),
912
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3.51 13a9 9 0 1 0 2.13-9.36L3 7" })
910
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
911
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 10 L8 5 L8 8 L15 8 A5 5 0 0 1 20 13 L20 16" }),
912
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 10 L8 15 L8 12" })
913
+ ] });
914
+ }
915
+ function RedoIcon() {
916
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
917
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 10 L16 5 L16 8 L9 8 A5 5 0 0 0 4 13 L4 16" }),
918
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 10 L16 15 L16 12" })
913
919
  ] });
914
920
  }
915
921
  function ResetViewIcon() {
@@ -994,9 +1000,23 @@ function PanelBody(props) {
994
1000
  disabled: !props.canUndo,
995
1001
  title: "Ho\xE0n t\xE1c (Ctrl/Cmd+Z)",
996
1002
  "aria-label": "Ho\xE0n t\xE1c",
1003
+ "data-testid": "undo-btn",
997
1004
  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",
998
1005
  children: /* @__PURE__ */ jsxRuntime.jsx(UndoIcon, {})
999
1006
  }
1007
+ ),
1008
+ /* @__PURE__ */ jsxRuntime.jsx(
1009
+ "button",
1010
+ {
1011
+ type: "button",
1012
+ onClick: props.onRedo,
1013
+ disabled: !props.canRedo,
1014
+ title: "L\xE0m l\u1EA1i (Ctrl/Cmd+Shift+Z)",
1015
+ "aria-label": "L\xE0m l\u1EA1i",
1016
+ "data-testid": "redo-btn",
1017
+ 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",
1018
+ children: /* @__PURE__ */ jsxRuntime.jsx(RedoIcon, {})
1019
+ }
1000
1020
  )
1001
1021
  ] }) }),
1002
1022
  /* @__PURE__ */ jsxRuntime.jsx(Section, { label: "C\xF4ng c\u1EE5", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-4 gap-1", children: GRAPH_TOOLS.map((t) => {
@@ -1352,6 +1372,7 @@ var init_EditorPanel = __esm({
1352
1372
  const [errors, setErrors] = react.useState({});
1353
1373
  const [tool, setToolState] = react.useState("move");
1354
1374
  const undoStackRef = react.useRef([]);
1375
+ const redoStackRef = react.useRef([]);
1355
1376
  const idCounterRef = react.useRef(1);
1356
1377
  const toolRef = react.useRef(tool);
1357
1378
  toolRef.current = tool;
@@ -1362,6 +1383,7 @@ var init_EditorPanel = __esm({
1362
1383
  const pushUndo = react.useCallback((g) => {
1363
1384
  undoStackRef.current.push(g);
1364
1385
  if (undoStackRef.current.length > 30) undoStackRef.current.shift();
1386
+ redoStackRef.current = [];
1365
1387
  }, []);
1366
1388
  const setErrorsWithNotify = react.useCallback(
1367
1389
  (updater) => {
@@ -1378,7 +1400,8 @@ var init_EditorPanel = __esm({
1378
1400
  tool: t,
1379
1401
  showAxis: g.view.showAxis,
1380
1402
  showGrid: g.view.showGrid,
1381
- canUndo: undoStackRef.current.length > 0
1403
+ canUndo: undoStackRef.current.length > 0,
1404
+ canRedo: redoStackRef.current.length > 0
1382
1405
  });
1383
1406
  }, []);
1384
1407
  const updateGraph = react.useCallback(
@@ -1393,6 +1416,58 @@ var init_EditorPanel = __esm({
1393
1416
  },
1394
1417
  [pushUndo, notifyStateChange]
1395
1418
  );
1419
+ const doUndo = react.useCallback(() => {
1420
+ const prev = undoStackRef.current.pop();
1421
+ if (!prev) return;
1422
+ redoStackRef.current.push(graphRef.current);
1423
+ if (redoStackRef.current.length > 30) redoStackRef.current.shift();
1424
+ graphRef.current = prev;
1425
+ forceUpdate((n) => n + 1);
1426
+ propsRef.current.onStateChange({
1427
+ tool: toolRef.current,
1428
+ showAxis: prev.view.showAxis,
1429
+ showGrid: prev.view.showGrid,
1430
+ canUndo: undoStackRef.current.length > 0,
1431
+ canRedo: redoStackRef.current.length > 0
1432
+ });
1433
+ propsRef.current.onGraphChange?.(prev);
1434
+ }, []);
1435
+ const doRedo = react.useCallback(() => {
1436
+ const next = redoStackRef.current.pop();
1437
+ if (!next) return;
1438
+ undoStackRef.current.push(graphRef.current);
1439
+ if (undoStackRef.current.length > 30) undoStackRef.current.shift();
1440
+ graphRef.current = next;
1441
+ forceUpdate((n) => n + 1);
1442
+ propsRef.current.onStateChange({
1443
+ tool: toolRef.current,
1444
+ showAxis: next.view.showAxis,
1445
+ showGrid: next.view.showGrid,
1446
+ canUndo: undoStackRef.current.length > 0,
1447
+ canRedo: redoStackRef.current.length > 0
1448
+ });
1449
+ propsRef.current.onGraphChange?.(next);
1450
+ }, []);
1451
+ react.useEffect(() => {
1452
+ const onKey = (e) => {
1453
+ const ae = document.activeElement;
1454
+ const inField = !!(ae && (ae.tagName === "INPUT" || ae.tagName === "TEXTAREA" || ae.isContentEditable));
1455
+ if (inField) return;
1456
+ if (!(e.metaKey || e.ctrlKey)) return;
1457
+ const key = e.key.toLowerCase();
1458
+ if (key === "z" && !e.shiftKey) {
1459
+ e.preventDefault();
1460
+ e.stopPropagation();
1461
+ doUndo();
1462
+ } else if (key === "z" && e.shiftKey || key === "y" && !e.shiftKey) {
1463
+ e.preventDefault();
1464
+ e.stopPropagation();
1465
+ doRedo();
1466
+ }
1467
+ };
1468
+ window.addEventListener("keydown", onKey, { capture: true });
1469
+ return () => window.removeEventListener("keydown", onKey, { capture: true });
1470
+ }, [doUndo, doRedo]);
1396
1471
  const onBoardEvent = react.useCallback((ev) => {
1397
1472
  const currentTool = toolRef.current;
1398
1473
  if (currentTool === "point-on-curve" && ev.type === "click-curve" && ev.functionId && ev.x !== void 0) {
@@ -1445,7 +1520,8 @@ var init_EditorPanel = __esm({
1445
1520
  tool: t,
1446
1521
  showAxis: g.view.showAxis,
1447
1522
  showGrid: g.view.showGrid,
1448
- canUndo: undoStackRef.current.length > 0
1523
+ canUndo: undoStackRef.current.length > 0,
1524
+ canRedo: redoStackRef.current.length > 0
1449
1525
  });
1450
1526
  },
1451
1527
  setShowAxis: (b) => updateGraph((g) => ({ ...g, view: { ...g.view, showAxis: b } })),
@@ -1454,19 +1530,8 @@ var init_EditorPanel = __esm({
1454
1530
  ...g,
1455
1531
  view: { ...g.view, xMin: -10, xMax: 10, yMin: -10, yMax: 10 }
1456
1532
  })),
1457
- undo: () => {
1458
- const prev = undoStackRef.current.pop();
1459
- if (!prev) return;
1460
- graphRef.current = prev;
1461
- forceUpdate((n) => n + 1);
1462
- propsRef.current.onStateChange({
1463
- tool: toolRef.current,
1464
- showAxis: prev.view.showAxis,
1465
- showGrid: prev.view.showGrid,
1466
- canUndo: undoStackRef.current.length > 0
1467
- });
1468
- propsRef.current.onGraphChange?.(prev);
1469
- },
1533
+ undo: doUndo,
1534
+ redo: doRedo,
1470
1535
  addFunction: (expr) => {
1471
1536
  const g = graphRef.current;
1472
1537
  if (g.functions.length >= MAX_FUNCTIONS) {
@@ -1559,7 +1624,7 @@ var init_EditorPanel = __esm({
1559
1624
  }),
1560
1625
  // deps: updateGraph stable; errors changes when function errors change; setErrorsWithNotify stable
1561
1626
  // eslint-disable-next-line react-hooks/exhaustive-deps
1562
- [updateGraph, errors, setErrorsWithNotify]
1627
+ [updateGraph, errors, setErrorsWithNotify, doUndo, doRedo]
1563
1628
  );
1564
1629
  react.useEffect(() => {
1565
1630
  if (!initialGraphNotifiedRef.current) {
@@ -1859,7 +1924,8 @@ var init_host = __esm({
1859
1924
  tool: "move",
1860
1925
  showAxis: true,
1861
1926
  showGrid: true,
1862
- canUndo: false
1927
+ canUndo: false,
1928
+ canRedo: false
1863
1929
  };
1864
1930
  Graph2DStampHost = react.forwardRef(
1865
1931
  function Graph2DStampHost2({ api, editingElement, onClose, isDark }, ref) {
@@ -1919,6 +1985,8 @@ var init_host = __esm({
1919
1985
  onResetView: () => panelRef.current?.resetView(),
1920
1986
  onUndo: () => panelRef.current?.undo(),
1921
1987
  canUndo: graphUIState.canUndo,
1988
+ onRedo: () => panelRef.current?.redo(),
1989
+ canRedo: graphUIState.canRedo,
1922
1990
  onClose,
1923
1991
  isDark,
1924
1992
  isMobile,