Skip to content

Commit

Permalink
feat: add boundsPadding prop to limit handle bounds
Browse files Browse the repository at this point in the history
  • Loading branch information
nerdyman committed Aug 2, 2020
1 parent 027b4b4 commit 6250dd2
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 15 deletions.
57 changes: 42 additions & 15 deletions src/react-compare-slider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ const ReactCompareSliderItem: React.FC<ReactCompareSliderCommonProps> = ({
/** Comparison slider properties. */
export interface ReactCompareSliderProps
extends Omit<ReactCompareSliderCommonProps, 'position'> {
/** Padding to limit handle bounds in pixels on the X (landscape) or Y (portrait) axis. */
boundsPadding?: number;
/** Custom handle component. */
handle?: React.ReactNode;
/** First item to show. */
Expand All @@ -118,7 +120,9 @@ export interface ReactCompareSliderProps

/** Properties for internal `updateInternalPosition` callback. */
interface UpdateInternalPositionProps
extends Required<Pick<ReactCompareSliderCommonProps, 'portrait'>> {
extends Required<
Pick<ReactCompareSliderProps, 'boundsPadding' | 'portrait'>
> {
/** X coordinate to update to (landscape). */
x: number;
/** Y coordinate to update to (portrait). */
Expand All @@ -137,15 +141,14 @@ export const ReactCompareSlider: React.FC<
onPositionChange,
portrait = false,
position = 50,
boundsPadding = 0,
style,
...props
}): React.ReactElement => {
/** Reference to root container. */
const containerRef = useRef<HTMLDivElement>(null);
/** Reference to current position as a percentage value. */
const internalPositionPc = useRef(position);
/** Previous `portrait` prop value. */
// const prevPropPortrait = usePrevious(portrait);
/** Previous `position` prop value. */
const prevPropPosition = usePrevious(position);
/** Internal position in pixels. */
Expand All @@ -157,7 +160,13 @@ export const ReactCompareSlider: React.FC<

/** Update internal px and pc */
const updateInternalPosition = useCallback(
({ x, y, isOffset, portrait: _portrait }: UpdateInternalPositionProps) => {
function updateInternalCall({
x,
y,
isOffset,
portrait: _portrait,
boundsPadding: _boundsPadding,
}: UpdateInternalPositionProps) {
const {
top,
left,
Expand Down Expand Up @@ -188,13 +197,21 @@ export const ReactCompareSlider: React.FC<
positionPx = width;
}

// Calculate percentage with bounds checking.
(internalPositionPc.current =
(positionPx / (_portrait ? height : width)) * 100),
0,
100;
// Update internal position percentage *without* bounds - we always want
// to return 0-100.
internalPositionPc.current =
(positionPx / (_portrait ? height : width)) * 100;

// Update internal pixel position capped to min/max bounds.
setInternalPositionPx(
Math.min(
// Get largest from pixel position *or* bounds padding
Math.max(positionPx, 0 + _boundsPadding),
// Use height *or* width based on orientation.
(_portrait ? height : width) - _boundsPadding
)
);

setInternalPositionPx(positionPx);
if (onPositionChange) onPositionChange(internalPositionPc.current);
},
[onPositionChange]
Expand All @@ -211,10 +228,17 @@ export const ReactCompareSlider: React.FC<

updateInternalPosition({
portrait,
boundsPadding,
x: (width / 100) * nextPosition,
y: (height / 100) * nextPosition,
});
}, [portrait, position, prevPropPosition, updateInternalPosition]);
}, [
portrait,
position,
prevPropPosition,
boundsPadding,
updateInternalPosition,
]);

/** Handle mouse/touch down. */
const handlePointerDown = useCallback(
Expand All @@ -223,29 +247,31 @@ export const ReactCompareSlider: React.FC<

updateInternalPosition({
portrait,
boundsPadding,
isOffset: true,
x: ev instanceof MouseEvent ? ev.pageX : ev.touches[0].pageX,
y: ev instanceof MouseEvent ? ev.pageY : ev.touches[0].pageY,
});

setIsDragging(true);
},
[portrait, updateInternalPosition]
[portrait, boundsPadding, updateInternalPosition]
);

/** Handle mouse/touch move. */
const handlePointerMove = useCallback(
(ev: MouseEvent | TouchEvent) => {
function moveCall(ev: MouseEvent | TouchEvent) {
if (!isDragging) return;

updateInternalPosition({
portrait,
boundsPadding,
isOffset: true,
x: ev instanceof MouseEvent ? ev.pageX : ev.touches[0].pageX,
y: ev instanceof MouseEvent ? ev.pageY : ev.touches[0].pageY,
});
},
[portrait, isDragging, updateInternalPosition]
[portrait, isDragging, boundsPadding, updateInternalPosition]
);

/** Handle mouse/touch up. */
Expand All @@ -258,11 +284,12 @@ export const ReactCompareSlider: React.FC<
({ width, height }: UseResizeObserverHandlerParams) => {
updateInternalPosition({
portrait,
boundsPadding,
x: (width / 100) * internalPositionPc.current,
y: (height / 100) * internalPositionPc.current,
});
},
[portrait, updateInternalPosition]
[portrait, boundsPadding, updateInternalPosition]
);

// Allow drag outside of container while pointer is still down.
Expand Down
2 changes: 2 additions & 0 deletions test/react-compare-slider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ describe('ReactCompareSlider', () => {

const { container } = render(
<ReactCompareSlider
boundsPadding={100}
position={75}
itemOne={<div test-id="rcs-one">Foo</div>}
itemTwo={<div test-id="rcs-two">Bar</div>}
handle={<Handle />}
Expand Down

0 comments on commit 6250dd2

Please sign in to comment.