74 lines
2.3 KiB
TypeScript
74 lines
2.3 KiB
TypeScript
const DEFAULT_MIN_HANDLE_SIZE = 36;
|
|
|
|
function clamp(value: number, min: number, max: number): number {
|
|
return Math.min(max, Math.max(min, value));
|
|
}
|
|
|
|
export type VerticalScrollbarGeometryInput = {
|
|
viewportSize: number;
|
|
contentSize: number;
|
|
offset: number;
|
|
minHandleSize?: number;
|
|
};
|
|
|
|
export type VerticalScrollbarGeometry = {
|
|
isVisible: boolean;
|
|
maxScrollOffset: number;
|
|
handleSize: number;
|
|
handleOffset: number;
|
|
maxHandleOffset: number;
|
|
};
|
|
|
|
export function computeVerticalScrollbarGeometry(
|
|
input: VerticalScrollbarGeometryInput,
|
|
): VerticalScrollbarGeometry {
|
|
const viewportSize = Number.isFinite(input.viewportSize) ? Math.max(0, input.viewportSize) : 0;
|
|
const contentSize = Number.isFinite(input.contentSize) ? Math.max(0, input.contentSize) : 0;
|
|
const minHandleSize = Number.isFinite(input.minHandleSize)
|
|
? Math.max(0, input.minHandleSize ?? DEFAULT_MIN_HANDLE_SIZE)
|
|
: DEFAULT_MIN_HANDLE_SIZE;
|
|
|
|
const maxScrollOffset = Math.max(0, contentSize - viewportSize);
|
|
if (maxScrollOffset <= 0 || viewportSize <= 0 || contentSize <= 0) {
|
|
return {
|
|
isVisible: false,
|
|
maxScrollOffset: 0,
|
|
handleSize: 0,
|
|
handleOffset: 0,
|
|
maxHandleOffset: 0,
|
|
};
|
|
}
|
|
|
|
const rawHandleSize = (viewportSize * viewportSize) / contentSize;
|
|
const handleSize = clamp(rawHandleSize, minHandleSize, viewportSize);
|
|
const maxHandleOffset = Math.max(0, viewportSize - handleSize);
|
|
const clampedOffset = clamp(input.offset, 0, maxScrollOffset);
|
|
const handleOffset =
|
|
maxScrollOffset > 0 ? (clampedOffset / maxScrollOffset) * maxHandleOffset : 0;
|
|
|
|
return {
|
|
isVisible: true,
|
|
maxScrollOffset,
|
|
handleSize,
|
|
handleOffset,
|
|
maxHandleOffset,
|
|
};
|
|
}
|
|
|
|
export type ScrollOffsetFromDragDeltaInput = {
|
|
startOffset: number;
|
|
dragDelta: number;
|
|
maxScrollOffset: number;
|
|
maxHandleOffset: number;
|
|
};
|
|
|
|
export function computeScrollOffsetFromDragDelta(input: ScrollOffsetFromDragDeltaInput): number {
|
|
if (input.maxScrollOffset <= 0 || input.maxHandleOffset <= 0) {
|
|
return clamp(input.startOffset, 0, Math.max(0, input.maxScrollOffset));
|
|
}
|
|
|
|
const scrollPerPixel = input.maxScrollOffset / input.maxHandleOffset;
|
|
const nextOffset = input.startOffset + input.dragDelta * scrollPerPixel;
|
|
return clamp(nextOffset, 0, input.maxScrollOffset);
|
|
}
|