react-compare-slider
Advanced tools
Comparing version 0.1.4 to 1.0.0
export * from './react-compare-slider-image'; | ||
export * from './react-compare-slider'; | ||
export { styleFitContainer } from './utils'; |
'use strict'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } | ||
var tslib = require('tslib'); | ||
var React = require('react'); | ||
@@ -9,35 +12,2 @@ var React__default = _interopDefault(React); | ||
function _extends() { | ||
_extends = Object.assign || function (target) { | ||
for (var i = 1; i < arguments.length; i++) { | ||
var source = arguments[i]; | ||
for (var key in source) { | ||
if (Object.prototype.hasOwnProperty.call(source, key)) { | ||
target[key] = source[key]; | ||
} | ||
} | ||
} | ||
return target; | ||
}; | ||
return _extends.apply(this, arguments); | ||
} | ||
function _objectWithoutPropertiesLoose(source, excluded) { | ||
if (source == null) return {}; | ||
var target = {}; | ||
var sourceKeys = Object.keys(source); | ||
var key, i; | ||
for (i = 0; i < sourceKeys.length; i++) { | ||
key = sourceKeys[i]; | ||
if (excluded.indexOf(key) >= 0) continue; | ||
target[key] = source[key]; | ||
} | ||
return target; | ||
} | ||
/** | ||
@@ -47,9 +17,12 @@ * CSS style util for child to fit container | ||
var styleFitContainer = function styleFitContainer(_temp) { | ||
var _ref = _temp === void 0 ? {} : _temp, | ||
_ref$objectFit = _ref.objectFit, | ||
objectFit = _ref$objectFit === void 0 ? 'cover' : _ref$objectFit, | ||
props = _objectWithoutPropertiesLoose(_ref, ["objectFit"]); | ||
var styleFitContainer = function styleFitContainer(_a) { | ||
if (_a === void 0) { | ||
_a = {}; | ||
} | ||
return _extends({ | ||
var _b = _a.objectFit, | ||
objectFit = _b === void 0 ? 'cover' : _b, | ||
props = tslib.__rest(_a, ["objectFit"]); | ||
return tslib.__assign({ | ||
display: 'block', | ||
@@ -63,9 +36,118 @@ width: '100%', | ||
/** | ||
* Handle container to control position | ||
* Event listener binding hook | ||
* @param eventName - Event to bind to | ||
* @param handler - Callback handler | ||
* @param element - Element to bind to | ||
* @see https://usehooks.com/useEventListener/ | ||
*/ | ||
var ReactCompareSliderHandleContainer = function ReactCompareSliderHandleContainer(_ref2) { | ||
var children = _ref2.children, | ||
position = _ref2.position, | ||
portrait = _ref2.portrait; | ||
var useEventListener = function useEventListener(eventName, handler, element, handlerOptions) { | ||
if (element === void 0) { | ||
element = window; | ||
} // Create a ref that stores handler | ||
var savedHandler = React.useRef(); // Update ref.current value if handler changes. | ||
// This allows our effect below to always get latest handler ... | ||
// ... without us needing to pass it in effect deps array ... | ||
// ... and potentially cause effect to re-run every render. | ||
React.useEffect(function () { | ||
savedHandler.current = handler; | ||
}, [handler]); | ||
React.useEffect(function () { | ||
// Make sure element supports addEventListener | ||
var isSupported = element && element.addEventListener; | ||
if (!isSupported) return; // Create event listener that calls handler function stored in ref | ||
var eventListener = function eventListener(event) { | ||
return savedHandler.current && savedHandler.current(event); | ||
}; // Add event listener | ||
element.addEventListener(eventName, eventListener, handlerOptions); // Remove event listener on cleanup | ||
return function () { | ||
element.removeEventListener(eventName, eventListener, handlerOptions); | ||
}; | ||
}, [eventName, element, handlerOptions] // Re-run if eventName or element changes | ||
); | ||
}; | ||
/** | ||
* Bind resize observer to ref | ||
* @param ref - Ref to bind to | ||
* @param handler - Callback for handling entry's bounding rect | ||
* @see https://tobbelindstrom.com/blog/resize-observer-hook/ https://codesandbox.io/s/zw8kylol8m | ||
*/ | ||
var useResizeObserver = function useResizeObserver(ref, handler) { | ||
var observer = React.useRef(new resizeObserver.ResizeObserver(function (_a) { | ||
var entry = _a[0]; | ||
handler && handler(entry.contentRect); | ||
})); | ||
var disconnect = React.useCallback(function () { | ||
var current = observer.current; | ||
current && current.disconnect(); | ||
}, []); | ||
var observe = React.useCallback(function () { | ||
ref.current && observer.current.observe(ref.current); | ||
}, [ref]); | ||
React.useLayoutEffect(function () { | ||
observe(); | ||
return function () { | ||
return disconnect(); | ||
}; | ||
}, [disconnect, observe]); | ||
}; | ||
/** | ||
* Whether client supports the CSS `object-fit` property | ||
*/ | ||
var CLIENT_SUPPORTS_CSS_OBJECT_FIT = typeof CSS !== 'undefined' && CSS.supports && | ||
/*#__PURE__*/ | ||
CSS.supports('object-fit', 'cover'); | ||
/** | ||
* Image with fallback background for browsers that don't support the | ||
* `object-fit` CSS property | ||
*/ | ||
var ReactCompareSliderImage = function ReactCompareSliderImage(_a) { | ||
var className = _a.className, | ||
_b = _a.fallbackEnable, | ||
fallbackEnable = _b === void 0 ? true : _b, | ||
style = _a.style, | ||
props = tslib.__rest(_a, ["className", "fallbackEnable", "style"]); | ||
var innerStyle = styleFitContainer(tslib.__assign({}, style)); | ||
var containerStyle = { | ||
width: innerStyle.width, | ||
height: innerStyle.height | ||
}; // Add fallback background props if requested | ||
if (!CLIENT_SUPPORTS_CSS_OBJECT_FIT && fallbackEnable) { | ||
// Set fallback CSS properties, use props from `innerStyle` if defined | ||
containerStyle.backgroundImage = innerStyle.backgroundImage || "url(" + props.src + ")"; | ||
containerStyle.backgroundSize = innerStyle.backgroundSize || 'cover'; | ||
containerStyle.backgroundPosition = innerStyle.backgroundPosition || 'center'; // Hide inner image | ||
innerStyle.opacity = 0; | ||
} | ||
return React__default.createElement("div", { | ||
className: className, | ||
style: containerStyle, | ||
"data-rcs": "image-root" | ||
}, React__default.createElement("img", tslib.__assign({}, props, { | ||
style: innerStyle, | ||
"data-rcs": "image-inner" | ||
}))); | ||
}; | ||
/** Handle container to control position */ | ||
var ReactCompareSliderHandleContainer = function ReactCompareSliderHandleContainer(_a) { | ||
var children = _a.children, | ||
position = _a.position, | ||
portrait = _a.portrait; | ||
var style = { | ||
@@ -94,10 +176,8 @@ position: 'absolute', | ||
}; | ||
/** | ||
* Overridable handle | ||
*/ | ||
/** Overridable handle */ | ||
var ReactCompareSliderHandle = function ReactCompareSliderHandle(_ref3) { | ||
var portrait = _ref3.portrait, | ||
props = _objectWithoutPropertiesLoose(_ref3, ["portrait"]); | ||
var ReactCompareSliderHandle = function ReactCompareSliderHandle(_a) { | ||
var portrait = _a.portrait, | ||
props = tslib.__rest(_a, ["portrait"]); | ||
@@ -111,3 +191,3 @@ var style = { | ||
}; | ||
return React__default.createElement("div", Object.assign({}, props, { | ||
return React__default.createElement("div", tslib.__assign({}, props, { | ||
style: style, | ||
@@ -117,10 +197,8 @@ "data-rcs": "main-handle-inner" | ||
}; | ||
/** | ||
* Container for items passed to main component | ||
*/ | ||
/** Container for items passed to main component */ | ||
var ReactCompareSliderItem = function ReactCompareSliderItem(_ref4) { | ||
var portrait = _ref4.portrait, | ||
position = _ref4.position, | ||
props = _objectWithoutPropertiesLoose(_ref4, ["portrait", "position"]); | ||
var ReactCompareSliderItem = function ReactCompareSliderItem(_a) { | ||
var portrait = _a.portrait, | ||
position = _a.position, | ||
props = tslib.__rest(_a, ["portrait", "position"]); | ||
@@ -140,3 +218,3 @@ var style = { | ||
}; | ||
return React__default.createElement("div", Object.assign({}, props, { | ||
return React__default.createElement("div", tslib.__assign({}, props, { | ||
style: style, | ||
@@ -146,208 +224,141 @@ "data-rcs": "clip-item" | ||
}; | ||
/** | ||
* Root component | ||
*/ | ||
/** Comparison slider */ | ||
var ReactCompareSlider = function ReactCompareSlider(_ref5) { | ||
var handle = _ref5.handle, | ||
itemOne = _ref5.itemOne, | ||
itemTwo = _ref5.itemTwo, | ||
onChange = _ref5.onChange, | ||
portrait = _ref5.portrait, | ||
_ref5$position = _ref5.position, | ||
position = _ref5$position === void 0 ? 50 : _ref5$position, | ||
props = _objectWithoutPropertiesLoose(_ref5, ["handle", "itemOne", "itemTwo", "onChange", "portrait", "position"]); | ||
var ReactCompareSlider = function ReactCompareSlider(_a) { | ||
var handle = _a.handle, | ||
itemOne = _a.itemOne, | ||
itemTwo = _a.itemTwo, | ||
onPositionChange = _a.onPositionChange, | ||
portrait = _a.portrait, | ||
_b = _a.position, | ||
position = _b === void 0 ? 50 : _b, | ||
style = _a.style, | ||
props = tslib.__rest(_a, ["handle", "itemOne", "itemTwo", "onPositionChange", "portrait", "position", "style"]); | ||
var containerRef = React.useRef(document.createElement('div')); | ||
var _useState = React.useState(false), | ||
isDragging = _useState[0], | ||
setIsDragging = _useState[1]; // const [containerDimensions, setContainerDimensions] = useState({ | ||
// width: 0, | ||
// height: 0, | ||
// }); | ||
var _useState2 = React.useState({ | ||
pc: position, | ||
px: 0 | ||
var _c = React.useState({ | ||
width: 0, | ||
height: 0 | ||
}), | ||
internalPosition = _useState2[0], | ||
setInternalPosition = _useState2[1]; | ||
/** | ||
* Update internal position state from `x` and `y` coordinate *or* a | ||
* `percentage` value | ||
*/ | ||
containerBounds = _c[0], | ||
setContainerBounds = _c[1]; | ||
var containerRef = React.useRef(document.createElement('div')); | ||
var internalPositionPc = React.useRef(position); | ||
var updateInternalPosition = React.useCallback(function (_ref6) { | ||
var percentage = _ref6.percentage, | ||
pointerX = _ref6.pointerX, | ||
pointerY = _ref6.pointerY; | ||
var _d = React.useState(0), | ||
internalPositionPx = _d[0], | ||
setInternalPositionPx = _d[1]; | ||
var _containerRef$current = containerRef.current.getBoundingClientRect(), | ||
top = _containerRef$current.top, | ||
left = _containerRef$current.left, | ||
width = _containerRef$current.width, | ||
height = _containerRef$current.height; | ||
var _e = React.useState(false), | ||
isDragging = _e[0], | ||
setIsDragging = _e[1]; | ||
var x = 0; | ||
var y = 0; // Use pointer positions if defined | ||
var hasWindowBinding = React.useRef(false); | ||
var updateInternalPosition = React.useCallback(function (_a) { | ||
var x = _a.x, | ||
y = _a.y; | ||
if (pointerX && pointerY) { | ||
x = pointerX - left - window.scrollX; | ||
y = pointerY - top - window.scrollY; // Use percentage if defined | ||
} else if (percentage) { | ||
x = width / 100 * percentage; | ||
y = height / 100 * percentage; | ||
} | ||
var _b = containerRef.current.getBoundingClientRect(), | ||
top = _b.top, | ||
left = _b.left; | ||
var px = portrait ? y : x; | ||
var pc = portrait ? y / height * 100 : x / width * 100; | ||
setInternalPosition({ | ||
pc: pc, | ||
px: px | ||
}); | ||
var positionPx = portrait ? y - top - window.pageYOffset : x - left - window.pageXOffset; | ||
setInternalPositionPx(positionPx); // Calculate percentage with bounds checking | ||
if (onChange) { | ||
onChange(pc); | ||
} | ||
}, [onChange, portrait]); // Update internal position if position prop changes | ||
internalPositionPc.current = Math.min(Math.max(positionPx / (portrait ? containerBounds.height : containerBounds.width) * 100, 0), 100); | ||
if (onPositionChange) onPositionChange(internalPositionPc.current); | ||
}, [containerBounds.height, containerBounds.width, onPositionChange, portrait]); // Update internal position if `position` prop changes | ||
React.useEffect(function () { | ||
var _a = containerRef.current.getBoundingClientRect(), | ||
top = _a.top, | ||
left = _a.left, | ||
width = _a.width, | ||
height = _a.height; | ||
updateInternalPosition({ | ||
percentage: position | ||
x: width / 100 * position + left, | ||
y: height / 100 * position + top | ||
}); | ||
}, [position, updateInternalPosition]); | ||
/** | ||
* Handle element resize event | ||
*/ | ||
var handleResize = React.useCallback(function () { | ||
var _containerRef$current2 = containerRef.current.getBoundingClientRect(), | ||
width = _containerRef$current2.width, | ||
height = _containerRef$current2.height; | ||
setInternalPosition(function (state) { | ||
return _extends({}, state, { | ||
px: portrait ? height / 100 * state.pc : width / 100 * state.pc | ||
var handlePointerDown = React.useCallback(function (ev) { | ||
ev.preventDefault(); | ||
updateInternalPosition({ | ||
x: ev instanceof MouseEvent ? ev.pageX : ev.touches[0].pageX, | ||
y: ev instanceof MouseEvent ? ev.pageY : ev.touches[0].pageY | ||
}); | ||
setIsDragging(true); | ||
}, [updateInternalPosition]); | ||
var handlePointerMove = React.useCallback(function (ev) { | ||
if (!isDragging) return; | ||
requestAnimationFrame(function () { | ||
updateInternalPosition({ | ||
x: ev instanceof MouseEvent ? ev.pageX : ev.touches[0].pageX, | ||
y: ev instanceof MouseEvent ? ev.pageY : ev.touches[0].pageY | ||
}); | ||
}); | ||
}, [portrait]); // Update internal position on resize | ||
}, [isDragging, updateInternalPosition]); | ||
var handlePointerUp = React.useCallback(function () { | ||
setIsDragging(false); | ||
}, []); | ||
var handleResize = React.useCallback(function (_a) { | ||
var width = _a.width, | ||
height = _a.height; | ||
setContainerBounds({ | ||
width: width, | ||
height: height | ||
}); | ||
setInternalPositionPx((portrait ? height : width) / 100 * internalPositionPc.current); | ||
}, [portrait]); // Allow drag outside of container while pointer is still down | ||
React.useEffect(function () { | ||
var ro = new resizeObserver.ResizeObserver(function () { | ||
handleResize(); | ||
}); // Bind observer | ||
var el = containerRef.current; | ||
ro.observe(el); | ||
return function () { | ||
return ro.unobserve(el); | ||
}; | ||
}, [handleResize]); // Bind and handle events | ||
React.useEffect(function () { | ||
var containerRefCurrent = containerRef.current; | ||
var handleMouseDown = function handleMouseDown(ev) { | ||
ev.preventDefault(); | ||
if (!isDragging) setIsDragging(true); | ||
updateInternalPosition({ | ||
pointerX: ev.pageX, | ||
pointerY: ev.pageY | ||
if (isDragging && !hasWindowBinding.current) { | ||
window.addEventListener('mousemove', handlePointerMove, { | ||
passive: true | ||
}); | ||
}; | ||
var handleMouseMove = function handleMouseMove(ev) { | ||
if (isDragging) { | ||
requestAnimationFrame(function () { | ||
updateInternalPosition({ | ||
pointerX: ev.pageX, | ||
pointerY: ev.pageY | ||
}); | ||
}); | ||
} | ||
}; | ||
var handleTouchStart = function handleTouchStart(ev) { | ||
ev.preventDefault(); | ||
if (!isDragging) setIsDragging(true); | ||
updateInternalPosition({ | ||
pointerX: ev.touches[0].pageX, | ||
pointerY: ev.touches[0].pageY | ||
document.addEventListener('mouseup', handlePointerUp, { | ||
passive: true | ||
}); | ||
}; | ||
window.addEventListener('touchmove', handlePointerMove, { | ||
passive: true | ||
}); | ||
window.addEventListener('touchend', handlePointerUp, { | ||
passive: true | ||
}); | ||
hasWindowBinding.current = true; | ||
} | ||
var handleTouchMove = function handleTouchMove(ev) { | ||
if (isDragging) { | ||
requestAnimationFrame(function () { | ||
updateInternalPosition({ | ||
pointerX: ev.touches[0].pageX, | ||
pointerY: ev.touches[0].pageY | ||
}); | ||
}); | ||
return function () { | ||
if (hasWindowBinding.current) { | ||
window.removeEventListener('mousemove', handlePointerMove); | ||
window.removeEventListener('mouseup', handlePointerUp); | ||
hasWindowBinding.current = false; | ||
} | ||
}; | ||
}, [handlePointerMove, handlePointerUp, isDragging]); // Bind resize observer to container | ||
var handleFinish = function handleFinish() { | ||
if (isDragging) setIsDragging(false); | ||
}; // Mouse event bindings | ||
useResizeObserver(containerRef, handleResize); | ||
useEventListener('mousedown', handlePointerDown, containerRef.current, { | ||
capture: true, | ||
passive: false | ||
}); | ||
useEventListener('touchmove', handlePointerMove, containerRef.current, { | ||
capture: false, | ||
passive: true | ||
}); | ||
useEventListener('touchend', handlePointerUp, containerRef.current, { | ||
capture: false, | ||
passive: true | ||
}); | ||
useEventListener('touchstart', handlePointerDown, containerRef.current, { | ||
capture: true, | ||
passive: false | ||
}); // Use custom handle if requested | ||
containerRefCurrent.addEventListener('mousedown', handleMouseDown, { | ||
capture: false, | ||
passive: false | ||
}); | ||
containerRefCurrent.addEventListener('mouseleave', handleFinish, { | ||
capture: false, | ||
passive: true | ||
}); | ||
containerRefCurrent.addEventListener('mousemove', handleMouseMove, { | ||
capture: false, | ||
passive: true | ||
}); | ||
containerRefCurrent.addEventListener('mouseup', handleFinish, { | ||
capture: false, | ||
passive: true | ||
}); // Touch event bindings | ||
containerRefCurrent.addEventListener('touchstart', handleTouchStart, { | ||
capture: false, | ||
passive: false | ||
}); | ||
containerRefCurrent.addEventListener('touchend', handleFinish, { | ||
capture: false, | ||
passive: true | ||
}); | ||
containerRefCurrent.addEventListener('touchmove', handleTouchMove, { | ||
capture: false, | ||
passive: true | ||
}); | ||
containerRefCurrent.addEventListener('touchcancel', handleFinish, { | ||
capture: false, | ||
passive: true | ||
}); | ||
return function () { | ||
// Mouse event bindings | ||
containerRefCurrent.removeEventListener('mousedown', handleMouseDown); | ||
containerRefCurrent.removeEventListener('mouseleave', handleFinish); | ||
containerRefCurrent.removeEventListener('mousemove', handleMouseMove); | ||
containerRefCurrent.removeEventListener('mouseup', handleFinish); // Touch events bindings | ||
containerRefCurrent.removeEventListener('touchstart', handleTouchStart); | ||
containerRefCurrent.removeEventListener('touchend', handleFinish); | ||
containerRefCurrent.removeEventListener('touchmove', handleTouchMove); | ||
containerRefCurrent.removeEventListener('touchcancel', handleFinish); | ||
}; | ||
}, [isDragging, portrait, updateInternalPosition]); // Use custom handle if requested | ||
var Handle = handle || React__default.createElement(ReactCompareSliderHandle, { | ||
portrait: portrait | ||
}); | ||
var style = { | ||
// @NOTE using `flex` to ensure Firefox will calculate the correct width | ||
// of bounding box, it will return `0` otherwise | ||
flex: 1, | ||
var rootStyle = tslib.__assign({ | ||
position: 'relative', | ||
@@ -360,12 +371,13 @@ overflow: 'hidden', | ||
WebkitUserSelect: 'none' | ||
}; | ||
return React__default.createElement("div", Object.assign({}, props, { | ||
}, style); | ||
return React__default.createElement("div", tslib.__assign({}, props, { | ||
ref: containerRef, | ||
style: style, | ||
style: rootStyle, | ||
"data-rcs": "root" | ||
}), itemTwo, React__default.createElement(ReactCompareSliderItem, { | ||
position: internalPosition.px, | ||
position: internalPositionPx, | ||
portrait: portrait | ||
}, itemOne), React__default.createElement(ReactCompareSliderHandleContainer, { | ||
position: internalPosition.px, | ||
position: internalPositionPx, | ||
portrait: portrait | ||
@@ -375,46 +387,2 @@ }, Handle)); | ||
/** | ||
* Whether client supports the CSS `object-fit` property | ||
*/ | ||
var CLIENT_SUPPORTS_CSS_OBJECT_FIT = typeof CSS !== 'undefined' && CSS.supports && | ||
/*#__PURE__*/ | ||
CSS.supports('object-fit', 'cover'); | ||
/** | ||
* Image with fallback background for browsers that don't support the | ||
* `object-fit` CSS property | ||
*/ | ||
var ReactCompareSliderImage = function ReactCompareSliderImage(_ref) { | ||
var className = _ref.className, | ||
_ref$fallbackEnable = _ref.fallbackEnable, | ||
fallbackEnable = _ref$fallbackEnable === void 0 ? true : _ref$fallbackEnable, | ||
style = _ref.style, | ||
props = _objectWithoutPropertiesLoose(_ref, ["className", "fallbackEnable", "style"]); | ||
var innerStyle = styleFitContainer(_extends({}, style)); | ||
var containerStyle = { | ||
width: innerStyle.width, | ||
height: innerStyle.height | ||
}; // Add fallback background props if requested | ||
if (!CLIENT_SUPPORTS_CSS_OBJECT_FIT && fallbackEnable) { | ||
// Set fallback CSS properties, use props from `innerStyle` if defined | ||
containerStyle.backgroundImage = innerStyle.backgroundImage || "url(" + props.src + ")"; | ||
containerStyle.backgroundSize = innerStyle.backgroundSize || 'cover'; | ||
containerStyle.backgroundPosition = innerStyle.backgroundPosition || 'center'; // Hide inner image | ||
innerStyle.opacity = 0; | ||
} | ||
return React__default.createElement("div", { | ||
className: className, | ||
style: containerStyle, | ||
"data-rcs": "image-root" | ||
}, React__default.createElement("img", Object.assign({}, props, { | ||
style: innerStyle, | ||
"data-rcs": "image-inner" | ||
}))); | ||
}; | ||
exports.CLIENT_SUPPORTS_CSS_OBJECT_FIT = CLIENT_SUPPORTS_CSS_OBJECT_FIT; | ||
@@ -421,0 +389,0 @@ exports.ReactCompareSlider = ReactCompareSlider; |
@@ -1,2 +0,2 @@ | ||
"use strict";var e,t=require("react"),n=(e=t)&&"object"==typeof e&&"default"in e?e.default:e,r=require("resize-observer");function o(){return(o=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function i(e,t){if(null==e)return{};var n,r,o={},i=Object.keys(e);for(r=0;r<i.length;r++)t.indexOf(n=i[r])>=0||(o[n]=e[n]);return o}var a=function(e){var t=void 0===e?{}:e,n=t.objectFit;return o({display:"block",width:"100%",height:"100%",maxWidth:"100%",objectFit:void 0===n?"cover":n},i(t,["objectFit"]))},s=function(e){var t=e.position,r=e.portrait;return n.createElement("div",{style:{position:"absolute",top:0,width:"100%",height:"100%",transform:r?"translateY("+t+"px)":"translateX("+t+"px)",pointerEvents:"none"},"data-rcs":"main-handle-container"},n.createElement("div",{style:{position:"absolute",width:r?"100%":void 0,height:r?void 0:"100%",transform:r?"translateY(-50%)":"translateX(-50%)",pointerEvents:"all"}},e.children))},c=function(e){var t=e.portrait,r=i(e,["portrait"]);return n.createElement("div",Object.assign({},r,{style:{height:t?3:"100%",width:t?"100%":3,backgroundColor:"#ffffff",boxShadow:"0 0 .2rem #000000",cursor:t?"ns-resize":"ew-resize"},"data-rcs":"main-handle-inner"}))},u=function(e){var t=e.portrait,r=e.position,o=i(e,["portrait","position"]);return n.createElement("div",Object.assign({},o,{style:{position:"absolute",top:0,left:0,width:"100%",height:"100%",clip:t?"rect(auto,auto,"+r+"px,auto)":"rect(auto,"+r+"px,auto,auto)",willChange:"clip",userSelect:"none",KhtmlUserSelect:"none",MozUserSelect:"none",WebkitUserSelect:"none"},"data-rcs":"clip-item"}))},p="undefined"!=typeof CSS&&CSS.supports&&CSS.supports("object-fit","cover");exports.CLIENT_SUPPORTS_CSS_OBJECT_FIT=p,exports.ReactCompareSlider=function(e){var a=e.handle,p=e.itemOne,l=e.itemTwo,v=e.onChange,d=e.portrait,m=e.position,f=void 0===m?50:m,h=i(e,["handle","itemOne","itemTwo","onChange","portrait","position"]),g=t.useRef(document.createElement("div")),b=t.useState(!1),E=b[0],S=b[1],w=t.useState({pc:f,px:0}),x=w[0],y=w[1],C=t.useCallback(function(e){var t=e.percentage,n=e.pointerX,r=e.pointerY,o=g.current.getBoundingClientRect(),i=o.width,a=o.height,s=0,c=0;n&&r?(s=n-o.left-window.scrollX,c=r-o.top-window.scrollY):t&&(s=i/100*t,c=a/100*t);var u=d?c/a*100:s/i*100;y({pc:u,px:d?c:s}),v&&v(u)},[v,d]);t.useEffect(function(){C({percentage:f})},[f,C]);var L=t.useCallback(function(){var e=g.current.getBoundingClientRect(),t=e.width,n=e.height;y(function(e){return o({},e,{px:d?n/100*e.pc:t/100*e.pc})})},[d]);t.useEffect(function(){var e=new r.ResizeObserver(function(){L()}),t=g.current;return e.observe(t),function(){return e.unobserve(t)}},[L]),t.useEffect(function(){var e=g.current,t=function(e){e.preventDefault(),E||S(!0),C({pointerX:e.pageX,pointerY:e.pageY})},n=function(e){E&&requestAnimationFrame(function(){C({pointerX:e.pageX,pointerY:e.pageY})})},r=function(e){e.preventDefault(),E||S(!0),C({pointerX:e.touches[0].pageX,pointerY:e.touches[0].pageY})},o=function(e){E&&requestAnimationFrame(function(){C({pointerX:e.touches[0].pageX,pointerY:e.touches[0].pageY})})},i=function(){E&&S(!1)};return e.addEventListener("mousedown",t,{capture:!1,passive:!1}),e.addEventListener("mouseleave",i,{capture:!1,passive:!0}),e.addEventListener("mousemove",n,{capture:!1,passive:!0}),e.addEventListener("mouseup",i,{capture:!1,passive:!0}),e.addEventListener("touchstart",r,{capture:!1,passive:!1}),e.addEventListener("touchend",i,{capture:!1,passive:!0}),e.addEventListener("touchmove",o,{capture:!1,passive:!0}),e.addEventListener("touchcancel",i,{capture:!1,passive:!0}),function(){e.removeEventListener("mousedown",t),e.removeEventListener("mouseleave",i),e.removeEventListener("mousemove",n),e.removeEventListener("mouseup",i),e.removeEventListener("touchstart",r),e.removeEventListener("touchend",i),e.removeEventListener("touchmove",o),e.removeEventListener("touchcancel",i)}},[E,d,C]);var k=a||n.createElement(c,{portrait:d});return n.createElement("div",Object.assign({},h,{ref:g,style:{flex:1,position:"relative",overflow:"hidden",cursor:E?d?"ns-resize":"ew-resize":void 0,userSelect:"none",KhtmlUserSelect:"none",MozUserSelect:"none",WebkitUserSelect:"none"},"data-rcs":"root"}),l,n.createElement(u,{position:x.px,portrait:d},p),n.createElement(s,{position:x.px,portrait:d},k))},exports.ReactCompareSliderHandle=c,exports.ReactCompareSliderImage=function(e){var t=e.className,r=e.fallbackEnable,s=void 0===r||r,c=e.style,u=i(e,["className","fallbackEnable","style"]),l=a(o({},c)),v={width:l.width,height:l.height};return!p&&s&&(v.backgroundImage=l.backgroundImage||"url("+u.src+")",v.backgroundSize=l.backgroundSize||"cover",v.backgroundPosition=l.backgroundPosition||"center",l.opacity=0),n.createElement("div",{className:t,style:v,"data-rcs":"image-root"},n.createElement("img",Object.assign({},u,{style:l,"data-rcs":"image-inner"})))},exports.styleFitContainer=a; | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e,t=require("tslib"),n=require("react"),r=(e=n)&&"object"==typeof e&&"default"in e?e.default:e,i=require("resize-observer"),o=function(e){void 0===e&&(e={});var n=e.objectFit,r=void 0===n?"cover":n,i=t.__rest(e,["objectFit"]);return t.__assign({display:"block",width:"100%",height:"100%",maxWidth:"100%",objectFit:r},i)},a=function(e,t,r,i){void 0===r&&(r=window);var o=n.useRef();n.useEffect((function(){o.current=t}),[t]),n.useEffect((function(){if(r&&r.addEventListener){var t=function(e){return o.current&&o.current(e)};return r.addEventListener(e,t,i),function(){r.removeEventListener(e,t,i)}}}),[e,r,i])},s="undefined"!=typeof CSS&&CSS.supports&&CSS.supports("object-fit","cover"),c=function(e){var t=e.position,n=e.portrait;return r.createElement("div",{style:{position:"absolute",top:0,width:"100%",height:"100%",transform:n?"translateY("+t+"px)":"translateX("+t+"px)",pointerEvents:"none"},"data-rcs":"main-handle-container"},r.createElement("div",{style:{position:"absolute",width:n?"100%":void 0,height:n?void 0:"100%",transform:n?"translateY(-50%)":"translateX(-50%)",pointerEvents:"all"}},e.children))},u=function(e){var n=e.portrait,i=t.__rest(e,["portrait"]);return r.createElement("div",t.__assign({},i,{style:{height:n?3:"100%",width:n?"100%":3,backgroundColor:"#ffffff",boxShadow:"0 0 .2rem #000000",cursor:n?"ns-resize":"ew-resize"},"data-rcs":"main-handle-inner"}))},l=function(e){var n=e.portrait,i=e.position,o=t.__rest(e,["portrait","position"]);return r.createElement("div",t.__assign({},o,{style:{position:"absolute",top:0,left:0,width:"100%",height:"100%",clip:n?"rect(auto,auto,"+i+"px,auto)":"rect(auto,"+i+"px,auto,auto)",willChange:"clip",userSelect:"none",KhtmlUserSelect:"none",MozUserSelect:"none",WebkitUserSelect:"none"},"data-rcs":"clip-item"}))};exports.CLIENT_SUPPORTS_CSS_OBJECT_FIT=s,exports.ReactCompareSlider=function(e){var o=e.handle,s=e.itemOne,d=e.itemTwo,p=e.onPositionChange,f=e.portrait,v=e.position,h=void 0===v?50:v,m=e.style,g=t.__rest(e,["handle","itemOne","itemTwo","onPositionChange","portrait","position","style"]),w=n.useState({width:0,height:0}),b=w[0],E=w[1],_=n.useRef(document.createElement("div")),S=n.useRef(h),C=n.useState(0),y=C[0],k=C[1],x=n.useState(!1),R=x[0],L=x[1],z=n.useRef(!1),M=n.useCallback((function(e){var t=e.x,n=e.y,r=_.current.getBoundingClientRect(),i=f?n-r.top-window.pageYOffset:t-r.left-window.pageXOffset;k(i),S.current=Math.min(Math.max(i/(f?b.height:b.width)*100,0),100),p&&p(S.current)}),[b.height,b.width,p,f]);n.useEffect((function(){var e=_.current.getBoundingClientRect();M({x:e.width/100*h+e.left,y:e.height/100*h+e.top})}),[h,M]);var O,P,U,X,Y,j=n.useCallback((function(e){e.preventDefault(),M({x:e instanceof MouseEvent?e.pageX:e.touches[0].pageX,y:e instanceof MouseEvent?e.pageY:e.touches[0].pageY}),L(!0)}),[M]),F=n.useCallback((function(e){R&&requestAnimationFrame((function(){M({x:e instanceof MouseEvent?e.pageX:e.touches[0].pageX,y:e instanceof MouseEvent?e.pageY:e.touches[0].pageY})}))}),[R,M]),T=n.useCallback((function(){L(!1)}),[]),I=n.useCallback((function(e){var t=e.width,n=e.height;E({width:t,height:n}),k((f?n:t)/100*S.current)}),[f]);n.useEffect((function(){return R&&!z.current&&(window.addEventListener("mousemove",F,{passive:!0}),document.addEventListener("mouseup",T,{passive:!0}),window.addEventListener("touchmove",F,{passive:!0}),window.addEventListener("touchend",T,{passive:!0}),z.current=!0),function(){z.current&&(window.removeEventListener("mousemove",F),window.removeEventListener("mouseup",T),z.current=!1)}}),[F,T,R]),O=_,P=I,U=n.useRef(new i.ResizeObserver((function(e){P&&P(e[0].contentRect)}))),X=n.useCallback((function(){var e=U.current;e&&e.disconnect()}),[]),Y=n.useCallback((function(){O.current&&U.current.observe(O.current)}),[O]),n.useLayoutEffect((function(){return Y(),function(){return X()}}),[X,Y]),a("mousedown",j,_.current,{capture:!0,passive:!1}),a("touchmove",F,_.current,{capture:!1,passive:!0}),a("touchend",T,_.current,{capture:!1,passive:!0}),a("touchstart",j,_.current,{capture:!0,passive:!1});var q=o||r.createElement(u,{portrait:f}),N=t.__assign({position:"relative",overflow:"hidden",cursor:R?f?"ns-resize":"ew-resize":void 0,userSelect:"none",KhtmlUserSelect:"none",MozUserSelect:"none",WebkitUserSelect:"none"},m);return r.createElement("div",t.__assign({},g,{ref:_,style:N,"data-rcs":"root"}),d,r.createElement(l,{position:y,portrait:f},s),r.createElement(c,{position:y,portrait:f},q))},exports.ReactCompareSliderHandle=u,exports.ReactCompareSliderImage=function(e){var n=e.className,i=e.fallbackEnable,a=void 0===i||i,c=e.style,u=t.__rest(e,["className","fallbackEnable","style"]),l=o(t.__assign({},c)),d={width:l.width,height:l.height};return!s&&a&&(d.backgroundImage=l.backgroundImage||"url("+u.src+")",d.backgroundSize=l.backgroundSize||"cover",d.backgroundPosition=l.backgroundPosition||"center",l.opacity=0),r.createElement("div",{className:n,style:d,"data-rcs":"image-root"},r.createElement("img",t.__assign({},u,{style:l,"data-rcs":"image-inner"})))},exports.styleFitContainer=o; | ||
//# sourceMappingURL=react-compare-slider.cjs.production.min.js.map |
import React from 'react'; | ||
/** | ||
* CSS style util for child to fit container | ||
*/ | ||
export declare const styleFitContainer: ({ objectFit, ...props }?: React.CSSProperties) => Partial<React.CSSProperties>; | ||
/** Slider position prop */ | ||
declare type ReactCompareSliderPropPosition = number; | ||
/** | ||
* Common props shared between child components | ||
*/ | ||
/** Common props shared between child components */ | ||
interface ReactCompareSliderCommonProps { | ||
@@ -14,11 +9,7 @@ portrait?: boolean; | ||
} | ||
/** | ||
* Overridable handle | ||
*/ | ||
/** Overridable handle */ | ||
export declare const ReactCompareSliderHandle: React.FC<Pick<ReactCompareSliderCommonProps, 'portrait'>>; | ||
/** | ||
* Props for main component | ||
*/ | ||
/** Comparison slider props */ | ||
export interface ReactCompareSliderProps { | ||
/** Handle component for dragging between comparisons */ | ||
/** Custom handle component */ | ||
handle?: React.ReactNode; | ||
@@ -30,3 +21,3 @@ /** First item to show */ | ||
/** Callback on position change */ | ||
onChange?: (position: ReactCompareSliderPropPosition) => void; | ||
onPositionChange?: (position: ReactCompareSliderPropPosition) => void; | ||
/** Orientation */ | ||
@@ -37,6 +28,4 @@ portrait?: boolean; | ||
} | ||
/** | ||
* Root component | ||
*/ | ||
/** Comparison slider */ | ||
export declare const ReactCompareSlider: React.FC<ReactCompareSliderProps & React.HtmlHTMLAttributes<HTMLDivElement>>; | ||
export {}; |
@@ -1,37 +0,5 @@ | ||
import React, { useRef, useState, useCallback, useEffect } from 'react'; | ||
import { __rest, __assign } from 'tslib'; | ||
import React, { useRef, useEffect, useCallback, useLayoutEffect, useState } from 'react'; | ||
import { ResizeObserver } from 'resize-observer'; | ||
function _extends() { | ||
_extends = Object.assign || function (target) { | ||
for (var i = 1; i < arguments.length; i++) { | ||
var source = arguments[i]; | ||
for (var key in source) { | ||
if (Object.prototype.hasOwnProperty.call(source, key)) { | ||
target[key] = source[key]; | ||
} | ||
} | ||
} | ||
return target; | ||
}; | ||
return _extends.apply(this, arguments); | ||
} | ||
function _objectWithoutPropertiesLoose(source, excluded) { | ||
if (source == null) return {}; | ||
var target = {}; | ||
var sourceKeys = Object.keys(source); | ||
var key, i; | ||
for (i = 0; i < sourceKeys.length; i++) { | ||
key = sourceKeys[i]; | ||
if (excluded.indexOf(key) >= 0) continue; | ||
target[key] = source[key]; | ||
} | ||
return target; | ||
} | ||
/** | ||
@@ -41,9 +9,12 @@ * CSS style util for child to fit container | ||
var styleFitContainer = function styleFitContainer(_temp) { | ||
var _ref = _temp === void 0 ? {} : _temp, | ||
_ref$objectFit = _ref.objectFit, | ||
objectFit = _ref$objectFit === void 0 ? 'cover' : _ref$objectFit, | ||
props = _objectWithoutPropertiesLoose(_ref, ["objectFit"]); | ||
var styleFitContainer = function styleFitContainer(_a) { | ||
if (_a === void 0) { | ||
_a = {}; | ||
} | ||
return _extends({ | ||
var _b = _a.objectFit, | ||
objectFit = _b === void 0 ? 'cover' : _b, | ||
props = __rest(_a, ["objectFit"]); | ||
return __assign({ | ||
display: 'block', | ||
@@ -57,9 +28,118 @@ width: '100%', | ||
/** | ||
* Handle container to control position | ||
* Event listener binding hook | ||
* @param eventName - Event to bind to | ||
* @param handler - Callback handler | ||
* @param element - Element to bind to | ||
* @see https://usehooks.com/useEventListener/ | ||
*/ | ||
var ReactCompareSliderHandleContainer = function ReactCompareSliderHandleContainer(_ref2) { | ||
var children = _ref2.children, | ||
position = _ref2.position, | ||
portrait = _ref2.portrait; | ||
var useEventListener = function useEventListener(eventName, handler, element, handlerOptions) { | ||
if (element === void 0) { | ||
element = window; | ||
} // Create a ref that stores handler | ||
var savedHandler = useRef(); // Update ref.current value if handler changes. | ||
// This allows our effect below to always get latest handler ... | ||
// ... without us needing to pass it in effect deps array ... | ||
// ... and potentially cause effect to re-run every render. | ||
useEffect(function () { | ||
savedHandler.current = handler; | ||
}, [handler]); | ||
useEffect(function () { | ||
// Make sure element supports addEventListener | ||
var isSupported = element && element.addEventListener; | ||
if (!isSupported) return; // Create event listener that calls handler function stored in ref | ||
var eventListener = function eventListener(event) { | ||
return savedHandler.current && savedHandler.current(event); | ||
}; // Add event listener | ||
element.addEventListener(eventName, eventListener, handlerOptions); // Remove event listener on cleanup | ||
return function () { | ||
element.removeEventListener(eventName, eventListener, handlerOptions); | ||
}; | ||
}, [eventName, element, handlerOptions] // Re-run if eventName or element changes | ||
); | ||
}; | ||
/** | ||
* Bind resize observer to ref | ||
* @param ref - Ref to bind to | ||
* @param handler - Callback for handling entry's bounding rect | ||
* @see https://tobbelindstrom.com/blog/resize-observer-hook/ https://codesandbox.io/s/zw8kylol8m | ||
*/ | ||
var useResizeObserver = function useResizeObserver(ref, handler) { | ||
var observer = useRef(new ResizeObserver(function (_a) { | ||
var entry = _a[0]; | ||
handler && handler(entry.contentRect); | ||
})); | ||
var disconnect = useCallback(function () { | ||
var current = observer.current; | ||
current && current.disconnect(); | ||
}, []); | ||
var observe = useCallback(function () { | ||
ref.current && observer.current.observe(ref.current); | ||
}, [ref]); | ||
useLayoutEffect(function () { | ||
observe(); | ||
return function () { | ||
return disconnect(); | ||
}; | ||
}, [disconnect, observe]); | ||
}; | ||
/** | ||
* Whether client supports the CSS `object-fit` property | ||
*/ | ||
var CLIENT_SUPPORTS_CSS_OBJECT_FIT = typeof CSS !== 'undefined' && CSS.supports && | ||
/*#__PURE__*/ | ||
CSS.supports('object-fit', 'cover'); | ||
/** | ||
* Image with fallback background for browsers that don't support the | ||
* `object-fit` CSS property | ||
*/ | ||
var ReactCompareSliderImage = function ReactCompareSliderImage(_a) { | ||
var className = _a.className, | ||
_b = _a.fallbackEnable, | ||
fallbackEnable = _b === void 0 ? true : _b, | ||
style = _a.style, | ||
props = __rest(_a, ["className", "fallbackEnable", "style"]); | ||
var innerStyle = styleFitContainer(__assign({}, style)); | ||
var containerStyle = { | ||
width: innerStyle.width, | ||
height: innerStyle.height | ||
}; // Add fallback background props if requested | ||
if (!CLIENT_SUPPORTS_CSS_OBJECT_FIT && fallbackEnable) { | ||
// Set fallback CSS properties, use props from `innerStyle` if defined | ||
containerStyle.backgroundImage = innerStyle.backgroundImage || "url(" + props.src + ")"; | ||
containerStyle.backgroundSize = innerStyle.backgroundSize || 'cover'; | ||
containerStyle.backgroundPosition = innerStyle.backgroundPosition || 'center'; // Hide inner image | ||
innerStyle.opacity = 0; | ||
} | ||
return React.createElement("div", { | ||
className: className, | ||
style: containerStyle, | ||
"data-rcs": "image-root" | ||
}, React.createElement("img", __assign({}, props, { | ||
style: innerStyle, | ||
"data-rcs": "image-inner" | ||
}))); | ||
}; | ||
/** Handle container to control position */ | ||
var ReactCompareSliderHandleContainer = function ReactCompareSliderHandleContainer(_a) { | ||
var children = _a.children, | ||
position = _a.position, | ||
portrait = _a.portrait; | ||
var style = { | ||
@@ -88,10 +168,8 @@ position: 'absolute', | ||
}; | ||
/** | ||
* Overridable handle | ||
*/ | ||
/** Overridable handle */ | ||
var ReactCompareSliderHandle = function ReactCompareSliderHandle(_ref3) { | ||
var portrait = _ref3.portrait, | ||
props = _objectWithoutPropertiesLoose(_ref3, ["portrait"]); | ||
var ReactCompareSliderHandle = function ReactCompareSliderHandle(_a) { | ||
var portrait = _a.portrait, | ||
props = __rest(_a, ["portrait"]); | ||
@@ -105,3 +183,3 @@ var style = { | ||
}; | ||
return React.createElement("div", Object.assign({}, props, { | ||
return React.createElement("div", __assign({}, props, { | ||
style: style, | ||
@@ -111,10 +189,8 @@ "data-rcs": "main-handle-inner" | ||
}; | ||
/** | ||
* Container for items passed to main component | ||
*/ | ||
/** Container for items passed to main component */ | ||
var ReactCompareSliderItem = function ReactCompareSliderItem(_ref4) { | ||
var portrait = _ref4.portrait, | ||
position = _ref4.position, | ||
props = _objectWithoutPropertiesLoose(_ref4, ["portrait", "position"]); | ||
var ReactCompareSliderItem = function ReactCompareSliderItem(_a) { | ||
var portrait = _a.portrait, | ||
position = _a.position, | ||
props = __rest(_a, ["portrait", "position"]); | ||
@@ -134,3 +210,3 @@ var style = { | ||
}; | ||
return React.createElement("div", Object.assign({}, props, { | ||
return React.createElement("div", __assign({}, props, { | ||
style: style, | ||
@@ -140,208 +216,141 @@ "data-rcs": "clip-item" | ||
}; | ||
/** | ||
* Root component | ||
*/ | ||
/** Comparison slider */ | ||
var ReactCompareSlider = function ReactCompareSlider(_ref5) { | ||
var handle = _ref5.handle, | ||
itemOne = _ref5.itemOne, | ||
itemTwo = _ref5.itemTwo, | ||
onChange = _ref5.onChange, | ||
portrait = _ref5.portrait, | ||
_ref5$position = _ref5.position, | ||
position = _ref5$position === void 0 ? 50 : _ref5$position, | ||
props = _objectWithoutPropertiesLoose(_ref5, ["handle", "itemOne", "itemTwo", "onChange", "portrait", "position"]); | ||
var ReactCompareSlider = function ReactCompareSlider(_a) { | ||
var handle = _a.handle, | ||
itemOne = _a.itemOne, | ||
itemTwo = _a.itemTwo, | ||
onPositionChange = _a.onPositionChange, | ||
portrait = _a.portrait, | ||
_b = _a.position, | ||
position = _b === void 0 ? 50 : _b, | ||
style = _a.style, | ||
props = __rest(_a, ["handle", "itemOne", "itemTwo", "onPositionChange", "portrait", "position", "style"]); | ||
var containerRef = useRef(document.createElement('div')); | ||
var _useState = useState(false), | ||
isDragging = _useState[0], | ||
setIsDragging = _useState[1]; // const [containerDimensions, setContainerDimensions] = useState({ | ||
// width: 0, | ||
// height: 0, | ||
// }); | ||
var _useState2 = useState({ | ||
pc: position, | ||
px: 0 | ||
var _c = useState({ | ||
width: 0, | ||
height: 0 | ||
}), | ||
internalPosition = _useState2[0], | ||
setInternalPosition = _useState2[1]; | ||
/** | ||
* Update internal position state from `x` and `y` coordinate *or* a | ||
* `percentage` value | ||
*/ | ||
containerBounds = _c[0], | ||
setContainerBounds = _c[1]; | ||
var containerRef = useRef(document.createElement('div')); | ||
var internalPositionPc = useRef(position); | ||
var updateInternalPosition = useCallback(function (_ref6) { | ||
var percentage = _ref6.percentage, | ||
pointerX = _ref6.pointerX, | ||
pointerY = _ref6.pointerY; | ||
var _d = useState(0), | ||
internalPositionPx = _d[0], | ||
setInternalPositionPx = _d[1]; | ||
var _containerRef$current = containerRef.current.getBoundingClientRect(), | ||
top = _containerRef$current.top, | ||
left = _containerRef$current.left, | ||
width = _containerRef$current.width, | ||
height = _containerRef$current.height; | ||
var _e = useState(false), | ||
isDragging = _e[0], | ||
setIsDragging = _e[1]; | ||
var x = 0; | ||
var y = 0; // Use pointer positions if defined | ||
var hasWindowBinding = useRef(false); | ||
var updateInternalPosition = useCallback(function (_a) { | ||
var x = _a.x, | ||
y = _a.y; | ||
if (pointerX && pointerY) { | ||
x = pointerX - left - window.scrollX; | ||
y = pointerY - top - window.scrollY; // Use percentage if defined | ||
} else if (percentage) { | ||
x = width / 100 * percentage; | ||
y = height / 100 * percentage; | ||
} | ||
var _b = containerRef.current.getBoundingClientRect(), | ||
top = _b.top, | ||
left = _b.left; | ||
var px = portrait ? y : x; | ||
var pc = portrait ? y / height * 100 : x / width * 100; | ||
setInternalPosition({ | ||
pc: pc, | ||
px: px | ||
}); | ||
var positionPx = portrait ? y - top - window.pageYOffset : x - left - window.pageXOffset; | ||
setInternalPositionPx(positionPx); // Calculate percentage with bounds checking | ||
if (onChange) { | ||
onChange(pc); | ||
} | ||
}, [onChange, portrait]); // Update internal position if position prop changes | ||
internalPositionPc.current = Math.min(Math.max(positionPx / (portrait ? containerBounds.height : containerBounds.width) * 100, 0), 100); | ||
if (onPositionChange) onPositionChange(internalPositionPc.current); | ||
}, [containerBounds.height, containerBounds.width, onPositionChange, portrait]); // Update internal position if `position` prop changes | ||
useEffect(function () { | ||
var _a = containerRef.current.getBoundingClientRect(), | ||
top = _a.top, | ||
left = _a.left, | ||
width = _a.width, | ||
height = _a.height; | ||
updateInternalPosition({ | ||
percentage: position | ||
x: width / 100 * position + left, | ||
y: height / 100 * position + top | ||
}); | ||
}, [position, updateInternalPosition]); | ||
/** | ||
* Handle element resize event | ||
*/ | ||
var handleResize = useCallback(function () { | ||
var _containerRef$current2 = containerRef.current.getBoundingClientRect(), | ||
width = _containerRef$current2.width, | ||
height = _containerRef$current2.height; | ||
setInternalPosition(function (state) { | ||
return _extends({}, state, { | ||
px: portrait ? height / 100 * state.pc : width / 100 * state.pc | ||
var handlePointerDown = useCallback(function (ev) { | ||
ev.preventDefault(); | ||
updateInternalPosition({ | ||
x: ev instanceof MouseEvent ? ev.pageX : ev.touches[0].pageX, | ||
y: ev instanceof MouseEvent ? ev.pageY : ev.touches[0].pageY | ||
}); | ||
setIsDragging(true); | ||
}, [updateInternalPosition]); | ||
var handlePointerMove = useCallback(function (ev) { | ||
if (!isDragging) return; | ||
requestAnimationFrame(function () { | ||
updateInternalPosition({ | ||
x: ev instanceof MouseEvent ? ev.pageX : ev.touches[0].pageX, | ||
y: ev instanceof MouseEvent ? ev.pageY : ev.touches[0].pageY | ||
}); | ||
}); | ||
}, [portrait]); // Update internal position on resize | ||
}, [isDragging, updateInternalPosition]); | ||
var handlePointerUp = useCallback(function () { | ||
setIsDragging(false); | ||
}, []); | ||
var handleResize = useCallback(function (_a) { | ||
var width = _a.width, | ||
height = _a.height; | ||
setContainerBounds({ | ||
width: width, | ||
height: height | ||
}); | ||
setInternalPositionPx((portrait ? height : width) / 100 * internalPositionPc.current); | ||
}, [portrait]); // Allow drag outside of container while pointer is still down | ||
useEffect(function () { | ||
var ro = new ResizeObserver(function () { | ||
handleResize(); | ||
}); // Bind observer | ||
var el = containerRef.current; | ||
ro.observe(el); | ||
return function () { | ||
return ro.unobserve(el); | ||
}; | ||
}, [handleResize]); // Bind and handle events | ||
useEffect(function () { | ||
var containerRefCurrent = containerRef.current; | ||
var handleMouseDown = function handleMouseDown(ev) { | ||
ev.preventDefault(); | ||
if (!isDragging) setIsDragging(true); | ||
updateInternalPosition({ | ||
pointerX: ev.pageX, | ||
pointerY: ev.pageY | ||
if (isDragging && !hasWindowBinding.current) { | ||
window.addEventListener('mousemove', handlePointerMove, { | ||
passive: true | ||
}); | ||
}; | ||
var handleMouseMove = function handleMouseMove(ev) { | ||
if (isDragging) { | ||
requestAnimationFrame(function () { | ||
updateInternalPosition({ | ||
pointerX: ev.pageX, | ||
pointerY: ev.pageY | ||
}); | ||
}); | ||
} | ||
}; | ||
var handleTouchStart = function handleTouchStart(ev) { | ||
ev.preventDefault(); | ||
if (!isDragging) setIsDragging(true); | ||
updateInternalPosition({ | ||
pointerX: ev.touches[0].pageX, | ||
pointerY: ev.touches[0].pageY | ||
document.addEventListener('mouseup', handlePointerUp, { | ||
passive: true | ||
}); | ||
}; | ||
window.addEventListener('touchmove', handlePointerMove, { | ||
passive: true | ||
}); | ||
window.addEventListener('touchend', handlePointerUp, { | ||
passive: true | ||
}); | ||
hasWindowBinding.current = true; | ||
} | ||
var handleTouchMove = function handleTouchMove(ev) { | ||
if (isDragging) { | ||
requestAnimationFrame(function () { | ||
updateInternalPosition({ | ||
pointerX: ev.touches[0].pageX, | ||
pointerY: ev.touches[0].pageY | ||
}); | ||
}); | ||
return function () { | ||
if (hasWindowBinding.current) { | ||
window.removeEventListener('mousemove', handlePointerMove); | ||
window.removeEventListener('mouseup', handlePointerUp); | ||
hasWindowBinding.current = false; | ||
} | ||
}; | ||
}, [handlePointerMove, handlePointerUp, isDragging]); // Bind resize observer to container | ||
var handleFinish = function handleFinish() { | ||
if (isDragging) setIsDragging(false); | ||
}; // Mouse event bindings | ||
useResizeObserver(containerRef, handleResize); | ||
useEventListener('mousedown', handlePointerDown, containerRef.current, { | ||
capture: true, | ||
passive: false | ||
}); | ||
useEventListener('touchmove', handlePointerMove, containerRef.current, { | ||
capture: false, | ||
passive: true | ||
}); | ||
useEventListener('touchend', handlePointerUp, containerRef.current, { | ||
capture: false, | ||
passive: true | ||
}); | ||
useEventListener('touchstart', handlePointerDown, containerRef.current, { | ||
capture: true, | ||
passive: false | ||
}); // Use custom handle if requested | ||
containerRefCurrent.addEventListener('mousedown', handleMouseDown, { | ||
capture: false, | ||
passive: false | ||
}); | ||
containerRefCurrent.addEventListener('mouseleave', handleFinish, { | ||
capture: false, | ||
passive: true | ||
}); | ||
containerRefCurrent.addEventListener('mousemove', handleMouseMove, { | ||
capture: false, | ||
passive: true | ||
}); | ||
containerRefCurrent.addEventListener('mouseup', handleFinish, { | ||
capture: false, | ||
passive: true | ||
}); // Touch event bindings | ||
containerRefCurrent.addEventListener('touchstart', handleTouchStart, { | ||
capture: false, | ||
passive: false | ||
}); | ||
containerRefCurrent.addEventListener('touchend', handleFinish, { | ||
capture: false, | ||
passive: true | ||
}); | ||
containerRefCurrent.addEventListener('touchmove', handleTouchMove, { | ||
capture: false, | ||
passive: true | ||
}); | ||
containerRefCurrent.addEventListener('touchcancel', handleFinish, { | ||
capture: false, | ||
passive: true | ||
}); | ||
return function () { | ||
// Mouse event bindings | ||
containerRefCurrent.removeEventListener('mousedown', handleMouseDown); | ||
containerRefCurrent.removeEventListener('mouseleave', handleFinish); | ||
containerRefCurrent.removeEventListener('mousemove', handleMouseMove); | ||
containerRefCurrent.removeEventListener('mouseup', handleFinish); // Touch events bindings | ||
containerRefCurrent.removeEventListener('touchstart', handleTouchStart); | ||
containerRefCurrent.removeEventListener('touchend', handleFinish); | ||
containerRefCurrent.removeEventListener('touchmove', handleTouchMove); | ||
containerRefCurrent.removeEventListener('touchcancel', handleFinish); | ||
}; | ||
}, [isDragging, portrait, updateInternalPosition]); // Use custom handle if requested | ||
var Handle = handle || React.createElement(ReactCompareSliderHandle, { | ||
portrait: portrait | ||
}); | ||
var style = { | ||
// @NOTE using `flex` to ensure Firefox will calculate the correct width | ||
// of bounding box, it will return `0` otherwise | ||
flex: 1, | ||
var rootStyle = __assign({ | ||
position: 'relative', | ||
@@ -354,12 +363,13 @@ overflow: 'hidden', | ||
WebkitUserSelect: 'none' | ||
}; | ||
return React.createElement("div", Object.assign({}, props, { | ||
}, style); | ||
return React.createElement("div", __assign({}, props, { | ||
ref: containerRef, | ||
style: style, | ||
style: rootStyle, | ||
"data-rcs": "root" | ||
}), itemTwo, React.createElement(ReactCompareSliderItem, { | ||
position: internalPosition.px, | ||
position: internalPositionPx, | ||
portrait: portrait | ||
}, itemOne), React.createElement(ReactCompareSliderHandleContainer, { | ||
position: internalPosition.px, | ||
position: internalPositionPx, | ||
portrait: portrait | ||
@@ -369,47 +379,3 @@ }, Handle)); | ||
/** | ||
* Whether client supports the CSS `object-fit` property | ||
*/ | ||
var CLIENT_SUPPORTS_CSS_OBJECT_FIT = typeof CSS !== 'undefined' && CSS.supports && | ||
/*#__PURE__*/ | ||
CSS.supports('object-fit', 'cover'); | ||
/** | ||
* Image with fallback background for browsers that don't support the | ||
* `object-fit` CSS property | ||
*/ | ||
var ReactCompareSliderImage = function ReactCompareSliderImage(_ref) { | ||
var className = _ref.className, | ||
_ref$fallbackEnable = _ref.fallbackEnable, | ||
fallbackEnable = _ref$fallbackEnable === void 0 ? true : _ref$fallbackEnable, | ||
style = _ref.style, | ||
props = _objectWithoutPropertiesLoose(_ref, ["className", "fallbackEnable", "style"]); | ||
var innerStyle = styleFitContainer(_extends({}, style)); | ||
var containerStyle = { | ||
width: innerStyle.width, | ||
height: innerStyle.height | ||
}; // Add fallback background props if requested | ||
if (!CLIENT_SUPPORTS_CSS_OBJECT_FIT && fallbackEnable) { | ||
// Set fallback CSS properties, use props from `innerStyle` if defined | ||
containerStyle.backgroundImage = innerStyle.backgroundImage || "url(" + props.src + ")"; | ||
containerStyle.backgroundSize = innerStyle.backgroundSize || 'cover'; | ||
containerStyle.backgroundPosition = innerStyle.backgroundPosition || 'center'; // Hide inner image | ||
innerStyle.opacity = 0; | ||
} | ||
return React.createElement("div", { | ||
className: className, | ||
style: containerStyle, | ||
"data-rcs": "image-root" | ||
}, React.createElement("img", Object.assign({}, props, { | ||
style: innerStyle, | ||
"data-rcs": "image-inner" | ||
}))); | ||
}; | ||
export { CLIENT_SUPPORTS_CSS_OBJECT_FIT, ReactCompareSlider, ReactCompareSliderHandle, ReactCompareSliderImage, styleFitContainer }; | ||
//# sourceMappingURL=react-compare-slider.esm.js.map |
{ | ||
"name": "react-compare-slider", | ||
"version": "0.1.4", | ||
"version": "1.0.0", | ||
"license": "MIT", | ||
@@ -36,6 +36,9 @@ "repository": { | ||
"release": "np", | ||
"test": "tsdx test --env=jsdom", | ||
"test:watch": "yarn test --coverage --watch", | ||
"test": "CI=true tsdx test --color", | ||
"test:watch": "tsdx test", | ||
"test:coverage": "CI=true tsdx test --color --coverage", | ||
"lint": "tsdx lint", | ||
"lint-staged": "lint-staged" | ||
"lint-staged": "lint-staged", | ||
"storybook": "start-storybook -p 6006 --ci", | ||
"storybook:build": "build-storybook" | ||
}, | ||
@@ -52,3 +55,3 @@ "peerDependencies": { | ||
"pretty-quick", | ||
"eslint --fix", | ||
"eslint --config ./.eslintrc.precommit.js --fix", | ||
"git add", | ||
@@ -70,22 +73,37 @@ "yarn test --bail --findRelatedTests" | ||
"devDependencies": { | ||
"@testing-library/react": "^9.3.0", | ||
"@types/jest": "^24.0.18", | ||
"@types/react": "^16.9.5", | ||
"@types/react-dom": "^16.9.1", | ||
"@typescript-eslint/eslint-plugin": "^2.3.2", | ||
"@typescript-eslint/parser": "^2.3.2", | ||
"eslint": "^6.5.1", | ||
"eslint-plugin-jest": "^22.17.0", | ||
"eslint-plugin-react": "^7.16.0", | ||
"eslint-plugin-react-hooks": "^2.1.2", | ||
"husky": "^3.0.8", | ||
"lint-staged": "^9.4.1", | ||
"np": "^5.1.0", | ||
"prettier": "^1.18.2", | ||
"pretty-quick": "^1.11.1", | ||
"react": "^16.10.2", | ||
"react-dom": "^16.10.2", | ||
"tsdx": "^0.9.3", | ||
"@storybook/addon-actions": "^5.3.0-rc.6", | ||
"@storybook/addon-docs": "^5.3.0-rc.6", | ||
"@storybook/addon-info": "^5.3.0-rc.6", | ||
"@storybook/addon-knobs": "^5.3.0-rc.6", | ||
"@storybook/addon-links": "^5.3.0-rc.6", | ||
"@storybook/addon-viewport": "^5.3.0-rc.6", | ||
"@storybook/addons": "^5.3.0-rc.6", | ||
"@storybook/preset-typescript": "^1.2.0", | ||
"@storybook/react": "^5.3.0-rc.6", | ||
"@storybook/theming": "^5.3.0-rc.6", | ||
"@testing-library/react": "^9.4.0", | ||
"@types/jest": "^24.0.25", | ||
"@types/node": "12.11.7", | ||
"@types/react": "^16.9.17", | ||
"@types/react-dom": "^16.9.4", | ||
"@types/storybook__react": "^4.0.2", | ||
"@typescript-eslint/eslint-plugin": "^2.14.0", | ||
"@typescript-eslint/parser": "^2.14.0", | ||
"babel-loader": "^8.0.6", | ||
"eslint": "^6.8.0", | ||
"eslint-plugin-jest": "^23.2.0", | ||
"eslint-plugin-react": "^7.17.0", | ||
"eslint-plugin-react-hooks": "^2.3.0", | ||
"husky": "^3.1.0", | ||
"lint-staged": "^9.5.0", | ||
"np": "^5.2.1", | ||
"prettier": "^1.19.1", | ||
"pretty-quick": "^2.0.1", | ||
"react": "^16.12.0", | ||
"react-docgen-typescript-loader": "^3.6.0", | ||
"react-dom": "^16.12.0", | ||
"ts-loader": "^6.2.1", | ||
"tsdx": "^0.12.1", | ||
"tslib": "^1.10.0", | ||
"typescript": "^3.6.3" | ||
"typescript": "^3.7.4" | ||
}, | ||
@@ -92,0 +110,0 @@ "dependencies": { |
@@ -1,7 +0,23 @@ | ||
![License](https://img.shields.io/npm/l/react-compare-slider) ![npm](https://img.shields.io/npm/v/react-compare-slider) ![npm bundle size](https://img.shields.io/bundlephobia/minzip/react-compare-slider) | ||
[![Build Status](https://github.com/nerdyman/react-compare-slider/workflows/node-ci/badge.svg)](https://github.com/nerdyman/react-compare-slider/workflows/node-ci) | ||
<div align="center"> | ||
<h1>React Compare Slider</h1> | ||
<p>Compare two components, side-by-side or top-to-toe.</p> | ||
# React Compare Slider | ||
<a href="https://github.com/nerdyman/react-compare-slider/blob/master/LICENSE"> | ||
<img src="https://img.shields.io/npm/l/react-compare-slider.svg" alt="License MIT"> | ||
</a> | ||
<a href="https://npmjs.com/package/react-compare-slider"> | ||
<img src="https://img.shields.io/npm/v/react-compare-slider.svg" alt="NPM package"> | ||
</a> | ||
<a href="https://bundlephobia.com/result?p=react-compare-slider"> | ||
<img src="https://img.shields.io/bundlephobia/minzip/react-compare-slider.svg" alt="Bundle size"> | ||
</a> | ||
<a href="https://github.com/nerdyman/react-compare-slider/actions?query=workflow%3Abuild"> | ||
<img src="https://github.com/nerdyman/react-compare-slider/workflows/build/badge.svg" alt="Build Status"> | ||
</a> | ||
<a href="https://festive-darwin-fab443.netlify.com/"> | ||
<img src="https://img.shields.io/badge/demos-🚀-blue.svg" alt="Demos" /> | ||
</a> | ||
</div> | ||
Compare two components, side-by-side or top-to-toe. | ||
--- | ||
@@ -13,6 +29,6 @@ ## Features | ||
- Simple API | ||
- Unopinionated & fully customizable – optionally use your own components and styles | ||
- Responsive, fluid | ||
- Fully customisable | ||
- Works in IE11+ | ||
- Teeny-tiny, only one ponyfill dependency | ||
- [Teeny-tiny](https://bundlephobia.com/result?p=react-compare-slider), only one ponyfill dependency | ||
- Type safe | ||
@@ -22,10 +38,4 @@ | ||
### CodeSandbox | ||
See Storybook for [documentation](https://festive-darwin-fab443.netlify.com/?path=/docs/docs-intro--page) and [demos](https://festive-darwin-fab443.netlify.com/?path=/docs/demos-images--default). | ||
**Note**: The CodeSandbox code view degrades performance slightly, see _Demo_ links for real-world performance. | ||
- Landscape, Portrait with custom handle | ||
- [Demo](<https://9si6l.codesandbox.io/>) | ||
- [Code](<https://codesandbox.io/s/react-compare-slider-simple-example-9si6l>) | ||
## Usage | ||
@@ -41,17 +51,22 @@ | ||
### Use it! | ||
### Basic Image Usage | ||
You *may* use `ReactCompareSliderImage` to render images or use your own custom | ||
components. | ||
```jsx | ||
import { ReactCompareSlider } from 'react-compare-slider'; | ||
import { ReactCompareSlider, ReactCompareSliderImage } from 'react-compare-slider'; | ||
<ReactCompareSlider | ||
itemOne={<img src="..." alt="Image one" />} | ||
itemTwo={<img src="..." alt="Image two" />} | ||
itemOne={<ReactCompareSliderImage src="..." srcSet="..." alt="Image one" />} | ||
itemTwo={<ReactCompareSliderImage src="..." srcSet="..." alt="Image two" />} | ||
/> | ||
``` | ||
## Props & docs | ||
### Advanced Usage | ||
### Props | ||
See the [docs](https://festive-darwin-fab443.netlify.com/?path=/docs/docs-intro--page) for advanced examples. | ||
## Props | ||
| Prop | Type | Required | Default value | Description | | ||
@@ -62,11 +77,15 @@ |------|------|:--------:|---------------|-------------| | ||
| `itemTwo` | `ReactNode` | ✓ | `undefined` | Second component to show in slider | | ||
| `onChange` | `function` | | `undefined` | Callback on position change, returns current position as argument `(position) => { ... }` | | ||
| `onPositionChange` | `function` | | `undefined` | Callback on position change, returns current position as argument `(position) => { ... }` | | ||
| `position` | `number` | | `50` | Initial percentage position of divide (`0-100`) | | ||
| `portrait` | `boolean` | | `undefined` | Whether to use portrait orientation | | ||
See the [API docs](https://festive-darwin-fab443.netlify.com/?path=/docs/docs-api--page) for detailed information. | ||
## Extending | ||
### `ReactCompareSliderImage` | ||
`ReactCompareSliderImage` is a standalone image component that detects whether the browser supports the `object-fit` CSS property, if not it will apply a background image to achieve the same effect. It calls `styleFitContainer` internally and will set `background-size`, `background-position` and `background-image` if they have not already been defined in a passed `style` prop. | ||
`ReactCompareSliderImage` is a standalone image component that detects whether the browser supports the `object-fit` CSS property, if not it will apply a background image to achieve the same effect. It will set `background-size`, `background-position` and `background-image` if they have not already been defined in a passed `style` prop. | ||
#### Props | ||
#### `ReactCompareSliderImage` Props | ||
@@ -93,13 +112,2 @@ `ReactCompareSliderImage` supports all attributes assignable to an `img` component, in addition to the following: | ||
Within slider: | ||
```jsx | ||
import { ReactCompareSlider, ReactCompareSliderImage } from 'react-compare-slider'; | ||
<ReactCompareSlider | ||
itemOne={<ReactCompareSliderImage src="..." alt="Image one" />} | ||
itemTwo={<ReactCompareSliderImage src="..." alt="Image two" />} | ||
/> | ||
``` | ||
### `styleFitContainer` | ||
@@ -117,4 +125,2 @@ | ||
// ... | ||
<div style={{ width: '100vw', height: '100vh' }}> | ||
@@ -128,3 +134,2 @@ <video | ||
}} | ||
// ... | ||
/> | ||
@@ -134,4 +139,8 @@ </div> | ||
## Requirements | ||
- React 16.8+ | ||
## Notes | ||
Bootstrapped with [TSDX](<https://github.com/palmerhq/tsdx>). |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
105822
14
1
139
35
758
1