From 6ec259a0e71174651bae95d4628138bf6fd68742 Mon Sep 17 00:00:00 2001
From: kj_sh604
Date: Sun, 15 Mar 2026 16:19:35 -0400
Subject: refactor: packages/

---
 packages/excalidraw/components/Stats/Angle.tsx | 95 ++++++++++++++++++++++++++
 1 file changed, 95 insertions(+)
 create mode 100644 packages/excalidraw/components/Stats/Angle.tsx

(limited to 'packages/excalidraw/components/Stats/Angle.tsx')

diff --git a/packages/excalidraw/components/Stats/Angle.tsx b/packages/excalidraw/components/Stats/Angle.tsx
new file mode 100644
index 0000000..409476a
--- /dev/null
+++ b/packages/excalidraw/components/Stats/Angle.tsx
@@ -0,0 +1,95 @@
+import { mutateElement } from "../../element/mutateElement";
+import { getBoundTextElement } from "../../element/textElement";
+import { isArrowElement, isElbowArrow } from "../../element/typeChecks";
+import type { ExcalidrawElement } from "../../element/types";
+import { angleIcon } from "../icons";
+import DragInput from "./DragInput";
+import type { DragInputCallbackType } from "./DragInput";
+import { getStepSizedValue, isPropertyEditable, updateBindings } from "./utils";
+import type Scene from "../../scene/Scene";
+import type { AppState } from "../../types";
+import type { Degrees } from "@excalidraw/math";
+import { degreesToRadians, radiansToDegrees } from "@excalidraw/math";
+
+interface AngleProps {
+  element: ExcalidrawElement;
+  scene: Scene;
+  appState: AppState;
+  property: "angle";
+}
+
+const STEP_SIZE = 15;
+
+const handleDegreeChange: DragInputCallbackType<AngleProps["property"]> = ({
+  accumulatedChange,
+  originalElements,
+  shouldChangeByStepSize,
+  nextValue,
+  scene,
+}) => {
+  const elementsMap = scene.getNonDeletedElementsMap();
+  const elements = scene.getNonDeletedElements();
+  const origElement = originalElements[0];
+  if (origElement && !isElbowArrow(origElement)) {
+    const latestElement = elementsMap.get(origElement.id);
+    if (!latestElement) {
+      return;
+    }
+
+    if (nextValue !== undefined) {
+      const nextAngle = degreesToRadians(nextValue as Degrees);
+      mutateElement(latestElement, {
+        angle: nextAngle,
+      });
+      updateBindings(latestElement, elementsMap, elements, scene);
+
+      const boundTextElement = getBoundTextElement(latestElement, elementsMap);
+      if (boundTextElement && !isArrowElement(latestElement)) {
+        mutateElement(boundTextElement, { angle: nextAngle });
+      }
+
+      return;
+    }
+
+    const originalAngleInDegrees =
+      Math.round(radiansToDegrees(origElement.angle) * 100) / 100;
+    const changeInDegrees = Math.round(accumulatedChange);
+    let nextAngleInDegrees = (originalAngleInDegrees + changeInDegrees) % 360;
+    if (shouldChangeByStepSize) {
+      nextAngleInDegrees = getStepSizedValue(nextAngleInDegrees, STEP_SIZE);
+    }
+
+    nextAngleInDegrees =
+      nextAngleInDegrees < 0 ? nextAngleInDegrees + 360 : nextAngleInDegrees;
+
+    const nextAngle = degreesToRadians(nextAngleInDegrees as Degrees);
+
+    mutateElement(latestElement, {
+      angle: nextAngle,
+    });
+    updateBindings(latestElement, elementsMap, elements, scene);
+
+    const boundTextElement = getBoundTextElement(latestElement, elementsMap);
+    if (boundTextElement && !isArrowElement(latestElement)) {
+      mutateElement(boundTextElement, { angle: nextAngle });
+    }
+  }
+};
+
+const Angle = ({ element, scene, appState, property }: AngleProps) => {
+  return (
+    <DragInput
+      label="A"
+      icon={angleIcon}
+      value={Math.round((radiansToDegrees(element.angle) % 360) * 100) / 100}
+      elements={[element]}
+      dragInputCallback={handleDegreeChange}
+      editable={isPropertyEditable(element, "angle")}
+      scene={scene}
+      appState={appState}
+      property={property}
+    />
+  );
+};
+
+export default Angle;
-- 
cgit v1.2.3

