202 lines
6.2 KiB
TypeScript
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,
|
|
]);
|
|
}
|