@xom11/whiteboard 0.10.0 → 0.10.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.
package/dist/index.js CHANGED
@@ -2308,15 +2308,15 @@ function CloseIcon() {
2308
2308
  ] });
2309
2309
  }
2310
2310
  function UndoIcon() {
2311
- 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: [
2312
- /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "3 7 3 13 9 13" }),
2313
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3.51 13a9 9 0 1 0 2.13-9.36L3 7" })
2311
+ 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: [
2312
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 10 L8 5 L8 8 L15 8 A5 5 0 0 1 20 13 L20 16" }),
2313
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 10 L8 15 L8 12" })
2314
2314
  ] });
2315
2315
  }
2316
2316
  function RedoIcon() {
2317
- 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: [
2318
- /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "21 7 21 13 15 13" }),
2319
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M20.49 13a9 9 0 1 1-2.13-9.36L21 7" })
2317
+ 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: [
2318
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 10 L16 5 L16 8 L9 8 A5 5 0 0 0 4 13 L4 16" }),
2319
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 10 L16 15 L16 12" })
2320
2320
  ] });
2321
2321
  }
2322
2322
  function AxisIcon() {
@@ -8756,9 +8756,15 @@ function CloseIcon3() {
8756
8756
  ] });
8757
8757
  }
8758
8758
  function UndoIcon3() {
8759
- 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: [
8760
- /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "3 7 3 13 9 13" }),
8761
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3.51 13a9 9 0 1 0 2.13-9.36L3 7" })
8759
+ 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: [
8760
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 10 L8 5 L8 8 L15 8 A5 5 0 0 1 20 13 L20 16" }),
8761
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 10 L8 15 L8 12" })
8762
+ ] });
8763
+ }
8764
+ function RedoIcon3() {
8765
+ 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: [
8766
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 10 L16 5 L16 8 L9 8 A5 5 0 0 0 4 13 L4 16" }),
8767
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 10 L16 15 L16 12" })
8762
8768
  ] });
8763
8769
  }
8764
8770
  function ResetViewIcon() {
@@ -8843,9 +8849,23 @@ function PanelBody(props) {
8843
8849
  disabled: !props.canUndo,
8844
8850
  title: "Ho\xE0n t\xE1c (Ctrl/Cmd+Z)",
8845
8851
  "aria-label": "Ho\xE0n t\xE1c",
8852
+ "data-testid": "undo-btn",
8846
8853
  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",
8847
8854
  children: /* @__PURE__ */ jsxRuntime.jsx(UndoIcon3, {})
8848
8855
  }
8856
+ ),
8857
+ /* @__PURE__ */ jsxRuntime.jsx(
8858
+ "button",
8859
+ {
8860
+ type: "button",
8861
+ onClick: props.onRedo,
8862
+ disabled: !props.canRedo,
8863
+ title: "L\xE0m l\u1EA1i (Ctrl/Cmd+Shift+Z)",
8864
+ "aria-label": "L\xE0m l\u1EA1i",
8865
+ "data-testid": "redo-btn",
8866
+ 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",
8867
+ children: /* @__PURE__ */ jsxRuntime.jsx(RedoIcon3, {})
8868
+ }
8849
8869
  )
8850
8870
  ] }) }),
