@@ -5,6 +5,7 @@ import { ContainerClip, ContainerHandle } from './Container';
5
5
import { ReactCompareSliderHandle } from './ReactCompareSliderHandle' ;
6
6
import type { ReactCompareSliderDetailedProps } from './types' ;
7
7
import type { UseResizeObserverHandlerProps } from './utils' ;
8
+ import { getPositionAsPercentage } from './utils' ;
8
9
import { KeyboardEventKeys , useEventListener , usePrevious , useResizeObserver } from './utils' ;
9
10
10
11
/** Properties for internal `updateInternalPosition` callback. */
@@ -52,9 +53,12 @@ export const ReactCompareSlider: FC<ReactCompareSliderDetailedProps> = ({
52
53
const hasWindowBinding = useRef ( false ) ;
53
54
/** Target container for pointer events. */
54
55
const [ interactiveTarget , setInteractiveTarget ] = useState < HTMLElement | null > ( ) ;
55
- /** Whether the bounds of the container element have been synchronised. */
56
- // @TODO Remove this.
57
- const [ didSyncBounds , setDidSyncBounds ] = useState ( false ) ;
56
+ const [ didMount , setDidMount ] = useState ( false ) ;
57
+
58
+ // Set mount state to ensure initial position setter is not skipped.
59
+ useEffect ( ( ) => {
60
+ setDidMount ( true ) ;
61
+ } , [ ] ) ;
58
62
59
63
// Set target container for pointer events.
60
64
useEffect ( ( ) => {
@@ -72,98 +76,69 @@ export const ReactCompareSlider: FC<ReactCompareSliderDetailedProps> = ({
72
76
portrait : _portrait ,
73
77
boundsPadding : _boundsPadding ,
74
78
} : UpdateInternalPositionProps ) {
75
- const { top , left , width, height } = (
79
+ const { left , top , width, height } = (
76
80
rootContainerRef . current as HTMLDivElement
77
81
) . getBoundingClientRect ( ) ;
78
82
79
- // Early out if width or height are zero, can't calculate values
80
- // from zeros.
83
+ // Early out if width or height are zero, can't calculate values from zeros.
81
84
if ( width === 0 || height === 0 ) return ;
82
85
83
- /**
84
- * Pixel position clamped within the container's bounds.
85
- * @NOTE This does *not* take `boundsPadding` into account because we need
86
- * the full coords to correctly position the handle.
87
- */
88
- const positionPx = Math . min (
89
- Math . max (
90
- // Determine bounds based on orientation
91
- _portrait
92
- ? isOffset
93
- ? y - top - window . pageYOffset
94
- : y
95
- : isOffset
96
- ? x - left - window . pageXOffset
97
- : x ,
98
- // Min value
99
- 0 ,
100
- ) ,
101
- // Max value
102
- _portrait ? height : width ,
103
- ) ;
104
-
105
86
/** Width or height with CSS scaling accounted for. */
106
87
const zoomScale = _portrait
107
88
? height / ( ( rootContainerRef . current as HTMLDivElement ) . offsetHeight || 1 )
108
89
: width / ( ( rootContainerRef . current as HTMLDivElement ) . offsetWidth || 1 ) ;
109
90
110
- const adjustedPosition = positionPx / zoomScale ;
111
- const adjustedWidth = width / zoomScale ;
112
- const adjustedHeight = height / zoomScale ;
113
-
114
- /**
115
- * Internal position percentage *without* bounds.
116
- * @NOTE This uses the entire container bounds **without** `boundsPadding`
117
- * to get the *real* bounds.
118
- */
119
- const nextInternalPositionPc =
120
- ( adjustedPosition / ( _portrait ? adjustedHeight : adjustedWidth ) ) * 100 ;
121
-
122
- /** Whether the current pixel position meets the min/max bounds. */
123
- const positionMeetsBounds = _portrait
124
- ? adjustedPosition === 0 || adjustedPosition === adjustedHeight
125
- : adjustedPosition === 0 || adjustedPosition === adjustedWidth ;
126
-
127
- const canSkipPositionPc =
128
- nextInternalPositionPc === internalPositionPc . current &&
91
+ // Convert passed pixel to percentage using the container's bounds.
92
+ const boundsPaddingPercentage =
93
+ ( ( _boundsPadding * zoomScale ) / ( _portrait ? height : width ) ) * 100 ;
94
+
95
+ const nextPosition = getPositionAsPercentage ( {
96
+ bounds : { x, y, width, height, left, top } ,
97
+ isOffset,
98
+ portrait : _portrait ,
99
+ } ) ;
100
+
101
+ /** Next position clamped within padded `boundsPadding` box. */
102
+ const nextPositionWithBoundsPadding = Math . min (
103
+ Math . max ( nextPosition , boundsPaddingPercentage * zoomScale ) ,
104
+ 100 - boundsPaddingPercentage * zoomScale ,
105
+ ) ;
106
+
107
+ const canSkipUpdate =
108
+ didMount &&
109
+ nextPosition === internalPositionPc . current &&
110
+ ( nextPosition === 100 || nextPosition === 0 ) &&
129
111
( internalPositionPc . current === 0 || internalPositionPc . current === 100 ) ;
130
112
131
- // Early out if pixel and percentage positions are already at the min/max
132
- // to prevent update spamming when the user is sliding outside of the
133
- // container.
134
- if ( didSyncBounds && canSkipPositionPc && positionMeetsBounds ) {
113
+ // Early out if pixel and percentage positions are already at the min/max to prevent update
114
+ // spamming when the user is sliding outside of the container.
115
+ if ( canSkipUpdate ) {
135
116
return ;
136
- } else {
137
- setDidSyncBounds ( true ) ;
138
117
}
139
118
140
119
// Set new internal position.
141
- internalPositionPc . current = nextInternalPositionPc ;
142
-
143
- /** Pixel position clamped to extremities *with* bounds padding. */
144
- const clampedPx = Math . min (
145
- // Get largest from pixel position *or* bounds padding.
146
- Math . max ( adjustedPosition , 0 + _boundsPadding ) ,
147
- // Use height *or* width based on orientation.
148
- ( _portrait ? adjustedHeight : adjustedWidth ) - _boundsPadding ,
149
- ) ;
120
+ internalPositionPc . current = nextPosition ;
150
121
151
122
( handleContainerRef . current as HTMLButtonElement ) . setAttribute (
152
123
'aria-valuenow' ,
153
124
`${ Math . round ( internalPositionPc . current ) } ` ,
154
125
) ;
155
126
156
- ( clipContainerRef . current as HTMLElement ) . style . clipPath = _portrait
157
- ? `inset(${ clampedPx } px 0 0 0)`
158
- : `inset(0 0 0 ${ clampedPx } px)` ;
127
+ ( handleContainerRef . current as HTMLElement ) . style . top = _portrait
128
+ ? `${ nextPositionWithBoundsPadding } %`
129
+ : '0' ;
130
+
131
+ ( handleContainerRef . current as HTMLElement ) . style . left = _portrait
132
+ ? '0'
133
+ : `${ nextPositionWithBoundsPadding } %` ;
159
134
160
- ( handleContainerRef . current as HTMLElement ) . style . transform = _portrait
161
- ? `translate3d(0,calc(-50% + ${ clampedPx } px), 0)`
162
- : `translate3d(calc(-50% + ${ clampedPx } px),0,0 )` ;
135
+ ( clipContainerRef . current as HTMLElement ) . style . clipPath = _portrait
136
+ ? `inset( ${ nextPositionWithBoundsPadding } % 0 0 0)`
137
+ : `inset(0 0 0 ${ nextPositionWithBoundsPadding } % )` ;
163
138
164
139
if ( onPositionChange ) onPositionChange ( internalPositionPc . current ) ;
165
140
} ,
166
- [ didSyncBounds , onPositionChange ] ,
141
+ [ didMount , onPositionChange ] ,
167
142
) ;
168
143
169
144
// Update internal position when other user controllable props change.
0 commit comments