Files
Webklar.com/node_modules/react-resizable-panels/src/hooks/useWindowSplitterPanelGroupBehavior.ts
Basilosaurusrex f027651f9b main repo
2025-11-24 18:09:40 +01:00

202 lines
6.2 KiB
TypeScript

import { isDevelopment } from "#is-development";
import { PanelData } from "../Panel";
import { Direction } from "../types";
import { adjustLayoutByDelta } from "../utils/adjustLayoutByDelta";
import { assert } from "../utils/assert";
import { calculateAriaValues } from "../utils/calculateAriaValues";
import { determinePivotIndices } from "../utils/determinePivotIndices";
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/
export function useWindowSplitterPanelGroupBehavior({
committedValuesRef,
eagerValuesRef,
groupId,
layout,
panelDataArray,
panelGroupElement,
setLayout,
}: {
committedValuesRef: RefObject<{
direction: Direction;
}>;
eagerValuesRef: RefObject<{
panelDataArray: PanelData[];
}>;
groupId: string;
layout: number[];
panelDataArray: PanelData[];
panelGroupElement: ParentNode | null;
setLayout: (sizes: number[]) => void;
}): void {
const devWarningsRef = useRef<{
didWarnAboutMissingResizeHandle: boolean;
}>({
didWarnAboutMissingResizeHandle: false,
});
useIsomorphicLayoutEffect(() => {
if (!panelGroupElement) {
return;
}
const resizeHandleElements = getResizeHandleElementsForGroup(
groupId,
panelGroupElement
);
for (let index = 0; index < panelDataArray.length - 1; index++) {
const { valueMax, valueMin, valueNow } = calculateAriaValues({
layout,
panelsArray: panelDataArray,
pivotIndices: [index, index + 1],
});
const resizeHandleElement = resizeHandleElements[index];
if (resizeHandleElement == null) {
if (isDevelopment) {
const { didWarnAboutMissingResizeHandle } = devWarningsRef.current;
if (!didWarnAboutMissingResizeHandle) {
devWarningsRef.current.didWarnAboutMissingResizeHandle = true;
console.warn(
`WARNING: Missing resize handle for PanelGroup "${groupId}"`
);
}
}
} else {
const panelData = panelDataArray[index];
assert(panelData, `No panel data found for index "${index}"`);
resizeHandleElement.setAttribute("aria-controls", panelData.id);
resizeHandleElement.setAttribute(
"aria-valuemax",
"" + Math.round(valueMax)
);
resizeHandleElement.setAttribute(
"aria-valuemin",
"" + Math.round(valueMin)
);
resizeHandleElement.setAttribute(
"aria-valuenow",
valueNow != null ? "" + Math.round(valueNow) : ""
);
}
}
return () => {
resizeHandleElements.forEach((resizeHandleElement, index) => {
resizeHandleElement.removeAttribute("aria-controls");
resizeHandleElement.removeAttribute("aria-valuemax");
resizeHandleElement.removeAttribute("aria-valuemin");
resizeHandleElement.removeAttribute("aria-valuenow");
});
};
}, [groupId, layout, panelDataArray, panelGroupElement]);
useEffect(() => {
if (!panelGroupElement) {
return;
}
const eagerValues = eagerValuesRef.current;
assert(eagerValues, `Eager values not found`);
const { panelDataArray } = eagerValues;
const groupElement = getPanelGroupElement(groupId, panelGroupElement);
assert(groupElement != null, `No group found for id "${groupId}"`);
const handles = getResizeHandleElementsForGroup(groupId, panelGroupElement);
assert(handles, `No resize handles found for group id "${groupId}"`);
const cleanupFunctions = handles.map((handle) => {
const handleId = handle.getAttribute("data-panel-resize-handle-id");
assert(handleId, `Resize handle element has no handle id attribute`);
const [idBefore, idAfter] = getResizeHandlePanelIds(
groupId,
handleId,
panelDataArray,
panelGroupElement
);
if (idBefore == null || idAfter == null) {
return () => {};
}
const onKeyDown = (event: KeyboardEvent) => {
if (event.defaultPrevented) {
return;
}
switch (event.key) {
case "Enter": {
event.preventDefault();
const index = panelDataArray.findIndex(
(panelData) => panelData.id === idBefore
);
if (index >= 0) {
const panelData = panelDataArray[index];
assert(panelData, `No panel data found for index ${index}`);
const size = layout[index];
const {
collapsedSize = 0,
collapsible,
minSize = 0,
} = panelData.constraints;
if (size != null && collapsible) {
const nextLayout = adjustLayoutByDelta({
delta: fuzzyNumbersEqual(size, collapsedSize)
? minSize - collapsedSize
: collapsedSize - size,
initialLayout: layout,
panelConstraints: panelDataArray.map(
(panelData) => panelData.constraints
),
pivotIndices: determinePivotIndices(
groupId,
handleId,
panelGroupElement
),
prevLayout: layout,
trigger: "keyboard",
});
if (layout !== nextLayout) {
setLayout(nextLayout);
}
}
}
break;
}
}
};
handle.addEventListener("keydown", onKeyDown);
return () => {
handle.removeEventListener("keydown", onKeyDown);
};
});
return () => {
cleanupFunctions.forEach((cleanupFunction) => cleanupFunction());
};
}, [
panelGroupElement,
committedValuesRef,
eagerValuesRef,
groupId,
layout,
panelDataArray,
setLayout,
]);
}