8851
8871
  /* @__PURE__ */ jsxRuntime.jsx(Section4, { label: "C\xF4ng c\u1EE5", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-4 gap-1", children: GRAPH_TOOLS.map((t) => {
@@ -9201,6 +9221,7 @@ var init_EditorPanel3 = __esm({
9201
9221
  const [errors, setErrors] = React8.useState({});
9202
9222
  const [tool, setToolState] = React8.useState("move");
9203
9223
  const undoStackRef = React8.useRef([]);
9224
+ const redoStackRef = React8.useRef([]);
9204
9225
  const idCounterRef = React8.useRef(1);
9205
9226
  const toolRef = React8.useRef(tool);
9206
9227
  toolRef.current = tool;
@@ -9211,6 +9232,7 @@ var init_EditorPanel3 = __esm({
9211
9232
  const pushUndo = React8.useCallback((g) => {
9212
9233
  undoStackRef.current.push(g);
9213
9234
  if (undoStackRef.current.length > 30) undoStackRef.current.shift();
9235
+ redoStackRef.current = [];
9214
9236
  }, []);
9215
9237
  const setErrorsWithNotify = React8.useCallback(
9216
9238
  (updater) => {
@@ -9227,7 +9249,8 @@ var init_EditorPanel3 = __esm({
9227
9249
  tool: t,
9228
9250
  showAxis: g.view.showAxis,
9229
9251
  showGrid: g.view.showGrid,
9230
- canUndo: undoStackRef.current.length > 0
9252
+ canUndo: undoStackRef.current.length > 0,
9253
+ canRedo: redoStackRef.current.length > 0
9231
9254
  });
9232
9255
  }, []);
9233
9256
  const updateGraph = React8.useCallback(
@@ -9242,6 +9265,58 @@ var init_EditorPanel3 = __esm({
9242
9265
  },
9243
9266
  [pushUndo, notifyStateChange]
9244
9267
  );
9268
+ const doUndo = React8.useCallback(() => {
9269
+ const prev = undoStackRef.current.pop();
9270
+ if (!prev) return;
9271
+ redoStackRef.current.push(graphRef.current);
9272
+ if (redoStackRef.current.length > 30) redoStackRef.current.shift();
9273
+ graphRef.current = prev;
9274
+ forceUpdate((n) => n + 1);
9275
+ propsRef.current.onStateChange({
9276
+ tool: toolRef.current,
9277
+ showAxis: prev.view.showAxis,
9278
+ showGrid: prev.view.showGrid,
9279
+ canUndo: undoStackRef.current.length > 0,
9280
+ canRedo: redoStackRef.current.length > 0
9281
+ });
9282
+ propsRef.current.onGraphChange?.(prev);
9283
+ }, []);
9284
+ const doRedo = React8.useCallback(() => {
9285
+ const next = redoStackRef.current.pop();
9286
+ if (!next) return;
9287
+ undoStackRef.current.push(graphRef.current);
9288
+ if (undoStackRef.current.length > 30) undoStackRef.current.shift();
9289
+ graphRef.current = next;
9290
+ forceUpdate((n) => n + 1);
9291
+ propsRef.current.onStateChange({
9292
+ tool: toolRef.current,
9293
+ showAxis: next.view.showAxis,
9294
+ showGrid: next.view.showGrid,
9295
+ canUndo: undoStackRef.current.length > 0,
9296
+ canRedo: redoStackRef.current.length > 0
9297
+ });
9298
+ propsRef.current.onGraphChange?.(next);
9299
+ }, []);
9300
+ React8.useEffect(() => {
9301
+ const onKey = (e) => {
9302
+ const ae = document.activeElement;
9303
+ const inField = !!(ae && (ae.tagName === "INPUT" || ae.tagName === "TEXTAREA" || ae.isContentEditable));
9304
+ if (inField) return;
9305
+ if (!(e.metaKey || e.ctrlKey)) return;
9306
+ const key = e.key.toLowerCase();
9307
+ if (key === "z" && !e.shiftKey) {
9308
+ e.preventDefault();
9309
+ e.stopPropagation();
9310
+ doUndo();
9311
+ } else if (key === "z" && e.shiftKey || key === "y" && !e.shiftKey) {
9312
+ e.preventDefault();
9313
+ e.stopPropagation();
9314
+ doRedo();
9315
+ }
9316
+ };
9317
+ window.addEventListener("keydown", onKey, { capture: true });
9318
+ return () => window.removeEventListener("keydown", onKey, { capture: true });
9319
+ }, [doUndo, doRedo]);
9245
9320
  const onBoardEvent = React8.useCallback((ev) => {
9246
9321
  const currentTool = toolRef.current;
9247
9322
  if (currentTool === "point-on-curve" && ev.type === "click-curve" && ev.functionId && ev.x !== void 0) {
@@ -9294,7 +9369,8 @@ var init_EditorPanel3 = __esm({
9294
9369
  tool: t,
9295
9370
  showAxis: g.view.showAxis,
9296
9371
  showGrid: g.view.showGrid,
9297
- canUndo: undoStackRef.current.length > 0
9372
+ canUndo: undoStackRef.current.length > 0,
9373
+ canRedo: redoStackRef.current.length > 0
9298
9374
  });
9299
9375
  },
9300
9376
  setShowAxis: (b) => updateGraph((g) => ({ ...g, view: { ...g.view, showAxis: b } })),
@@ -9303,19 +9379,8 @@ var init_EditorPanel3 = __esm({
9303
9379
  ...g,
9304
9380
  view: { ...g.view, xMin: -10, xMax: 10, yMin: -10, yMax: 10 }
9305
9381
  })),
9306
- undo: () => {
9307
- const prev = undoStackRef.current.pop();
9308
- if (!prev) return;
9309
- graphRef.current = prev;
9310
- forceUpdate((n) => n + 1);
9311
- propsRef.current.onStateChange({
9312
- tool: toolRef.current,
9313
- showAxis: prev.view.showAxis,
9314
- showGrid: prev.view.showGrid,
9315
- canUndo: undoStackRef.current.length > 0
9316
- });
9317
- propsRef.current.onGraphChange?.(prev);
9318
- },
9382
+ undo: doUndo,
9383
+ redo: doRedo,
9319
9384
  addFunction: (expr) => {
9320
9385
  const g = graphRef.current;
9321
9386
  if (g.functions.length >= MAX_FUNCTIONS) {
@@ -9408,7 +9473,7 @@ var init_EditorPanel3 = __esm({
9408
9473
  }),
9409
9474
  // deps: updateGraph stable; errors changes when function errors change; setErrorsWithNotify stable
9410
9475
  // eslint-disable-next-line react-hooks/exhaustive-deps
9411
- [updateGraph, errors, setErrorsWithNotify]
9476
+ [updateGraph, errors, setErrorsWithNotify, doUndo, doRedo]
9412
9477
  );
9413
9478
  React8.useEffect(() => {
9414
9479
  if (!initialGraphNotifiedRef.current) {
@@ -9553,7 +9618,8 @@ var init_host4 = __esm({
9553
9618
  tool: "move",
9554
9619
  showAxis: true,
9555
9620
  showGrid: true,
9556
- canUndo: false
9621
+ canUndo: false,
9622
+ canRedo: false
9557
9623
  };
9558
9624
  Graph2DStampHost = React8.forwardRef(
9559
9625
  function Graph2DStampHost2({ api, editingElement, onClose, isDark }, ref) {
@@ -9613,6 +9679,8 @@ var init_host4 = __esm({
9613
9679
  onResetView: () => panelRef.current?.resetView(),
9614
9680
  onUndo: () => panelRef.current?.undo(),
9615
9681
  canUndo: graphUIState.canUndo,
9682
+ onRedo: () => panelRef.current?.redo(),
9683
+ canRedo: graphUIState.canRedo,
9616
9684
  onClose,
9617
9685
  isDark,
9618
9686
  isMobile,