(
- ({ onMouseDown, onTouchStart, ...props }, ref) => {
- const id = useId();
- const { direction, panelRefs, addPanelRef } = useFlexyPanelsContext();
- const { handleMouseDown: handleDragMouseDown, handleTouchStart: handleDragTouchStart } = usePanelDrag({
- direction,
- panelRefs,
- handleId: id,
- });
+export const FlexyPanelHandle = forwardRef<
+ HTMLDivElement,
+ FlexyPanelHandleProps
+>(({ onMouseDown, onTouchStart, ...props }, ref) => {
+ const id = useId();
+ const { direction } = useFlexyPanelsContext();
+ const {
+ handleMouseDown: handleDragMouseDown,
+ handleTouchStart: handleDragTouchStart,
+ } = usePanelDrag({
+ direction,
+ handleId: id,
+ });
- const handleMouseDown = useCallback(
- (e: React.MouseEvent
) => {
- handleDragMouseDown(e);
- onMouseDown?.(e);
- },
- [handleDragMouseDown, onMouseDown]
- );
+ const handleMouseDown = useCallback(
+ (e: React.MouseEvent) => {
+ handleDragMouseDown(e);
+ onMouseDown?.(e);
+ },
+ [handleDragMouseDown, onMouseDown]
+ );
- const handleTouchStartEvent = useCallback(
- (e: React.TouchEvent) => {
- handleDragTouchStart(e);
- onTouchStart?.(e);
- },
- [handleDragTouchStart, onTouchStart]
- );
+ const handleTouchStartEvent = useCallback(
+ (e: React.TouchEvent) => {
+ handleDragTouchStart(e);
+ onTouchStart?.(e);
+ },
+ [handleDragTouchStart, onTouchStart]
+ );
- // Store the forwarded ref in a ref to avoid dependency issues
- const forwardedRef = useRef(ref);
- useEffect(() => {
- forwardedRef.current = ref;
- }, [ref]);
+ // Store the forwarded ref in a ref to avoid dependency issues
+ const forwardedRef = useRef(ref);
+ useEffect(() => {
+ forwardedRef.current = ref;
+ }, [ref]);
- const setRef = useCallback(
- (node: HTMLDivElement | null) => {
- // Call addPanelRef to register the element
- if (node) {
- addPanelRef(node);
- }
- // Forward the ref if provided
- const currentRef = forwardedRef.current;
- if (typeof currentRef === "function") {
- currentRef(node);
- } else if (currentRef) {
- currentRef.current = node;
- }
- },
- [addPanelRef]
- );
+ const setRef = useCallback((node: HTMLDivElement | null) => {
+ // Forward the ref if provided
+ const currentRef = forwardedRef.current;
+ if (typeof currentRef === "function") {
+ currentRef(node);
+ } else if (currentRef) {
+ currentRef.current = node;
+ }
+ }, []);
- return (
-
- );
- }
-);
+ return (
+
+ );
+});
FlexyPanelHandle.displayName = "FlexyPanelHandle";
diff --git a/packages/react-flexy-panels/src/contexts/FlexyPanelContext.ts b/packages/react-flexy-panels/src/contexts/FlexyPanelContext.ts
index 8e0d100..d7c89e0 100644
--- a/packages/react-flexy-panels/src/contexts/FlexyPanelContext.ts
+++ b/packages/react-flexy-panels/src/contexts/FlexyPanelContext.ts
@@ -3,12 +3,6 @@ import { Direction } from "../types";
export const FlexyPanelsContext = createContext<{
direction: Direction;
- panelRefs: Array;
- addPanelRef: (ref: HTMLDivElement) => void;
}>({
direction: "horizontal",
- panelRefs: [],
- addPanelRef: () => {
- return;
- },
});
diff --git a/packages/react-flexy-panels/src/hooks/usePanelDrag.ts b/packages/react-flexy-panels/src/hooks/usePanelDrag.ts
index e948d35..fc578d2 100644
--- a/packages/react-flexy-panels/src/hooks/usePanelDrag.ts
+++ b/packages/react-flexy-panels/src/hooks/usePanelDrag.ts
@@ -1,36 +1,37 @@
import { Direction } from "../types";
-import { updatePanelSizes } from "../utils";
+import { findAdjacentPanels, updatePanelSizes } from "../utils";
import { useCallback, useEffect, useRef, useState } from "react";
type UsePanelDragOptions = {
direction: Direction;
- panelRefs: Array;
handleId: string;
};
/**
* Custom hook to handle panel dragging logic
*/
-export const usePanelDrag = ({
- direction,
- panelRefs,
- handleId,
-}: UsePanelDragOptions) => {
+export const usePanelDrag = ({ direction, handleId }: UsePanelDragOptions) => {
const [isDragging, setIsDragging] = useState(false);
const dragStartRef = useRef(0);
+ const handleElementRef = useRef(null);
const updateDragPosition = useCallback(
(clientX: number, clientY: number) => {
const dragCurrent = direction === "horizontal" ? clientX : clientY;
const dragDelta = dragCurrent - dragStartRef.current;
- const handleIndex = panelRefs.findIndex((ref) => ref.id === handleId);
- if (handleIndex === -1) {
+ // Find handle element if not cached
+ if (!handleElementRef.current) {
+ handleElementRef.current = document.getElementById(handleId);
+ }
+
+ if (!handleElementRef.current) {
return;
}
- const panel1 = panelRefs[handleIndex - 1];
- const panel2 = panelRefs[handleIndex + 1];
+ // Find adjacent panels using DOM traversal
+ const { panel1, panel2 } = findAdjacentPanels(handleElementRef.current);
+
if (!panel1 || !panel2) {
return;
}
@@ -43,7 +44,7 @@ export const usePanelDrag = ({
});
dragStartRef.current = dragCurrent;
},
- [direction, handleId, panelRefs]
+ [direction, handleId]
);
const onDrag = useCallback(
@@ -71,6 +72,8 @@ export const usePanelDrag = ({
const handleMouseDown = useCallback(
(e: React.MouseEvent) => {
+ // Cache handle element reference on drag start
+ handleElementRef.current = e.currentTarget;
setIsDragging(true);
dragStartRef.current = direction === "horizontal" ? e.clientX : e.clientY;
},
@@ -80,9 +83,12 @@ export const usePanelDrag = ({
const handleTouchStart = useCallback(
(e: React.TouchEvent) => {
if (e.touches.length > 0) {
+ // Cache handle element reference on drag start
+ handleElementRef.current = e.currentTarget;
setIsDragging(true);
const touch = e.touches[0];
- dragStartRef.current = direction === "horizontal" ? touch.clientX : touch.clientY;
+ dragStartRef.current =
+ direction === "horizontal" ? touch.clientX : touch.clientY;
}
},
[direction]
@@ -102,7 +108,7 @@ export const usePanelDrag = ({
const originalUserSelect = document.body.style.userSelect;
// Disable text selection during drag
document.body.style.userSelect = "none";
-
+
document.addEventListener("mousemove", onDrag);
document.addEventListener("mouseup", handleMouseUp);
document.addEventListener("touchmove", onTouchDrag, { passive: false });
@@ -112,7 +118,10 @@ export const usePanelDrag = ({
return () => {
// Restore original user-select value
document.body.style.userSelect = originalUserSelect;
-
+
+ // Clear handle element reference on drag end
+ handleElementRef.current = null;
+
document.removeEventListener("mousemove", onDrag);
document.removeEventListener("mouseup", handleMouseUp);
document.removeEventListener("touchmove", onTouchDrag);
diff --git a/packages/react-flexy-panels/src/utils/findAdjacentPanels.ts b/packages/react-flexy-panels/src/utils/findAdjacentPanels.ts
new file mode 100644
index 0000000..0a74d7f
--- /dev/null
+++ b/packages/react-flexy-panels/src/utils/findAdjacentPanels.ts
@@ -0,0 +1,36 @@
+/**
+ * Finds adjacent panels to a handle element using DOM traversal
+ */
+export function findAdjacentPanels(handleElement: HTMLElement): {
+ panel1: HTMLDivElement | null;
+ panel2: HTMLDivElement | null;
+} {
+ // Helper to check if an element is a panel (has data-unit attribute)
+ const isPanel = (el: Element | null): el is HTMLDivElement => {
+ return el instanceof HTMLDivElement && el.hasAttribute("data-unit");
+ };
+
+ // Find previous sibling panel
+ let panel1: HTMLDivElement | null = null;
+ let current: Element | null = handleElement.previousElementSibling;
+ while (current && !panel1) {
+ if (isPanel(current)) {
+ panel1 = current;
+ } else {
+ current = current.previousElementSibling;
+ }
+ }
+
+ // Find next sibling panel
+ let panel2: HTMLDivElement | null = null;
+ current = handleElement.nextElementSibling;
+ while (current && !panel2) {
+ if (isPanel(current)) {
+ panel2 = current;
+ } else {
+ current = current.nextElementSibling;
+ }
+ }
+
+ return { panel1, panel2 };
+}
diff --git a/packages/react-flexy-panels/src/utils/index.ts b/packages/react-flexy-panels/src/utils/index.ts
index 8a218ad..1561491 100644
--- a/packages/react-flexy-panels/src/utils/index.ts
+++ b/packages/react-flexy-panels/src/utils/index.ts
@@ -1,2 +1,3 @@
+export * from "./attachSetSizeFunction";
+export * from "./findAdjacentPanels";
export * from "./panelSize";
-
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 3741dc4..90a750d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -121,8 +121,8 @@ importers:
specifier: 22.1.1
version: 22.1.1(@babel/traverse@7.28.5)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))(nx@22.1.1(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17)))(verdaccio@6.2.1(encoding@0.1.13)(typanion@3.14.0))
'@rlx-ui/mcp':
- specifier: ^0.0.6
- version: 0.0.6
+ specifier: ^0.0.12
+ version: 0.0.12
'@swc-node/register':
specifier: ~1.9.1
version: 1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.25)(typescript@5.9.3)
@@ -2153,8 +2153,8 @@ packages:
react: '>=18.0.0'
react-dom: '>=18.0.0'
- '@rlx-ui/mcp@0.0.6':
- resolution: {integrity: sha512-VnjH49uMcTWEIy1DDVqE7SP1hlO3bEXecgQprV0ZSaZ8IudRFgsPjBjE8eimp3Vvg87cQtD2eBhdpZY94mHBog==}
+ '@rlx-ui/mcp@0.0.12':
+ resolution: {integrity: sha512-ppIarxt8tlmIS6pX9z10GOIzBgiNCspGrd/XrYB5MFY8Risn20WUBqLGKu54x+wqKBVq+ysMHWvHfqPtquLaAQ==}
hasBin: true
'@rlx-widgets/base@0.0.3':
@@ -10902,7 +10902,7 @@ snapshots:
react: 19.2.0
react-dom: 19.2.0(react@19.2.0)
- '@rlx-ui/mcp@0.0.6':
+ '@rlx-ui/mcp@0.0.12':
dependencies:
'@modelcontextprotocol/sdk': 1.22.0
zod: 3.25.76