Repository: bvaughn/react-resizable-panels
Pull request #444 (2025-01-14 15:53:43)

Replace innerHtml with insertRule
The use of `.innerHTML` causes issues for hosts that have opted in to [Trusted Types](https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API). Because `.innerHTML` can act as a vector for DOM-XSS attacks, websites might lock it down with Trusted Types, causing the specific line to throw a runtime error.
 
In the case of this specific library, the `.innerHTML` call could just as easily be replaced with inserting a CSS rule, as follows:
```
styleElement.sheet?.insertRule(`*{cursor: ${style}!important;}`)
```
 
Note that to satisfy TypeScript, null-coalescing is added to the `.sheet` access. However, when a style element is attached to the DOM, that `.sheet` property should never be undefined.

vercel[bot]: [vc]: #xSP8EopneIkuvv7e1SsDrUHN9F1xQCp3voXT5sqx0Lw=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJyZWFjdC1yZXNpemFibGUtcGFuZWxzIiwicm9vdERpcmVjdG9yeSI6bnVsbCwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL2JyaWFuLXZhdWdobnMtcHJvamVjdHMvcmVhY3QtcmVzaXphYmxlLXBhbmVscy85cjRTS0pnM0h2TldFWmFFREx6ak1UUUhzTGhkIiwicHJldmlld1VybCI6InJlYWN0LXJlc2l6YWJsZS1wYW5lbHMtZ2l0LWZvcmstMi01MTAxNTUtYnJpYW4tdmF1Z2hucy1wcm9qZWN0cy52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IlBFTkRJTkciLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoicmVhY3QtcmVzaXphYmxlLXBhbmVscy1naXQtZm9yay0yLTUxMDE1NS1icmlhbi12YXVnaG5zLXByb2plY3RzLnZlcmNlbC5hcHAifX1dfQ==
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **react-resizable-panels** | 🔄 Building ([Inspect](https://vercel.com/brian-vaughns-projects/react-resizable-panels/9r4SKJg3HvNWEZaEDLzjMTQHsLhd)) | [Visit Preview](https://react-resizable-panels-git-fork-2-510155-brian-vaughns-projects.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/react-resizable-panels-git-fork-2-510155-brian-vaughns-projects.vercel.app?via=pr-comment-feedback-link) | Jan 14, 2025 3:53pm |



Patch:
diff --git a/packages/react-resizable-panels-website/index.html b/packages/react-resizable-panels-website/index.html
index 76d1682..003e131 100644
--- a/packages/react-resizable-panels-website/index.html
+++ b/packages/react-resizable-panels-website/index.html
@@ -1,13 +1,11 @@
 <!doctype html>
-<html>
-  <html>
-    <head>
-      <title>React components for resizable panels</title>
-      <meta name="viewport" content="width=device-width, initial-scale=1" />
-      <link rel="stylesheet" href="./root.css" />
-      <link rel="stylesheet" href="./CodeMirror.css" />
-    </head>
-  </html>
+<html lang="en">
+  <head>
+    <title>React components for resizable panels</title>
+    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    <link rel="stylesheet" href="./root.css" />
+    <link rel="stylesheet" href="./CodeMirror.css" />
+  </head>
   <body>
     <div id="root"></div>
     <script type="module" src="index.tsx"></script>
diff --git a/packages/react-resizable-panels-website/src/components/ResizeHandle.tsx b/packages/react-resizable-panels-website/src/components/ResizeHandle.tsx
index 38984b5..cedea53 100644
--- a/packages/react-resizable-panels-website/src/components/ResizeHandle.tsx
+++ b/packages/react-resizable-panels-website/src/components/ResizeHandle.tsx
@@ -1,11 +1,15 @@
-import { PanelResizeHandle } from "react-resizable-panels";
+import {
+  PanelResizeHandle,
+  PanelResizeHandleProps,
+} from "react-resizable-panels";
 
 import styles from "./ResizeHandle.module.css";
 
 export function ResizeHandle({
   className = "",
   id,
-}: {
+  ...rest
+}: PanelResizeHandleProps & {
   className?: string;
   id?: string;
 }) {
@@ -13,6 +17,7 @@ export function ResizeHandle({
     <PanelResizeHandle
       className={[styles.ResizeHandle, className].join(" ")}
       id={id}
+      {...rest}
     />
   );
 }
diff --git a/packages/react-resizable-panels-website/src/routes/examples/ExternalPersistence.tsx b/packages/react-resizable-panels-website/src/routes/examples/ExternalPersistence.tsx
index 7019bee..6b7226a 100644
--- a/packages/react-resizable-panels-website/src/routes/examples/ExternalPersistence.tsx
+++ b/packages/react-resizable-panels-website/src/routes/examples/ExternalPersistence.tsx
@@ -18,7 +18,7 @@ export default function ExternalPersistence() {
           <p>
             By default, a <code>PanelGroup</code> with an{" "}
             <code>autoSaveId</code> will store layout information in{" "}
-            <code>localStorage</code>. This example shows how the how to use the{" "}
+            <code>localStorage</code>. This example shows how to use the{" "}
             <code>storage</code> prop to override that behavior. For this demo,
             layout is saved as part of the URL hash.
           </p>
diff --git a/packages/react-resizable-panels/.eslintrc.cjs b/packages/react-resizable-panels/.eslintrc.cjs
index 4a3162a..9bbe4d1 100644
--- a/packages/react-resizable-panels/.eslintrc.cjs
+++ b/packages/react-resizable-panels/.eslintrc.cjs
@@ -6,16 +6,10 @@ module.exports = {
     project: "../../tsconfig.json",
     tsconfigRootDir: __dirname,
   },
-  plugins: ["@typescript-eslint", "no-restricted-imports", "react-hooks"],
+  plugins: ["@typescript-eslint", "react-hooks"],
   root: true,
   rules: {
     "@typescript-eslint/no-non-null-assertion": "error",
-    "no-restricted-imports": [
-      "error",
-      {
-        paths: ["react"],
-      },
-    ],
     "react-hooks/rules-of-hooks": "error",
     "react-hooks/exhaustive-deps": [
       "warn",
diff --git a/packages/react-resizable-panels/CHANGELOG.md b/packages/react-resizable-panels/CHANGELOG.md
index 567745e..257f4f7 100644
--- a/packages/react-resizable-panels/CHANGELOG.md
+++ b/packages/react-resizable-panels/CHANGELOG.md
@@ -1,5 +1,12 @@
 # Changelog
 
+# 2.1.8
+
+- [#463](https://github.com/bvaughn/react-resizable-panels/pull/463): Fix `aria-controls` attribute value for auto-generated ids
+- [#464](https://github.com/bvaughn/react-resizable-panels/pull/464): Fix duplicate type declarations for React
+- [#465](https://github.com/bvaughn/react-resizable-panels/pull/465): Add `onPointerDown`, `onPointerUp`, and `onClick` callbacks to `PanelResizeHandle` so users can implement double-click
+- [#466](https://github.com/bvaughn/react-resizable-panels/pull/466): Fix bad `removeEventListener` call that caused pointer state to get broken between pages/routes
+
 # 2.1.7
 
 - [#427](https://github.com/bvaughn/react-resizable-panels/pull/427): Stacking order checks also check for `SVGElement`s (bug fix)
diff --git a/packages/react-resizable-panels/package.json b/packages/react-resizable-panels/package.json
index 5ff738e..a0528ec 100644
--- a/packages/react-resizable-panels/package.json
+++ b/packages/react-resizable-panels/package.json
@@ -1,6 +1,6 @@
 {
   "name": "react-resizable-panels",
-  "version": "2.1.7",
+  "version": "2.1.8",
   "description": "React components for resizable panel groups/layouts",
   "author": "Brian Vaughn <brian.david.vaughn@gmail.com>",
   "license": "MIT",
@@ -79,7 +79,6 @@
     "@babel/plugin-proposal-nullish-coalescing-operator": "7.18.6",
     "@babel/plugin-proposal-optional-chaining": "7.21.0",
     "eslint": "^8.37.0",
-    "eslint-plugin-no-restricted-imports": "^0.0.0",
     "eslint-plugin-react-hooks": "^4.6.0",
     "jest": "^29.7.0",
     "jest-environment-jsdom": "^29.7.0",
diff --git a/packages/react-resizable-panels/src/Panel.ts b/packages/react-resizable-panels/src/Panel.ts
index 5873afc..ef2f442 100644
--- a/packages/react-resizable-panels/src/Panel.ts
+++ b/packages/react-resizable-panels/src/Panel.ts
@@ -13,7 +13,7 @@ import {
   useContext,
   useImperativeHandle,
   useRef,
-} from "./vendor/react";
+} from "react";
 
 export type PanelOnCollapse = () => void;
 export type PanelOnExpand = () => void;
@@ -235,7 +235,7 @@ export function PanelWithForwardedRef({
 
     children,
     className: classNameFromProps,
-    id: idFromProps,
+    id: panelId,
     style: {
       ...style,
       ...styleFromProps,
diff --git a/packages/react-resizable-panels/src/PanelGroup.ts b/packages/react-resizable-panels/src/PanelGroup.ts
index 82a8d17..4b7fb33 100644
--- a/packages/react-resizable-panels/src/PanelGroup.ts
+++ b/packages/react-resizable-panels/src/PanelGroup.ts
@@ -56,7 +56,7 @@ import {
   useMemo,
   useRef,
   useState,
-} from "./vendor/react";
+} from "react";
 
 const LOCAL_STORAGE_DEBOUNCE_INTERVAL = 100;
 
diff --git a/packages/react-resizable-panels/src/PanelGroupContext.ts b/packages/react-resizable-panels/src/PanelGroupContext.ts
index 02eef1a..6c51b70 100644
--- a/packages/react-resizable-panels/src/PanelGroupContext.ts
+++ b/packages/react-resizable-panels/src/PanelGroupContext.ts
@@ -1,5 +1,5 @@
 import { PanelConstraints, PanelData } from "./Panel";
-import { CSSProperties, createContext } from "./vendor/react";
+import { CSSProperties, createContext } from "react";
 
 // The "contextmenu" event is not supported as a PointerEvent in all browsers yet, so MouseEvent still need to be handled
 export type ResizeEvent = KeyboardEvent | PointerEvent | MouseEvent;
diff --git a/packages/react-resizable-panels/src/PanelResizeHandle.ts b/packages/react-resizable-panels/src/PanelResizeHandle.ts
index 5ee7c83..ac0ebc4 100644
--- a/packages/react-resizable-panels/src/PanelResizeHandle.ts
+++ b/packages/react-resizable-panels/src/PanelResizeHandle.ts
@@ -1,3 +1,15 @@
+import {
+  createElement,
+  CSSProperties,
+  HTMLAttributes,
+  PropsWithChildren,
+  ReactElement,
+  useContext,
+  useEffect,
+  useRef,
+  useState,
+} from "react";
+import { RESIZE_HANDLE_ATTRIBUTES } from "./constants";
 import useIsomorphicLayoutEffect from "./hooks/useIsomorphicEffect";
 import useUniqueId from "./hooks/useUniqueId";
 import { useWindowSplitterResizeHandlerBehavior } from "./hooks/useWindowSplitterBehavior";
@@ -12,24 +24,13 @@ import {
   ResizeHandlerAction,
 } from "./PanelResizeHandleRegistry";
 import { assert } from "./utils/assert";
-import {
-  createElement,
-  CSSProperties,
-  HTMLAttributes,
-  PropsWithChildren,
-  ReactElement,
-  useContext,
-  useEffect,
-  useRef,
-  useState,
-} from "./vendor/react";
 
 export type PanelResizeHandleOnDragging = (isDragging: boolean) => void;
 export type ResizeHandlerState = "drag" | "hover" | "inactive";
 
 export type PanelResizeHandleProps = Omit<
   HTMLAttributes<keyof HTMLElementTagNameMap>,
-  "id" | "onBlur" | "onFocus"
+  "id" | "onBlur" | "onClick" | "onFocus" | "onPointerDown" | "onPointerUp"
 > &
   PropsWithChildren<{
     className?: string;
@@ -37,8 +38,11 @@ export type PanelResizeHandleProps = Omit<
     hitAreaMargins?: PointerHitAreaMargins;
     id?: string | null;
     onBlur?: () => void;
+    onClick?: () => void;
     onDragging?: PanelResizeHandleOnDragging;
     onFocus?: () => void;
+    onPointerDown?: () => void;
+    onPointerUp?: () => void;
     style?: CSSProperties;
     tabIndex?: number;
     tagName?: keyof HTMLElementTagNameMap;
@@ -51,8 +55,11 @@ export function PanelResizeHandle({
   hitAreaMargins,
   id: idFromProps,
   onBlur,
+  onClick,
   onDragging,
   onFocus,
+  onPointerDown,
+  onPointerUp,
   style: styleFromProps = {},
   tabIndex = 0,
   tagName: Type = "div",
@@ -62,10 +69,16 @@ export function PanelResizeHandle({
 
   // Use a ref to guard against users passing inline props
   const callbacksRef = useRef<{
+    onClick: (() => void) | undefined;
     onDragging: PanelResizeHandleOnDragging | undefined;
-  }>({ onDragging });
+    onPointerDown: (() => void) | undefined;
+    onPointerUp: (() => void) | undefined;
+  }>({ onClick, onDragging, onPointerDown, onPointerUp });
   useEffect(() => {
+    callbacksRef.current.onClick = onClick;
     callbacksRef.current.onDragging = onDragging;
+    callbacksRef.current.onPointerDown = onPointerDown;
+    callbacksRef.current.onPointerUp = onPointerUp;
   });
 
   const panelGroupContext = useContext(PanelGroupContext);
@@ -126,52 +139,61 @@ export function PanelResizeHandle({
     const element = elementRef.current;
     assert(element, "Element ref not attached");
 
+    let didMove = false;
+
     const setResizeHandlerState = (
       action: ResizeHandlerAction,
       isActive: boolean,
       event: ResizeEvent | null
     ) => {
-      if (isActive) {
-        switch (action) {
-          case "down": {
-            setState("drag");
+      if (!isActive) {
+        setState("inactive");
+        return;
+      }
 
-            assert(event, 'Expected event to be defined for "down" action');
+      switch (action) {
+        case "down": {
+          setState("drag");
 
-            startDragging(resizeHandleId, event);
+          didMove = false;
 
-            const { onDragging } = callbacksRef.current;
-            if (onDragging) {
-              onDragging(true);
-            }
-            break;
-          }
-          case "move": {
-            const { state } = committedValuesRef.current;
+          assert(event, 'Expected event to be defined for "down" action');
+
+          startDragging(resizeHandleId, event);
 
-            if (state !== "drag") {
-              setState("hover");
-            }
+          const { onDragging, onPointerDown } = callbacksRef.current;
+          onDragging?.(true);
+          onPointerDown?.();
+          break;
+        }
+        case "move": {
+          const { state } = committedValuesRef.current;
 
-            assert(event, 'Expected event to be defined for "move" action');
+          didMove = true;
 
-            resizeHandler(event);
-            break;
-          }
-          case "up": {
+          if (state !== "drag") {
             setState("hover");
+          }
+
+          assert(event, 'Expected event to be defined for "move" action');
 
-            stopDragging();
+          resizeHandler(event);
+          break;
+        }
+        case "up": {
+          setState("hover");
+
+          stopDragging();
 
-            const { onDragging } = callbacksRef.current;
-            if (onDragging) {
-              onDragging(false);
-            }
-            break;
+          const { onClick, onDragging, onPointerUp } = callbacksRef.current;
+          onDragging?.(false);
+          onPointerUp?.();
+
+          if (!didMove) {
+            onClick?.();
           }
+          break;
         }
-      } else {
-        setState("inactive");
       }
     };
 
@@ -234,12 +256,12 @@ export function PanelResizeHandle({
     // CSS selectors
     "data-panel-group-direction": direction,
     "data-panel-group-id": groupId,
-    "data-resize-handle": "",
-    "data-resize-handle-active":
+    [RESIZE_HANDLE_ATTRIBUTES.root]: "",
+    [RESIZE_HANDLE_ATTRIBUTES.active]:
       state === "drag" ? "pointer" : isFocused ? "keyboard" : undefined,
-    "data-resize-handle-state": state,
-    "data-panel-resize-handle-enabled": !disabled,
-    "data-panel-resize-handle-id": resizeHandleId,
+    [RESIZE_HANDLE_ATTRIBUTES.state]: state,
+    [RESIZE_HANDLE_ATTRIBUTES.enabled]: !disabled,
+    [RESIZE_HANDLE_ATTRIBUTES.id]: resizeHandleId,
   });
 }
 
diff --git a/packages/react-resizable-panels/src/PanelResizeHandleRegistry.ts b/packages/react-resizable-panels/src/PanelResizeHandleRegistry.ts
index 3885a9a..304b5f6 100644
--- a/packages/react-resizable-panels/src/PanelResizeHandleRegistry.ts
+++ b/packages/react-resizable-panels/src/PanelResizeHandleRegistry.ts
@@ -1,3 +1,4 @@
+import { RESIZE_HANDLE_ATTRIBUTES } from "./constants";
 import { Direction, ResizeEvent } from "./types";
 import { resetGlobalCursorStyle, setGlobalCursorStyle } from "./utils/cursor";
 import { getResizeEventCoordinates } from "./utils/events/getResizeEventCoordinates";
@@ -104,7 +105,10 @@ function handlePointerDown(event: PointerEvent) {
     updateResizeHandlerStates("down", event);
 
     event.preventDefault();
-    event.stopPropagation();
+
+    if (!isWithinResizeHandle(target as HTMLElement)) {
+      event.stopImmediatePropagation();
+    }
   }
 }
 
@@ -147,6 +151,10 @@ function handlePointerUp(event: ResizeEvent) {
 
   if (intersectingHandles.length > 0) {
     event.preventDefault();
+
+    if (!isWithinResizeHandle(target as HTMLElement)) {
+      event.stopImmediatePropagation();
+    }
   }
 
   updateResizeHandlerStates("up", event);
@@ -156,6 +164,20 @@ function handlePointerUp(event: ResizeEvent) {
   updateListeners();
 }
 
+function isWithinResizeHandle(element: HTMLElement | null) {
+  let currentElement = element;
+
+  while (currentElement) {
+    if (currentElement.hasAttribute(RESIZE_HANDLE_ATTRIBUTES.root)) {
+      return true;
+    }
+
+    currentElement = currentElement.parentElement;
+  }
+
+  return false;
+}
+
 function recalculateIntersectingHandles({
   target,
   x,
@@ -278,47 +300,45 @@ function updateCursor() {
   }
 }
 
-function updateListeners() {
-  ownerDocumentCounts.forEach((_, ownerDocument) => {
-    const { body } = ownerDocument;
-
-    body.removeEventListener("contextmenu", handlePointerUp);
-    body.removeEventListener("pointerdown", handlePointerDown);
-    body.removeEventListener("pointerleave", handlePointerMove);
-    body.removeEventListener("pointermove", handlePointerMove);
-  });
+let listenersAbortController = new AbortController();
 
-  window.removeEventListener("pointerup", handlePointerUp);
-  window.removeEventListener("pointercancel", handlePointerUp);
+function updateListeners() {
+  listenersAbortController.abort();
+  listenersAbortController = new AbortController();
 
-  if (registeredResizeHandlers.size > 0) {
-    if (isPointerDown) {
-      if (intersectingHandles.length > 0) {
-        ownerDocumentCounts.forEach((count, ownerDocument) => {
-          const { body } = ownerDocument;
+  const options: AddEventListenerOptions = {
+    capture: true,
+    signal: listenersAbortController.signal,
+  };
 
-          if (count > 0) {
-            body.addEventListener("contextmenu", handlePointerUp);
-            body.addEventListener("pointerleave", handlePointerMove);
-            body.addEventListener("pointermove", handlePointerMove);
-          }
-        });
-      }
+  if (!registeredResizeHandlers.size) {
+    return;
+  }
 
-      window.addEventListener("pointerup", handlePointerUp);
-      window.addEventListener("pointercancel", handlePointerUp);
-    } else {
+  if (isPointerDown) {
+    if (intersectingHandles.length > 0) {
       ownerDocumentCounts.forEach((count, ownerDocument) => {
         const { body } = ownerDocument;
 
         if (count > 0) {
-          body.addEventListener("pointerdown", handlePointerDown, {
-            capture: true,
-          });
-          body.addEventListener("pointermove", handlePointerMove);
+          body.addEventListener("contextmenu", handlePointerUp, options);
+          body.addEventListener("pointerleave", handlePointerMove, options);
+          body.addEventListener("pointermove", handlePointerMove, options);
         }
       });
     }
+
+    window.addEventListener("pointerup", handlePointerUp, options);
+    window.addEventListener("pointercancel", handlePointerUp, options);
+  } else {
+    ownerDocumentCounts.forEach((count, ownerDocument) => {
+      const { body } = ownerDocument;
+
+      if (count > 0) {
+        body.addEventListener("pointerdown", handlePointerDown, options);
+        body.addEventListener("pointermove", handlePointerMove, options);
+      }
+    });
   }
 }
 
diff --git a/packages/react-resizable-panels/src/constants.ts b/packages/react-resizable-panels/src/constants.ts
index c81d2a8..0813228 100644
--- a/packages/react-resizable-panels/src/constants.ts
+++ b/packages/react-resizable-panels/src/constants.ts
@@ -1,1 +1,9 @@
 export const PRECISION = 10;
+
+export const RESIZE_HANDLE_ATTRIBUTES = {
+  root: "data-resize-handle",
+  active: "data-resize-handle-active",
+  state: "data-resize-handle-state",
+  enabled: "data-panel-resize-handle-enabled",
+  id: "data-panel-resize-handle-id",
+} as const;
diff --git a/packages/react-resizable-panels/src/hooks/useForceUpdate.ts b/packages/react-resizable-panels/src/hooks/useForceUpdate.ts
index 2f4c389..05c67ce 100644
--- a/packages/react-resizable-panels/src/hooks/useForceUpdate.ts
+++ b/packages/react-resizable-panels/src/hooks/useForceUpdate.ts
@@ -1,4 +1,4 @@
-import { useCallback, useState } from "../vendor/react";
+import { useCallback, useState } from "react";
 
 export function useForceUpdate() {
   const [_, setCount] = useState(0);
diff --git a/packages/react-resizable-panels/src/hooks/useIsomorphicEffect.ts b/packages/react-resizable-panels/src/hooks/useIsomorphicEffect.ts
index c1eac1b..af7bb75 100644
--- a/packages/react-resizable-panels/src/hooks/useIsomorphicEffect.ts
+++ b/packages/react-resizable-panels/src/hooks/useIsomorphicEffect.ts
@@ -1,8 +1,6 @@
 import { isBrowser } from "#is-browser";
-import { useLayoutEffect_do_not_use_directly } from "../vendor/react";
+import { useLayoutEffect as useLayoutEffectBrowser } from "react";
 
-const useIsomorphicLayoutEffect = isBrowser
-  ? useLayoutEffect_do_not_use_directly
-  : () => {};
+const useIsomorphicLayoutEffect = isBrowser ? useLayoutEffectBrowser : () => {};
 
 export default useIsomorphicLayoutEffect;
diff --git a/packages/react-resizable-panels/src/hooks/useUniqueId.ts b/packages/react-resizable-panels/src/hooks/useUniqueId.ts
index a3d49a8..bbe99c7 100644
--- a/packages/react-resizable-panels/src/hooks/useUniqueId.ts
+++ b/packages/react-resizable-panels/src/hooks/useUniqueId.ts
@@ -1,4 +1,7 @@
-import { useId, useRef } from "../vendor/react";
+import * as React from "react";
+import { useRef } from "react";
+
+const useId = (React as any)["useId".toString()] as (() => string) | undefined;
 
 const wrappedUseId: () => string | null =
   typeof useId === "function" ? useId : (): null => null;
diff --git a/packages/react-resizable-panels/src/hooks/useWindowSplitterBehavior.ts b/packages/react-resizable-panels/src/hooks/useWindowSplitterBehavior.ts
index b0123be..77808a9 100644
--- a/packages/react-resizable-panels/src/hooks/useWindowSplitterBehavior.ts
+++ b/packages/react-resizable-panels/src/hooks/useWindowSplitterBehavior.ts
@@ -3,7 +3,7 @@ import { assert } from "../utils/assert";
 import { getResizeHandleElement } from "../utils/dom/getResizeHandleElement";
 import { getResizeHandleElementIndex } from "../utils/dom/getResizeHandleElementIndex";
 import { getResizeHandleElementsForGroup } from "../utils/dom/getResizeHandleElementsForGroup";
-import { useEffect } from "../vendor/react";
+import { useEffect } from "react";
 
 // https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/
 
diff --git a/packages/react-resizable-panels/src/hooks/useWindowSplitterPanelGroupBehavior.ts b/packages/react-resizable-panels/src/hooks/useWindowSplitterPanelGroupBehavior.ts
index 4ec08cf..58fe7fb 100644
--- a/packages/react-resizable-panels/src/hooks/useWindowSplitterPanelGroupBehavior.ts
+++ b/packages/react-resizable-panels/src/hooks/useWindowSplitterPanelGroupBehavior.ts
@@ -1,4 +1,6 @@
 import { isDevelopment } from "#is-development";
+import { RefObject, useEffect, useRef } from "react";
+import { RESIZE_HANDLE_ATTRIBUTES } from "../constants";
 import { PanelData } from "../Panel";
 import { Direction } from "../types";
 import { adjustLayoutByDelta } from "../utils/adjustLayoutByDelta";
@@ -9,7 +11,6 @@ import { getPanelGroupElement } from "../utils/dom/getPanelGroupElement";
 import { getResizeHandleElementsForGroup } from "../utils/dom/getResizeHandleElementsForGroup";
 import { getResizeHandlePanelIds } from "../utils/dom/getResizeHandlePanelIds";
 import { fuzzyNumbersEqual } from "../utils/numbers/fuzzyNumbersEqual";
-import { RefObject, useEffect, useRef } from "../vendor/react";
 import useIsomorphicLayoutEffect from "./useIsomorphicEffect";
 
 // https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/
@@ -115,7 +116,7 @@ export function useWindowSplitterPanelGroupBehavior({
     assert(handles, `No resize handles found for group id "${groupId}"`);
 
     const cleanupFunctions = handles.map((handle) => {
-      const handleId = handle.getAttribute("data-panel-resize-handle-id");
+      const handleId = handle.getAttribute(RESIZE_HANDLE_ATTRIBUTES.id);
       assert(handleId, `Resize handle element has no handle id attribute`);
 
       const [idBefore, idAfter] = getResizeHandlePanelIds(
diff --git a/packages/react-resizable-panels/src/utils/computePanelFlexBoxStyle.ts b/packages/react-resizable-panels/src/utils/computePanelFlexBoxStyle.ts
index 2fba40b..65ba441 100644
--- a/packages/react-resizable-panels/src/utils/computePanelFlexBoxStyle.ts
+++ b/packages/react-resizable-panels/src/utils/computePanelFlexBoxStyle.ts
@@ -2,7 +2,7 @@
 
 import { PanelData } from "../Panel";
 import { DragState } from "../PanelGroupContext";
-import { CSSProperties } from "../vendor/react";
+import { CSSProperties } from "react";
 
 // the % of the group's overall space this panel should occupy.
 export function computePanelFlexBoxStyle({
diff --git a/packages/react-resizable-panels/src/utils/cursor.ts b/packages/react-resizable-panels/src/utils/cursor.ts
index de4f903..b438ac4 100644
--- a/packages/react-resizable-panels/src/utils/cursor.ts
+++ b/packages/react-resizable-panels/src/utils/cursor.ts
@@ -99,5 +99,5 @@ export function setGlobalCursorStyle(
     document.head.appendChild(styleElement);
   }
 
-  styleElement.innerHTML = `*{cursor: ${style}!important;}`;
+  styleElement.sheet?.insertRule(`*{cursor: ${style}!important;}`)
 }
diff --git a/packages/react-resizable-panels/src/utils/dom/getResizeHandleElement.ts b/packages/react-resizable-panels/src/utils/dom/getResizeHandleElement.ts
index 378d2d1..8b61263 100644
--- a/packages/react-resizable-panels/src/utils/dom/getResizeHandleElement.ts
+++ b/packages/react-resizable-panels/src/utils/dom/getResizeHandleElement.ts
@@ -1,8 +1,12 @@
+import { RESIZE_HANDLE_ATTRIBUTES } from "../../constants";
+
 export function getResizeHandleElement(
   id: string,
   scope: ParentNode | HTMLElement = document
 ): HTMLElement | null {
-  const element = scope.querySelector(`[data-panel-resize-handle-id="${id}"]`);
+  const element = scope.querySelector(
+    `[${RESIZE_HANDLE_ATTRIBUTES.id}="${id}"]`
+  );
   if (element) {
     return element as HTMLElement;
   }
diff --git a/packages/react-resizable-panels/src/utils/dom/getResizeHandleElementIndex.ts b/packages/react-resizable-panels/src/utils/dom/getResizeHandleElementIndex.ts
index d4a1f2d..b4625c3 100644
--- a/packages/react-resizable-panels/src/utils/dom/getResizeHandleElementIndex.ts
+++ b/packages/react-resizable-panels/src/utils/dom/getResizeHandleElementIndex.ts
@@ -1,3 +1,4 @@
+import { RESIZE_HANDLE_ATTRIBUTES } from "../../constants";
 import { getResizeHandleElementsForGroup } from "./getResizeHandleElementsForGroup";
 
 export function getResizeHandleElementIndex(
@@ -7,7 +8,7 @@ export function getResizeHandleElementIndex(
 ): number | null {
   const handles = getResizeHandleElementsForGroup(groupId, scope);
   const index = handles.findIndex(
-    (handle) => handle.getAttribute("data-panel-resize-handle-id") === id
+    (handle) => handle.getAttribute(RESIZE_HANDLE_ATTRIBUTES.id) === id
   );
   return index ?? null;
 }
diff --git a/packages/react-resizable-panels/src/utils/dom/getResizeHandleElementsForGroup.ts b/packages/react-resizable-panels/src/utils/dom/getResizeHandleElementsForGroup.ts
index 3082f9c..1cb33b5 100644
--- a/packages/react-resizable-panels/src/utils/dom/getResizeHandleElementsForGroup.ts
+++ b/packages/react-resizable-panels/src/utils/dom/getResizeHandleElementsForGroup.ts
@@ -1,10 +1,12 @@
+import { RESIZE_HANDLE_ATTRIBUTES } from "../../constants";
+
 export function getResizeHandleElementsForGroup(
   groupId: string,
   scope: ParentNode | HTMLElement = document
 ): HTMLElement[] {
   return Array.from(
     scope.querySelectorAll(
-      `[data-panel-resize-handle-id][data-panel-group-id="${groupId}"]`
+      `[${RESIZE_HANDLE_ATTRIBUTES.id}][data-panel-group-id="${groupId}"]`
     )
   );
 }
diff --git a/packages/react-resizable-panels/src/vendor/react.ts b/packages/react-resizable-panels/src/vendor/react.ts
deleted file mode 100644
index df7315d..0000000
--- a/packages/react-resizable-panels/src/vendor/react.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-// This module exists to work around Webpack issue https://github.com/webpack/webpack/issues/14814
-// and limitations with ParcelJS parsing of the useId workaround (used below).
-// For the time being, all react-resizable-panels must import "react" with the "* as React" syntax.
-// To avoid mistakes, we use the ESLint "no-restricted-imports" to prevent "react" imports except in this file.
-// See https://github.com/bvaughn/react-resizable-panels/issues/118
-
-// eslint-disable-next-line no-restricted-imports
-import * as React from "react";
-
-// eslint-disable-next-line no-restricted-imports
-import type {
-  CSSProperties,
-  ElementType,
-  ForwardedRef,
-  HTMLAttributes,
-  MouseEvent,
-  PropsWithChildren,
-  ReactElement,
-  ReactNode,
-  RefObject,
-  TouchEvent,
-} from "react";
-
-const {
-  createElement,
-  createContext,
-  createRef,
-  forwardRef,
-  useCallback,
-  useContext,
-  useEffect,
-  useImperativeHandle,
-  useLayoutEffect,
-  useMemo,
-  useRef,
-  useState,
-} = React;
-
-// `Math.random()` and `.slice(0, 5)` prevents bundlers from trying to `import { useId } from 'react'`
-const useId = (React as any)[
-  `useId${Math.random()}`.slice(0, 5)
-] as () => string;
-
-const useLayoutEffect_do_not_use_directly = useLayoutEffect;
-
-export {
-  createElement,
-  createContext,
-  createRef,
-  forwardRef,
-  useCallback,
-  useContext,
-  useEffect,
-  useId,
-  useImperativeHandle,
-  useLayoutEffect_do_not_use_directly,
-  useMemo,
-  useRef,
-  useState,
-};
-
-export type {
-  CSSProperties,
-  ElementType,
-  ForwardedRef,
-  HTMLAttributes,
-  MouseEvent,
-  PropsWithChildren,
-  ReactElement,
-  ReactNode,
-  RefObject,
-  TouchEvent,
-};
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f430d46..bb39343 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -87,9 +87,6 @@ importers:
       eslint:
         specifier: ^8.37.0
         version: 8.47.0
-      eslint-plugin-no-restricted-imports:
-        specifier: ^0.0.0
-        version: 0.0.0
       eslint-plugin-react-hooks:
         specifier: ^4.6.0
         version: 4.6.0(eslint@8.47.0)
@@ -3175,13 +3172,6 @@ packages:
       source-map: 0.6.1
     dev: true
 
-  /eslint-plugin-no-restricted-imports@0.0.0:
-    resolution: {integrity: sha512-sxHk6vmXheLagzJGKqq93u9sz+xXLyuXcW4ZCPrOMQ0PL365PyJyhaKRWYgMgPZs1YhBmhyopdqO62AlGz+jSQ==}
-    engines: {node: '>=0.10.0'}
-    dependencies:
-      requireindex: 1.1.0
-    dev: true
-
   /eslint-plugin-react-hooks@4.6.0(eslint@8.47.0):
     resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==}
     engines: {node: '>=10'}
@@ -4952,9 +4942,6 @@ packages:
     resolution: {integrity: sha512-2GTVocFkwblV/TIg9AmT7TI2fO4xdWkyN8aFUEVtiVNWt96GTR3FgQyHFValfCbcj1k9Xf962Ws2hYXYUr9k1Q==}
     engines: {node: '>= 12.0.0'}
     hasBin: true
-    peerDependenciesMeta:
-      '@parcel/core':
-        optional: true
     dependencies:
       '@parcel/config-default': 2.9.3(@parcel/core@2.9.3)
       '@parcel/core': 2.9.3
@@ -5262,11 +5249,6 @@ packages:
     engines: {node: '>=0.10.0'}
     dev: true
 
-  /requireindex@1.1.0:
-    resolution: {integrity: sha512-LBnkqsDE7BZKvqylbmn7lTIVdpx4K/QCduRATpO5R+wtPmky/a8pN1bO2D6wXppn1497AJF9mNjqAXr6bdl9jg==}
-    engines: {node: '>=0.10.5'}
-    dev: true
-
   /requires-port@1.0.0:
     resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
     dev: true


Test patch:
diff --git a/packages/react-resizable-panels/src/Panel.test.tsx b/packages/react-resizable-panels/src/Panel.test.tsx
index 647df06..f1cf41a 100644
--- a/packages/react-resizable-panels/src/Panel.test.tsx
+++ b/packages/react-resizable-panels/src/Panel.test.tsx
@@ -8,7 +8,7 @@ import {
   verifyAttribute,
   verifyExpandedPanelGroupLayout,
 } from "./utils/test-utils";
-import { createRef } from "./vendor/react";
+import { createRef } from "react";
 
 describe("PanelGroup", () => {
   let expectedWarnings: string[] = [];
@@ -1012,7 +1012,7 @@ describe("PanelGroup", () => {
       expect(element?.getAttribute("id")).toBe("explicit-id");
     });
 
-    it("should not pass auto-generated id prop to DOM", () => {
+    it("should pass auto-generated id prop to DOM", () => {
       act(() => {
         root.render(
           <PanelGroup direction="horizontal">
@@ -1024,7 +1024,7 @@ describe("PanelGroup", () => {
       const element = container.querySelector("[data-panel]");
 
       expect(element).not.toBeNull();
-      expect(element?.getAttribute("id")).toBeNull();
+      expect(element?.getAttribute("id")).not.toBeNull();
     });
   });
 
diff --git a/packages/react-resizable-panels/src/PanelGroup.test.tsx b/packages/react-resizable-panels/src/PanelGroup.test.tsx
index 223c65c..4f71c92 100644
--- a/packages/react-resizable-panels/src/PanelGroup.test.tsx
+++ b/packages/react-resizable-panels/src/PanelGroup.test.tsx
@@ -17,7 +17,7 @@ import {
   mockPanelGroupOffsetWidthAndHeight,
   verifyAttribute,
 } from "./utils/test-utils";
-import { createRef } from "./vendor/react";
+import { createRef } from "react";
 
 describe("PanelGroup", () => {
   let expectedWarnings: string[] = [];
diff --git a/packages/react-resizable-panels/src/PanelResizeHandle.test.tsx b/packages/react-resizable-panels/src/PanelResizeHandle.test.tsx
index e7c3e71..1611bdc 100644
--- a/packages/react-resizable-panels/src/PanelResizeHandle.test.tsx
+++ b/packages/react-resizable-panels/src/PanelResizeHandle.test.tsx
@@ -6,6 +6,7 @@ import {
   PanelResizeHandle,
   type PanelResizeHandleProps,
 } from ".";
+import { RESIZE_HANDLE_ATTRIBUTES } from "./constants";
 import { assert } from "./utils/assert";
 import * as cursorUtils from "./utils/cursor";
 import { getResizeHandleElement } from "./utils/dom/getResizeHandleElement";
@@ -129,61 +130,131 @@ describe("PanelResizeHandle", () => {
   }
 
   describe("callbacks", () => {
-    describe("onDragging", () => {
-      it("should fire when dragging starts/stops", () => {
-        const onDragging = jest.fn();
-
-        const { leftElement } = setupMockedGroup({
-          leftProps: { onDragging },
-        });
-
-        act(() => {
-          dispatchPointerEvent("pointermove", leftElement);
-        });
-        expect(onDragging).not.toHaveBeenCalled();
-
-        act(() => {
-          dispatchPointerEvent("pointerdown", leftElement);
-        });
-        expect(onDragging).toHaveBeenCalledTimes(1);
-        expect(onDragging).toHaveBeenCalledWith(true);
-
-        act(() => {
-          dispatchPointerEvent("pointerup", leftElement);
-        });
-        expect(onDragging).toHaveBeenCalledTimes(2);
-        expect(onDragging).toHaveBeenCalledWith(false);
+    it("should fire when dragging starts/stops", () => {
+      const onDragging = jest.fn();
+      const onPointerDown = jest.fn();
+      const onPointerUp = jest.fn();
+
+      const { leftElement } = setupMockedGroup({
+        leftProps: { onDragging, onPointerDown, onPointerUp },
+      });
+
+      act(() => {
+        dispatchPointerEvent("pointermove", leftElement);
+      });
+      expect(onPointerDown).not.toHaveBeenCalled();
+      expect(onDragging).not.toHaveBeenCalled();
+      expect(onPointerUp).not.toHaveBeenCalled();
+
+      act(() => {
+        dispatchPointerEvent("pointerdown", leftElement);
+      });
+      expect(onPointerDown).toHaveBeenCalledTimes(1);
+      expect(onDragging).toHaveBeenCalledTimes(1);
+      expect(onDragging).toHaveBeenCalledWith(true);
+      expect(onPointerUp).not.toHaveBeenCalled();
+
+      act(() => {
+        dispatchPointerEvent("pointerup", leftElement);
+      });
+      expect(onPointerDown).toHaveBeenCalledTimes(1);
+      expect(onDragging).toHaveBeenCalledTimes(2);
+      expect(onDragging).toHaveBeenCalledWith(false);
+      expect(onPointerUp).toHaveBeenCalledTimes(1);
+    });
+
+    it("should fire on-click if not dragged", () => {
+      const onClick = jest.fn();
+      const onDragging = jest.fn();
+
+      const { leftElement } = setupMockedGroup({
+        leftProps: { onClick, onDragging },
       });
 
-      it("should only fire for the handle that has been dragged", () => {
-        const onDraggingLeft = jest.fn();
-        const onDraggingRight = jest.fn();
-
-        const { leftElement } = setupMockedGroup({
-          leftProps: { onDragging: onDraggingLeft },
-          rightProps: { onDragging: onDraggingRight },
-        });
-
-        act(() => {
-          dispatchPointerEvent("pointermove", leftElement);
-        });
-        expect(onDraggingLeft).not.toHaveBeenCalled();
-        expect(onDraggingRight).not.toHaveBeenCalled();
-
-        act(() => {
-          dispatchPointerEvent("pointerdown", leftElement);
-        });
-        expect(onDraggingLeft).toHaveBeenCalledTimes(1);
-        expect(onDraggingLeft).toHaveBeenCalledWith(true);
-        expect(onDraggingRight).not.toHaveBeenCalled();
-
-        act(() => {
-          dispatchPointerEvent("pointerup", leftElement);
-        });
-        expect(onDraggingLeft).toHaveBeenCalledTimes(2);
-        expect(onDraggingLeft).toHaveBeenCalledWith(false);
-        expect(onDraggingRight).not.toHaveBeenCalled();
+      act(() => {
+        dispatchPointerEvent("pointermove", leftElement);
       });
+      expect(onDragging).not.toHaveBeenCalled();
+      expect(onClick).not.toHaveBeenCalled();
+
+      act(() => {
+        dispatchPointerEvent("pointerdown", leftElement);
+      });
+      expect(onDragging).toHaveBeenCalledTimes(1);
+      expect(onDragging).toHaveBeenCalledWith(true);
+      expect(onClick).not.toHaveBeenCalled();
+
+      act(() => {
+        dispatchPointerEvent("pointerup", leftElement);
+      });
+      expect(onDragging).toHaveBeenCalledTimes(2);
+      expect(onDragging).toHaveBeenCalledWith(false);
+      expect(onClick).toHaveBeenCalledTimes(1);
+    });
+
+    it("should not fire on-click if dragged", () => {
+      const onClick = jest.fn();
+      const onDragging = jest.fn();
+
+      const { leftElement } = setupMockedGroup({
+        leftProps: { onClick, onDragging },
+      });
+
+      act(() => {
+        dispatchPointerEvent("pointermove", leftElement);
+      });
+      expect(onDragging).not.toHaveBeenCalled();
+      expect(onClick).not.toHaveBeenCalled();
+
+      act(() => {
+        dispatchPointerEvent("pointerdown", leftElement);
+      });
+      expect(onDragging).toHaveBeenCalledTimes(1);
+      expect(onDragging).toHaveBeenCalledWith(true);
+      expect(onClick).not.toHaveBeenCalled();
+
+      act(() => {
+        dispatchPointerEvent("pointermove", leftElement);
+      });
+      expect(onDragging).toHaveBeenCalledTimes(1);
+      expect(onClick).not.toHaveBeenCalled();
+
+      act(() => {
+        dispatchPointerEvent("pointerup", leftElement);
+      });
+      expect(onDragging).toHaveBeenCalledTimes(2);
+      expect(onDragging).toHaveBeenCalledWith(false);
+      expect(onClick).not.toHaveBeenCalled();
+    });
+
+    it("should only fire for the handle that has been dragged", () => {
+      const onDraggingLeft = jest.fn();
+      const onDraggingRight = jest.fn();
+
+      const { leftElement } = setupMockedGroup({
+        leftProps: { onDragging: onDraggingLeft },
+        rightProps: { onDragging: onDraggingRight },
+      });
+
+      act(() => {
+        dispatchPointerEvent("pointermove", leftElement);
+      });
+      expect(onDraggingLeft).not.toHaveBeenCalled();
+      expect(onDraggingRight).not.toHaveBeenCalled();
+
+      act(() => {
+        dispatchPointerEvent("pointerdown", leftElement);
+      });
+      expect(onDraggingLeft).toHaveBeenCalledTimes(1);
+      expect(onDraggingLeft).toHaveBeenCalledWith(true);
+      expect(onDraggingRight).not.toHaveBeenCalled();
+
+      act(() => {
+        dispatchPointerEvent("pointerup", leftElement);
+      });
+      expect(onDraggingLeft).toHaveBeenCalledTimes(2);
+      expect(onDraggingLeft).toHaveBeenCalledWith(false);
+      expect(onDraggingRight).not.toHaveBeenCalled();
     });
   });
 
@@ -192,92 +263,88 @@ describe("PanelResizeHandle", () => {
       const { leftElement, rightElement } = setupMockedGroup();
 
       verifyAttribute(leftElement, "data-panel-group-id", "test-group");
-      verifyAttribute(leftElement, "data-resize-handle", "");
+      verifyAttribute(leftElement, RESIZE_HANDLE_ATTRIBUTES.root, "");
       verifyAttribute(leftElement, "data-panel-group-direction", "horizontal");
-      verifyAttribute(leftElement, "data-panel-resize-handle-enabled", "true");
-      verifyAttribute(
-        leftElement,
-        "data-panel-resize-handle-id",
-        "handle-left"
-      );
+      verifyAttribute(leftElement, RESIZE_HANDLE_ATTRIBUTES.enabled, "true");
+      verifyAttribute(leftElement, RESIZE_HANDLE_ATTRIBUTES.id, "handle-left");
 
       verifyAttribute(rightElement, "data-panel-group-id", "test-group");
-      verifyAttribute(rightElement, "data-resize-handle", "");
+      verifyAttribute(rightElement, RESIZE_HANDLE_ATTRIBUTES.root, "");
       verifyAttribute(rightElement, "data-panel-group-direction", "horizontal");
-      verifyAttribute(rightElement, "data-panel-resize-handle-enabled", "true");
+      verifyAttribute(rightElement, RESIZE_HANDLE_ATTRIBUTES.enabled, "true");
       verifyAttribute(
         rightElement,
-        "data-panel-resize-handle-id",
+        RESIZE_HANDLE_ATTRIBUTES.id,
         "handle-right"
       );
     });
 
-    it("should update data-resize-handle-active and data-resize-handle-state when dragging starts/stops", () => {
+    it(`should update ${RESIZE_HANDLE_ATTRIBUTES.active} and ${RESIZE_HANDLE_ATTRIBUTES.state} when dragging starts/stops`, () => {
       const { leftElement, rightElement } = setupMockedGroup();
-      verifyAttribute(leftElement, "data-resize-handle-active", null);
-      verifyAttribute(rightElement, "data-resize-handle-active", null);
-      verifyAttribute(leftElement, "data-resize-handle-state", "inactive");
-      verifyAttribute(rightElement, "data-resize-handle-state", "inactive");
+      verifyAttribute(leftElement, RESIZE_HANDLE_ATTRIBUTES.active, null);
+      verifyAttribute(rightElement, RESIZE_HANDLE_ATTRIBUTES.active, null);
+      verifyAttribute(leftElement, RESIZE_HANDLE_ATTRIBUTES.state, "inactive");
+      verifyAttribute(rightElement, RESIZE_HANDLE_ATTRIBUTES.state, "inactive");
 
       act(() => {
         dispatchPointerEvent("pointermove", leftElement);
       });
-      verifyAttribute(leftElement, "data-resize-handle-active", null);
-      verifyAttribute(rightElement, "data-resize-handle-active", null);
-      verifyAttribute(leftElement, "data-resize-handle-state", "hover");
-      verifyAttribute(rightElement, "data-resize-handle-state", "inactive");
+      verifyAttribute(leftElement, RESIZE_HANDLE_ATTRIBUTES.active, null);
+      verifyAttribute(rightElement, RESIZE_HANDLE_ATTRIBUTES.active, null);
+      verifyAttribute(leftElement, RESIZE_HANDLE_ATTRIBUTES.state, "hover");
+      verifyAttribute(rightElement, RESIZE_HANDLE_ATTRIBUTES.state, "inactive");
 
       act(() => {
         dispatchPointerEvent("pointerdown", leftElement);
       });
-      verifyAttribute(leftElement, "data-resize-handle-active", "pointer");
-      verifyAttribute(rightElement, "data-resize-handle-active", null);
-      verifyAttribute(leftElement, "data-resize-handle-state", "drag");
-      verifyAttribute(rightElement, "data-resize-handle-state", "inactive");
+      verifyAttribute(leftElement, RESIZE_HANDLE_ATTRIBUTES.active, "pointer");
+      verifyAttribute(rightElement, RESIZE_HANDLE_ATTRIBUTES.active, null);
+      verifyAttribute(leftElement, RESIZE_HANDLE_ATTRIBUTES.state, "drag");
+      verifyAttribute(rightElement, RESIZE_HANDLE_ATTRIBUTES.state, "inactive");
 
       act(() => {
         dispatchPointerEvent("pointermove", leftElement);
       });
-      verifyAttribute(leftElement, "data-resize-handle-active", "pointer");
-      verifyAttribute(rightElement, "data-resize-handle-active", null);
-      verifyAttribute(leftElement, "data-resize-handle-state", "drag");
-      verifyAttribute(rightElement, "data-resize-handle-state", "inactive");
+      verifyAttribute(leftElement, RESIZE_HANDLE_ATTRIBUTES.active, "pointer");
+      verifyAttribute(rightElement, RESIZE_HANDLE_ATTRIBUTES.active, null);
+      verifyAttribute(leftElement, RESIZE_HANDLE_ATTRIBUTES.state, "drag");
+      verifyAttribute(rightElement, RESIZE_HANDLE_ATTRIBUTES.state, "inactive");
 
       act(() => {
         dispatchPointerEvent("pointerup", leftElement);
       });
-      verifyAttribute(leftElement, "data-resize-handle-active", null);
-      verifyAttribute(rightElement, "data-resize-handle-active", null);
-      verifyAttribute(leftElement, "data-resize-handle-state", "hover");
-      verifyAttribute(rightElement, "data-resize-handle-state", "inactive");
+      verifyAttribute(leftElement, RESIZE_HANDLE_ATTRIBUTES.active, null);
+      verifyAttribute(rightElement, RESIZE_HANDLE_ATTRIBUTES.active, null);
+      verifyAttribute(leftElement, RESIZE_HANDLE_ATTRIBUTES.state, "hover");
+      verifyAttribute(rightElement, RESIZE_HANDLE_ATTRIBUTES.state, "inactive");
 
       act(() => {
         dispatchPointerEvent("pointermove", rightElement);
       });
-      verifyAttribute(leftElement, "data-resize-handle-active", null);
-      verifyAttribute(rightElement, "data-resize-handle-active", null);
-      verifyAttribute(leftElement, "data-resize-handle-state", "inactive");
-      verifyAttribute(rightElement, "data-resize-handle-state", "hover");
+      verifyAttribute(leftElement, RESIZE_HANDLE_ATTRIBUTES.active, null);
+      verifyAttribute(rightElement, RESIZE_HANDLE_ATTRIBUTES.active, null);
+      verifyAttribute(leftElement, RESIZE_HANDLE_ATTRIBUTES.state, "inactive");
+      verifyAttribute(rightElement, RESIZE_HANDLE_ATTRIBUTES.state, "hover");
     });
 
-    it("should update data-resize-handle-active when focused", () => {
+    it(`should update ${RESIZE_HANDLE_ATTRIBUTES.active} when focused`, () => {
       const { leftElement, rightElement } = setupMockedGroup();
-      verifyAttribute(leftElement, "data-resize-handle-active", null);
-      verifyAttribute(rightElement, "data-resize-handle-active", null);
+      verifyAttribute(leftElement, RESIZE_HANDLE_ATTRIBUTES.active, null);
+      verifyAttribute(rightElement, RESIZE_HANDLE_ATTRIBUTES.active, null);
 
       act(() => {
         leftElement.focus();
       });
       expect(document.activeElement).toBe(leftElement);
-      verifyAttribute(leftElement, "data-resize-handle-active", "keyboard");
-      verifyAttribute(rightElement, "data-resize-handle-active", null);
+      verifyAttribute(leftElement, RESIZE_HANDLE_ATTRIBUTES.active, "keyboard");
+      verifyAttribute(rightElement, RESIZE_HANDLE_ATTRIBUTES.active, null);
 
       act(() => {
         leftElement.blur();
       });
       expect(document.activeElement).not.toBe(leftElement);
-      verifyAttribute(leftElement, "data-resize-handle-active", null);
-      verifyAttribute(rightElement, "data-resize-handle-active", null);
+      verifyAttribute(leftElement, RESIZE_HANDLE_ATTRIBUTES.active, null);
+      verifyAttribute(rightElement, RESIZE_HANDLE_ATTRIBUTES.active, null);
     });
   });
 
@@ -293,7 +360,9 @@ describe("PanelResizeHandle", () => {
         );
       });
 
-      const element = container.querySelector("[data-resize-handle]");
+      const element = container.querySelector(
+        `[${RESIZE_HANDLE_ATTRIBUTES.root}]`
+      );
 
       expect(element).not.toBeNull();
       expect(element?.getAttribute("id")).toBe("explicit-id");
@@ -310,7 +379,9 @@ describe("PanelResizeHandle", () => {
         );
       });
 
-      const element = container.querySelector("[data-resize-handle]");
+      const element = container.querySelector(
+        `[${RESIZE_HANDLE_ATTRIBUTES.root}]`
+      );
 
       expect(element).not.toBeNull();
       expect(element?.getAttribute("id")).toBeNull();
diff --git a/packages/react-resizable-panels/src/utils/test-utils.ts b/packages/react-resizable-panels/src/utils/test-utils.ts
index faa87b9..5335cf4 100644
--- a/packages/react-resizable-panels/src/utils/test-utils.ts
+++ b/packages/react-resizable-panels/src/utils/test-utils.ts
@@ -1,3 +1,4 @@
+import { RESIZE_HANDLE_ATTRIBUTES } from "../constants";
 import { assert } from "./assert";
 
 const util = require("util");
@@ -98,7 +99,7 @@ export function mockPanelGroupOffsetWidthAndHeight(
   Object.defineProperty(HTMLElement.prototype, "offsetHeight", {
     configurable: true,
     get: function () {
-      if (this.hasAttribute("data-resize-handle")) {
+      if (this.hasAttribute(RESIZE_HANDLE_ATTRIBUTES.root)) {
         return 0;
       } else if (this.hasAttribute("data-panel-group")) {
         return mockHeight;
@@ -109,7 +110,7 @@ export function mockPanelGroupOffsetWidthAndHeight(
   Object.defineProperty(HTMLElement.prototype, "offsetWidth", {
     configurable: true,
     get: function () {
-      if (this.hasAttribute("data-resize-handle")) {
+      if (this.hasAttribute(RESIZE_HANDLE_ATTRIBUTES.root)) {
         return 0;
       } else if (this.hasAttribute("data-panel-group")) {
         return mockWidth;