cloudinary-react
Advanced tools
Comparing version 1.5.1 to 1.6.0
@@ -0,1 +1,15 @@ | ||
1.6.0 / 2020-07-06 | ||
================== | ||
New functionality and features | ||
------------------------------ | ||
* Add support for lazy loading images, placeholder and accessibility (#162) | ||
Other Changes | ||
------------- | ||
* Update cloudinary-core dependency to version 2.10.1 | ||
* Refactor Image component (#167) | ||
* Update tests to fail when component is not exported | ||
* Add test for variable names (#164) | ||
1.5.1 / 2020-06-21 | ||
@@ -2,0 +16,0 @@ ================== |
@@ -52,2 +52,12 @@ "use strict"; | ||
/** | ||
* Check if given component is a Cloudinary Component with given displayName | ||
* @param component the component to check | ||
* @param displayName of wanted component | ||
* @return {boolean} | ||
*/ | ||
var isCloudinaryComponent = function isCloudinaryComponent(component, displayName) { | ||
return !!( /*#__PURE__*/_react["default"].isValidElement(component) && component.type && component.type.displayName === displayName); | ||
}; | ||
/** | ||
* Return a new object containing keys and values where keys are in the keys list | ||
@@ -59,2 +69,3 @@ * @param {object} source Object to copy values from | ||
function only(source) { | ||
@@ -86,3 +97,3 @@ var keys = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; | ||
function CloudinaryComponent(props, context) { | ||
function CloudinaryComponent(_props, _context) { | ||
var _this; | ||
@@ -92,4 +103,20 @@ | ||
_this = _super.call(this, props, context); | ||
_this.getContext = _this.getContext.bind(_assertThisInitialized(_this)); | ||
_this = _super.call(this, _props, _context); | ||
_defineProperty(_assertThisInitialized(_this), "getContext", function () { | ||
return _this.context || {}; | ||
}); | ||
_defineProperty(_assertThisInitialized(_this), "onIntersect", function () { | ||
_this.setState({ | ||
isInView: true | ||
}); | ||
}); | ||
_defineProperty(_assertThisInitialized(_this), "getExtendedProps", function () { | ||
var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _this.props; | ||
var context = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _this.getContext(); | ||
return CloudinaryComponent.normalizeOptions(context, props); | ||
}); | ||
return _this; | ||
@@ -104,5 +131,9 @@ } | ||
}, { | ||
key: "getContext", | ||
value: function getContext() { | ||
return this.context || {}; | ||
key: "getChildPlaceholder", | ||
value: function getChildPlaceholder(children) { | ||
if (children) { | ||
return _react["default"].Children.toArray(children).find(function (child) { | ||
return isCloudinaryComponent(child, "CloudinaryPlaceholder"); | ||
}); | ||
} | ||
} | ||
@@ -114,19 +145,10 @@ }, { | ||
if (children === undefined || children === null) return null; | ||
var result = children ? _react["default"].Children.toArray(children).filter(function (child) { | ||
return isCloudinaryComponent(child, "CloudinaryTransformation"); | ||
}).map(function (child) { | ||
var options = CloudinaryComponent.normalizeOptions(child.props, child.context); | ||
var mapped = _react["default"].Children.map(children, function (child) { | ||
if (! /*#__PURE__*/_react["default"].isValidElement(child)) { | ||
// child is not an element (e.g. simple text) | ||
return; | ||
} | ||
var options = {}; | ||
if (child.type && child.type.exposesProps) { | ||
options = CloudinaryComponent.normalizeOptions(child.props, child.context); | ||
} | ||
var childOptions = _this2.getChildTransformations(child.props.children); | ||
if (childOptions !== undefined && childOptions !== null) { | ||
if (childOptions) { | ||
options.transformation = childOptions; | ||
@@ -136,9 +158,4 @@ } | ||
return options; | ||
}); | ||
if (mapped != null) { | ||
return mapped.filter(function (o) { | ||
return !_cloudinaryCore.Util.isEmpty(o); | ||
}); | ||
} else return null; | ||
}) : []; | ||
return result.length ? result : null; | ||
} | ||
@@ -157,3 +174,5 @@ /** | ||
var children = extendedProps.children, | ||
rest = _objectWithoutProperties(extendedProps, ["children"]); | ||
accessibility = extendedProps.accessibility, | ||
placeholder = extendedProps.placeholder, | ||
rest = _objectWithoutProperties(extendedProps, ["children", "accessibility", "placeholder"]); | ||
@@ -165,4 +184,14 @@ var ownTransformation = only(_cloudinaryCore.Util.withCamelCaseKeys(rest), _cloudinaryCore.Transformation.methods) || {}; | ||
ownTransformation.transformation = childrenOptions; | ||
} | ||
} //Append placeholder and accessibility if exists | ||
var advancedTransformations = { | ||
accessibility: accessibility, | ||
placeholder: placeholder | ||
}; | ||
Object.keys(advancedTransformations).filter(function (k) { | ||
return advancedTransformations[k]; | ||
}).map(function (k) { | ||
ownTransformation[k] = advancedTransformations[k]; | ||
}); | ||
return ownTransformation; | ||
@@ -179,5 +208,15 @@ } | ||
}, { | ||
key: "getUrl", | ||
key: "getConfiguredCloudinary", | ||
/** | ||
* Generated a configured Cloudinary object. | ||
* @param extendedProps React props combined with custom Cloudinary configuration options | ||
* @return {Cloudinary} configured using extendedProps | ||
*/ | ||
value: function getConfiguredCloudinary(extendedProps) { | ||
var options = _cloudinaryCore.Util.extractUrlParams(_cloudinaryCore.Util.withSnakeCaseKeys(extendedProps)); | ||
return Cloudinary["new"](options); | ||
} | ||
/** | ||
* Generate a Cloudinary resource URL based on the options provided and child Transformation elements | ||
@@ -188,11 +227,17 @@ * @param extendedProps React props combined with custom Cloudinary configuration options | ||
*/ | ||
}, { | ||
key: "getUrl", | ||
value: function getUrl(extendedProps) { | ||
var transformation = this.getTransformation(extendedProps); | ||
var publicId = extendedProps.publicId; | ||
var cl = getConfiguredCloudinary(extendedProps); | ||
return cl.url(publicId, this.getTransformation(extendedProps)); | ||
} | ||
/** | ||
* Merges context & props | ||
* @param props of this component | ||
* @param context of this component | ||
* @return {Object} | ||
*/ | ||
var options = _cloudinaryCore.Util.extractUrlParams(_cloudinaryCore.Util.withSnakeCaseKeys(extendedProps)); | ||
var cl = _cloudinaryCore.Cloudinary["new"](options); | ||
return cl.url(extendedProps.publicId, transformation); | ||
} | ||
}], [{ | ||
@@ -206,3 +251,3 @@ key: "normalizeOptions", | ||
return options.reduce(function (left, right) { | ||
for (var key in right) { | ||
Object.keys(right || {}).forEach(function (key) { | ||
var value = right[key]; | ||
@@ -213,4 +258,3 @@ | ||
} | ||
} | ||
}); | ||
return left; | ||
@@ -228,3 +272,2 @@ }, {}); | ||
CloudinaryComponent.propTypes.publicId = _propTypes["default"].string; | ||
CloudinaryComponent.propTypes.responsive = _propTypes["default"].bool; | ||
/** | ||
@@ -231,0 +274,0 @@ * Create a React type definition object. All items are PropTypes.string or [string] or object or [object]. |
@@ -8,6 +8,4 @@ "use strict"; | ||
var _react = _interopRequireDefault(require("react")); | ||
var _react = _interopRequireWildcard(require("react")); | ||
var _cloudinaryCore = _interopRequireWildcard(require("cloudinary-core")); | ||
var _CloudinaryComponent2 = _interopRequireDefault(require("../CloudinaryComponent")); | ||
@@ -17,2 +15,8 @@ | ||
var _cloudinaryCore = require("cloudinary-core"); | ||
var _propTypes = _interopRequireDefault(require("prop-types")); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } | ||
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; } | ||
@@ -22,4 +26,2 @@ | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } | ||
function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } | ||
@@ -29,6 +31,2 @@ | ||
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } | ||
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; } | ||
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } | ||
@@ -38,4 +36,6 @@ | ||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } | ||
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } | ||
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; } | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
@@ -61,11 +61,7 @@ | ||
var defaultBreakpoints = function defaultBreakpoints(width) { | ||
var steps = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 100; | ||
return steps * Math.ceil(width / steps); | ||
}; | ||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } | ||
/** | ||
* A component representing a Cloudinary served image | ||
*/ | ||
var Image = /*#__PURE__*/function (_CloudinaryComponent) { | ||
@@ -82,270 +78,166 @@ _inherits(Image, _CloudinaryComponent); | ||
_this = _super.call(this, props, context); | ||
_this.handleResize = _this.handleResize.bind(_assertThisInitialized(_this)); | ||
_this.attachRef = _this.attachRef.bind(_assertThisInitialized(_this)); | ||
var state = { | ||
responsive: false, | ||
url: undefined, | ||
breakpoints: defaultBreakpoints | ||
}; | ||
_this.state = _objectSpread(_objectSpread({}, state), _this.prepareState(props, context)); | ||
return _this; | ||
} | ||
/** | ||
* Retrieve the window or default view of the current element | ||
* @returns {DocumentView|*} | ||
* @private | ||
*/ | ||
_defineProperty(_assertThisInitialized(_this), "isResponsive", function () { | ||
return _this.props.responsive && _this.imgElement && _this.imgElement.current; | ||
}); | ||
_createClass(Image, [{ | ||
key: "prepareState", | ||
value: function prepareState() { | ||
var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.props; | ||
var context = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.getContext(); | ||
_defineProperty(_assertThisInitialized(_this), "getOptions", function () { | ||
var extendedProps = _this.getExtendedProps(); | ||
var extendedProps = _CloudinaryComponent2["default"].normalizeOptions(context, props); | ||
var _extendedProps$_this$ = _objectSpread(_objectSpread({}, extendedProps), _this.getTransformation(extendedProps)), | ||
children = _extendedProps$_this$.children, | ||
innerRef = _extendedProps$_this$.innerRef, | ||
options = _objectWithoutProperties(_extendedProps$_this$, ["children", "innerRef"]); | ||
var url = this.getUrl(extendedProps); | ||
var state = {}; | ||
var updatedOptions = {}; | ||
return options; | ||
}); | ||
if (extendedProps.breakpoints !== undefined) { | ||
state.breakpoints = extendedProps.breakpoints; | ||
} | ||
_defineProperty(_assertThisInitialized(_this), "getAttributes", function () { | ||
var additionalOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var isInView = _this.state.isInView; | ||
var placeholder = additionalOptions.placeholder; | ||
if (extendedProps.responsive) { | ||
state.responsive = true; | ||
updatedOptions = this.cloudinaryUpdate(url, state); | ||
url = updatedOptions.url; | ||
} | ||
var options = _objectSpread(_objectSpread({}, _this.getOptions()), additionalOptions); | ||
var currentState = this.state || {}; | ||
state.width = updatedOptions.width; | ||
var _extractCloudinaryPro = (0, _Util.extractCloudinaryProps)(options), | ||
nonCloudinaryProps = _extractCloudinaryPro.nonCloudinaryProps; | ||
if (!_cloudinaryCore.Util.isEmpty(url) && url !== currentState.url) { | ||
state.url = url; | ||
} | ||
var attributes = _objectSpread(_objectSpread({}, (0, _Util.getImageTag)(options).attributes()), nonCloudinaryProps); // Set placeholder Id | ||
return state; | ||
} | ||
}, { | ||
key: "attachRef", | ||
value: function attachRef(element) { | ||
this.element = element; | ||
var innerRef = this.props.innerRef; | ||
if (innerRef) { | ||
if (innerRef instanceof Function) { | ||
innerRef(element); | ||
} else { | ||
innerRef.current = element; | ||
} | ||
} | ||
} | ||
}, { | ||
key: "handleResize", | ||
value: function handleResize() { | ||
var _this2 = this; | ||
if (placeholder && attributes.id) { | ||
attributes.id = attributes.id + '-cld-placeholder'; | ||
} // Remove unneeded attributes, | ||
if (!this.props.responsive || this.rqf) return; | ||
this.rqf = (0, _Util.requestAnimationFrame)(function () { | ||
_this2.rqf = null; | ||
var newState = _this2.prepareState(); | ||
["src", "accessibility", "placeholder"].forEach(function (attr) { | ||
delete attributes[attr]; | ||
}); // Set src or data-src attribute | ||
if (!_cloudinaryCore.Util.isEmpty(newState.url)) { | ||
_this2.setState(newState); | ||
} | ||
}); | ||
} | ||
}, { | ||
key: "componentDidMount", | ||
value: function componentDidMount() { | ||
// now that we have a this.element, we need to calculate the URL | ||
this.handleResize(); | ||
} | ||
}, { | ||
key: "componentWillUnmount", | ||
value: function componentWillUnmount() { | ||
this.element = undefined; | ||
var srcAttrName = isInView || !_this.shouldLazyLoad(options) ? "src" : "data-src"; | ||
attributes[srcAttrName] = (0, _Util.getConfiguredCloudinary)(options).url(options.publicId, options); | ||
return attributes; | ||
}); | ||
if (this.listener) { | ||
this.listener.cancel(); | ||
this.window && this.window.removeEventListener('resize', this.listener); | ||
_defineProperty(_assertThisInitialized(_this), "update", function () { | ||
if (_this.isResponsive()) { | ||
var removeListener = (0, _Util.makeElementResponsive)(_this.imgElement.current, _this.getOptions()); | ||
_this.listenerRemovers.push(removeListener); | ||
} | ||
this.listener = undefined; | ||
} | ||
}, { | ||
key: "componentDidUpdate", | ||
value: function componentDidUpdate(prevProps) { | ||
this.setState(this.prepareState()); | ||
if (_this.shouldLazyLoad(_this.getExtendedProps())) { | ||
_cloudinaryCore.Util.detectIntersection(_this.imgElement.current, _this.onIntersect); | ||
} | ||
}); | ||
if (this.state.responsive) { | ||
var wait = (0, _Util.firstDefined)(this.props.responsiveDebounce, this.getContext().responsiveDebounce, 100); | ||
_defineProperty(_assertThisInitialized(_this), "attachRef", function (imgElement) { | ||
var innerRef = _this.props.innerRef; | ||
_this.imgElement.current = imgElement; | ||
if (this.listener) { | ||
this.window && this.window.removeEventListener('resize', this.listener); | ||
if (innerRef) { | ||
if (innerRef instanceof Function) { | ||
innerRef(imgElement); | ||
} else { | ||
innerRef.current = imgElement; | ||
} | ||
this.listener = (0, _Util.debounce)(this.handleResize, wait); | ||
this.window && this.window.addEventListener('resize', this.listener); | ||
} | ||
} | ||
}, { | ||
key: "render", | ||
value: function render() { | ||
var _CloudinaryComponent$ = _CloudinaryComponent2["default"].normalizeOptions(this.props, this.getContext()), | ||
publicId = _CloudinaryComponent$.publicId, | ||
responsive = _CloudinaryComponent$.responsive, | ||
responsiveDebounce = _CloudinaryComponent$.responsiveDebounce, | ||
children = _CloudinaryComponent$.children, | ||
innerRef = _CloudinaryComponent$.innerRef, | ||
options = _objectWithoutProperties(_CloudinaryComponent$, ["publicId", "responsive", "responsiveDebounce", "children", "innerRef"]); | ||
}); | ||
var attributes = _cloudinaryCore["default"].Transformation["new"](options).toHtmlAttributes(); | ||
_defineProperty(_assertThisInitialized(_this), "shouldLazyLoad", function (_ref) { | ||
var loading = _ref.loading; | ||
return loading === "lazy" || loading === "auto"; | ||
}); | ||
var url = this.state.url; | ||
return /*#__PURE__*/_react["default"].createElement("img", _extends({}, attributes, { | ||
src: url, | ||
ref: this.attachRef | ||
})); | ||
} // Methods from cloudinary_js | ||
_defineProperty(_assertThisInitialized(_this), "handleImageLoaded", function () { | ||
var onLoad = _this.props.onLoad; | ||
}, { | ||
key: "findContainerWidth", | ||
value: function findContainerWidth() { | ||
var containerWidth, style; | ||
containerWidth = 0; | ||
var element = this.element; | ||
while ((0, _Util.isElement)(element = element != null ? element.parentNode : void 0) && !containerWidth) { | ||
style = this.window ? this.window.getComputedStyle(element) : ''; | ||
if (!/^inline/.test(style.display)) { | ||
containerWidth = _cloudinaryCore.Util.width(element); | ||
_this.setState({ | ||
isLoaded: true | ||
}, function () { | ||
if (onLoad) { | ||
onLoad(); | ||
} | ||
} | ||
}); | ||
}); | ||
return Math.round(containerWidth); | ||
} | ||
}, { | ||
key: "applyBreakpoints", | ||
value: function applyBreakpoints(width, steps, options) { | ||
options = _CloudinaryComponent2["default"].normalizeOptions(this.getContext(), this.props, options); | ||
var responsiveUseBreakpoints = options.responsiveUseBreakpoints; | ||
_this.imgElement = /*#__PURE__*/(0, _react.createRef)(); | ||
_this.state = { | ||
isLoaded: false | ||
}; | ||
_this.listenerRemovers = []; | ||
return _this; | ||
} | ||
/** | ||
* @return true when this image element should be made responsive, false otherwise. | ||
*/ | ||
if (!responsiveUseBreakpoints || responsiveUseBreakpoints === 'resize' && !options.resizing) { | ||
return width; | ||
} else { | ||
return this.calc_breakpoint(width, steps); | ||
} | ||
} | ||
}, { | ||
key: "calc_breakpoint", | ||
value: function calc_breakpoint(width, steps) { | ||
var breakpoints, point; | ||
breakpoints = this.state && this.state.breakpoints || defaultBreakpoints; | ||
if (_cloudinaryCore.Util.isFunction(breakpoints)) { | ||
return breakpoints(width, steps); | ||
} else { | ||
if (_cloudinaryCore.Util.isString(breakpoints)) { | ||
breakpoints = function () { | ||
var j, len, ref, results; | ||
ref = breakpoints.split(','); | ||
results = []; | ||
_createClass(Image, [{ | ||
key: "componentDidMount", | ||
for (j = 0, len = ref.length; j < len; j++) { | ||
point = ref[j]; | ||
results.push(parseInt(point)); | ||
} | ||
return results; | ||
}().sort(function (a, b) { | ||
return a - b; | ||
}); | ||
} | ||
return (0, _Util.closestAbove)(breakpoints, width); | ||
} | ||
/** | ||
* Invoked immediately after a component is mounted (inserted into the tree) | ||
*/ | ||
value: function componentDidMount() { | ||
this.update(); | ||
} | ||
}, { | ||
key: "device_pixel_ratio", | ||
value: function device_pixel_ratio() { | ||
var roundDpr = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; | ||
var dpr, dprString; | ||
dpr = (typeof this.window !== "undefined" && this.window !== null ? this.window.devicePixelRatio : void 0) || 1; | ||
/** | ||
* Invoked immediately after updating occurs. This method is not called for the initial render. | ||
*/ | ||
if (roundDpr) { | ||
dpr = Math.ceil(dpr); | ||
} | ||
if (dpr <= 0 || isNaN(dpr)) { | ||
dpr = 1; | ||
} | ||
dprString = dpr.toString(); | ||
if (dprString.match(/^\d+$/)) { | ||
dprString += '.0'; | ||
} | ||
return dprString; | ||
} | ||
}, { | ||
key: "updateDpr", | ||
value: function updateDpr(dataSrc, roundDpr) { | ||
return dataSrc.replace(/\bdpr_(1\.0|auto)\b/g, 'dpr_' + this.device_pixel_ratio(roundDpr)); | ||
key: "componentDidUpdate", | ||
value: function componentDidUpdate() { | ||
this.update(); | ||
} | ||
}, { | ||
key: "maxWidth", | ||
value: function maxWidth(requiredWidth) { | ||
return Math.max(this.state && this.state.width || 0, requiredWidth); | ||
key: "componentWillUnmount", | ||
value: function componentWillUnmount() { | ||
this.listenerRemovers.forEach(function (removeListener) { | ||
removeListener(); | ||
}); | ||
} | ||
/** | ||
* Updates this Image's isLoaded state, | ||
* And fires props.onLoad if exists. | ||
*/ | ||
}, { | ||
key: "cloudinaryUpdate", | ||
value: function cloudinaryUpdate(url) { | ||
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
var requiredWidth; | ||
var match; | ||
var resultUrl = this.updateDpr(url, options.roundDpr); | ||
key: "render", | ||
value: function render() { | ||
var attachRef = this.attachRef, | ||
getAttributes = this.getAttributes, | ||
handleImageLoaded = this.handleImageLoaded; | ||
if (options.responsive || this.state && this.state.responsive) { | ||
var containerWidth = this.findContainerWidth(); | ||
var _this$getExtendedProp = this.getExtendedProps(), | ||
children = _this$getExtendedProp.children; | ||
if (containerWidth !== 0) { | ||
if (/w_auto:breakpoints/.test(resultUrl)) { | ||
requiredWidth = this.maxWidth(containerWidth, this.element); | ||
resultUrl = resultUrl.replace(/w_auto:breakpoints([_0-9]*)(:[0-9]+)?/, "w_auto:breakpoints$1:" + requiredWidth); | ||
} else { | ||
match = /w_auto(:(\d+))?/.exec(resultUrl); | ||
var placeholder = this.getChildPlaceholder(children); | ||
var isLoaded = this.state.isLoaded; | ||
var attributes = getAttributes(); //If image wasn't loaded and there's a placeholder then we render it alongside the image. | ||
if (match) { | ||
requiredWidth = this.applyBreakpoints(containerWidth, match[2], options); | ||
requiredWidth = this.maxWidth(requiredWidth, this.element); | ||
resultUrl = resultUrl.replace(/w_auto[^,\/]*/g, "w_" + requiredWidth); | ||
} | ||
} | ||
} else { | ||
resultUrl = ""; | ||
} | ||
if (!isLoaded && placeholder) { | ||
var placeholderStyle = { | ||
display: isLoaded ? 'none' : 'inline' | ||
}; | ||
attributes.style = _objectSpread(_objectSpread({}, attributes.style || {}), {}, { | ||
opacity: 0, | ||
position: 'absolute' | ||
}); | ||
attributes.onLoad = handleImageLoaded; | ||
var placeholderAttributes = getAttributes({ | ||
placeholder: placeholder.props.type | ||
}); | ||
return /*#__PURE__*/_react["default"].createElement(_react.Fragment, null, /*#__PURE__*/_react["default"].createElement("img", _extends({ | ||
ref: attachRef | ||
}, attributes)), /*#__PURE__*/_react["default"].createElement("div", { | ||
style: placeholderStyle | ||
}, /*#__PURE__*/_react["default"].createElement("img", placeholderAttributes))); | ||
} | ||
return { | ||
url: resultUrl, | ||
width: requiredWidth | ||
}; | ||
return /*#__PURE__*/_react["default"].createElement("img", _extends({ | ||
ref: attachRef | ||
}, attributes)); | ||
} | ||
}, { | ||
key: "window", | ||
get: function get() { | ||
var windowRef = null; | ||
if (typeof window !== "undefined") { | ||
windowRef = window; | ||
} | ||
return this.element && this.element.ownerDocument ? this.element.ownerDocument.defaultView || windowRef : windowRef; | ||
} | ||
}]); | ||
@@ -358,3 +250,6 @@ | ||
Image.propTypes = _CloudinaryComponent2["default"].propTypes; | ||
Image.propTypes.responsive = _propTypes["default"].bool; | ||
Image.propTypes.loading = _propTypes["default"].string; | ||
Image.propTypes.accessibility = _propTypes["default"].string; | ||
var _default = Image; | ||
exports["default"] = _default; |
@@ -63,3 +63,4 @@ "use strict"; | ||
Transformation.exposesProps = true; | ||
Transformation.displayName = "CloudinaryTransformation"; | ||
var _default = Transformation; | ||
exports["default"] = _default; |
@@ -36,2 +36,8 @@ "use strict"; | ||
}); | ||
Object.defineProperty(exports, "Placeholder", { | ||
enumerable: true, | ||
get: function get() { | ||
return _Placeholder["default"]; | ||
} | ||
}); | ||
@@ -50,2 +56,4 @@ var _react = _interopRequireDefault(require("react")); | ||
var _Placeholder = _interopRequireDefault(require("./components/Placeholder")); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } |
@@ -18,3 +18,3 @@ "use strict"; | ||
var CLOUDINARY_PROPS = _cloudinaryCore.Transformation.PARAM_NAMES.map(_cloudinaryCore.Util.camelCase).reduce(function (accumulator, cloudinaryPropName) { | ||
var CLOUDINARY_PROPS = _cloudinaryCore.Transformation.PARAM_NAMES.map(_cloudinaryCore.Util.camelCase).concat(['publicId', 'breakpoints']).reduce(function (accumulator, cloudinaryPropName) { | ||
accumulator[cloudinaryPropName] = true; | ||
@@ -21,0 +21,0 @@ return accumulator; |
@@ -6,38 +6,5 @@ "use strict"; | ||
}); | ||
Object.defineProperty(exports, "debounce", { | ||
enumerable: true, | ||
get: function get() { | ||
return _debounce2["default"]; | ||
} | ||
}); | ||
Object.defineProperty(exports, "firstDefined", { | ||
enumerable: true, | ||
get: function get() { | ||
return _firstDefined2["default"]; | ||
} | ||
}); | ||
Object.defineProperty(exports, "closestAbove", { | ||
enumerable: true, | ||
get: function get() { | ||
return _closestAbove2["default"]; | ||
} | ||
}); | ||
Object.defineProperty(exports, "requestAnimationFrame", { | ||
enumerable: true, | ||
get: function get() { | ||
return _requestAnimationFrame.requestAnimationFrame; | ||
} | ||
}); | ||
Object.defineProperty(exports, "cancelAnimationFrame", { | ||
enumerable: true, | ||
get: function get() { | ||
return _requestAnimationFrame.cancelAnimationFrame; | ||
} | ||
}); | ||
Object.defineProperty(exports, "isElement", { | ||
enumerable: true, | ||
get: function get() { | ||
return _isElement2["default"]; | ||
} | ||
}); | ||
var _exportNames = { | ||
extractCloudinaryProps: true | ||
}; | ||
Object.defineProperty(exports, "extractCloudinaryProps", { | ||
@@ -50,14 +17,17 @@ enumerable: true, | ||
var _debounce2 = _interopRequireDefault(require("./debounce")); | ||
var _extractCloudinaryProps2 = _interopRequireDefault(require("./extractCloudinaryProps")); | ||
var _firstDefined2 = _interopRequireDefault(require("./firstDefined")); | ||
var _cloudinaryCoreUtils = require("./cloudinaryCoreUtils"); | ||
var _closestAbove2 = _interopRequireDefault(require("./closestAbove")); | ||
Object.keys(_cloudinaryCoreUtils).forEach(function (key) { | ||
if (key === "default" || key === "__esModule") return; | ||
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; | ||
Object.defineProperty(exports, key, { | ||
enumerable: true, | ||
get: function get() { | ||
return _cloudinaryCoreUtils[key]; | ||
} | ||
}); | ||
}); | ||
var _requestAnimationFrame = require("./requestAnimationFrame"); | ||
var _isElement2 = _interopRequireDefault(require("./isElement")); | ||
var _extractCloudinaryProps2 = _interopRequireDefault(require("./extractCloudinaryProps")); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } |
{ | ||
"name": "cloudinary-react", | ||
"version": "1.5.1", | ||
"version": "1.6.0", | ||
"description": "Present Cloudinary images and videos using React components", | ||
@@ -8,7 +8,9 @@ "main": "lib/index.js", | ||
"test": "node_modules/.bin/mocha --require @babel/register test/.setup.js --recursive test", | ||
"test:all": "run-s build test test-dist test-lib", | ||
"test:all": "run-s build test test-dist test-lib test:e2e", | ||
"test-dist": "TEST_SUBJECT=dist node_modules/.bin/mocha --require @babel/register test/.setup.js --recursive test", | ||
"test-lib": "TEST_SUBJECT=lib node_modules/.bin/mocha --require @babel/register test/.setup.js --recursive test", | ||
"pretest:e2e": "npm run build && npm pack && cpy cloudinary-react-*.tgz e2e-test --rename=cloudinary-react.tgz", | ||
"test:e2e": "cd ./e2e-test && npm run test", | ||
"prebuild": "node_modules/.bin/babel src --out-dir lib --copy-files ", | ||
"build": "node_modules/.bin/webpack && npm run build-storybook && npm run bundlewatch", | ||
"build": "node_modules/.bin/webpack && npm run bundlewatch", | ||
"bundlewatch": "bundlewatch --config ./bundlewatch.config.js", | ||
@@ -47,2 +49,3 @@ "storybook": "start-storybook -p 6006", | ||
"chai-string": "^1.4.0", | ||
"cpy-cli": "^3.1.1", | ||
"del-cli": "^3.0.0", | ||
@@ -52,3 +55,3 @@ "enzyme": "^3.10.0", | ||
"jsdom": "^11.12.0", | ||
"mocha": "^4.0.1", | ||
"mocha": "^8.0.1", | ||
"npm-run-all": "^4.1.5", | ||
@@ -60,3 +63,3 @@ "react-dom": "^16.3.3", | ||
"dependencies": { | ||
"cloudinary-core": "^2.9.0", | ||
"cloudinary-core": "^2.10.1", | ||
"prop-types": "^15.6.2" | ||
@@ -66,3 +69,8 @@ }, | ||
"react": "^16.3.3" | ||
} | ||
}, | ||
"files": [ | ||
"src", | ||
"lib", | ||
"dist" | ||
] | ||
} |
import React, {PureComponent} from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import {Cloudinary, Transformation, Util} from 'cloudinary-core'; | ||
import {Transformation, Util} from 'cloudinary-core'; | ||
import {CloudinaryContextType} from '../CloudinaryContext/CloudinaryContextType'; | ||
const camelCase = Util.camelCase; | ||
const {camelCase} = Util; | ||
/** | ||
* Check if given component is a Cloudinary Component with given displayName | ||
* @param component the component to check | ||
* @param displayName of wanted component | ||
* @return {boolean} | ||
*/ | ||
const isCloudinaryComponent = (component, displayName) => { | ||
return !!(React.isValidElement(component) | ||
&& component.type | ||
&& component.type.displayName === displayName); | ||
}; | ||
/** | ||
* Return a new object containing keys and values where keys are in the keys list | ||
@@ -15,3 +27,3 @@ * @param {object} source Object to copy values from | ||
function only(source, keys = []) { | ||
if(!source){ | ||
if (!source) { | ||
return source; | ||
@@ -35,3 +47,2 @@ } | ||
super(props, context); | ||
this.getContext = this.getContext.bind(this); | ||
} | ||
@@ -43,26 +54,33 @@ | ||
getContext(){ | ||
getContext = () => { | ||
return this.context || {}; | ||
} | ||
/** | ||
* React function: Called when this element is in view | ||
*/ | ||
onIntersect = () =>{ | ||
this.setState({isInView: true}) | ||
} | ||
getChildPlaceholder(children){ | ||
if (children) { | ||
return React.Children.toArray(children) | ||
.find(child => isCloudinaryComponent(child, "CloudinaryPlaceholder")); | ||
} | ||
} | ||
getChildTransformations(children) { | ||
if(children === undefined || children === null) return null; | ||
let mapped = React.Children.map(children, child =>{ | ||
if (!React.isValidElement(child)) { | ||
// child is not an element (e.g. simple text) | ||
return; | ||
} | ||
let options = {}; | ||
if (child.type && child.type.exposesProps){ | ||
options = CloudinaryComponent.normalizeOptions(child.props, child.context); | ||
} | ||
let childOptions = this.getChildTransformations(child.props.children); | ||
if(childOptions !== undefined && childOptions !== null){ | ||
options.transformation = childOptions; | ||
} | ||
return options; | ||
}); | ||
if(mapped != null){ | ||
return mapped.filter(o=>!Util.isEmpty(o)); | ||
} else return null; | ||
let result = children ? React.Children.toArray(children) | ||
.filter(child => isCloudinaryComponent(child, "CloudinaryTransformation")) | ||
.map(child => { | ||
const options = CloudinaryComponent.normalizeOptions(child.props, child.context); | ||
const childOptions = this.getChildTransformations(child.props.children); | ||
if (childOptions) { | ||
options.transformation = childOptions; | ||
} | ||
return options; | ||
}) : []; | ||
return result.length ? result : null; | ||
} | ||
@@ -78,3 +96,3 @@ | ||
getTransformation(extendedProps) { | ||
let {children, ...rest} = extendedProps; | ||
let {children, accessibility, placeholder, ...rest} = extendedProps; | ||
let ownTransformation = only(Util.withCamelCaseKeys(rest), Transformation.methods) || {}; | ||
@@ -85,2 +103,9 @@ let childrenOptions = this.getChildTransformations(children); | ||
} | ||
//Append placeholder and accessibility if exists | ||
const advancedTransformations = {accessibility, placeholder}; | ||
Object.keys(advancedTransformations).filter(k=>advancedTransformations[k]).map(k=>{ | ||
ownTransformation[k] = advancedTransformations[k]; | ||
}); | ||
return ownTransformation; | ||
@@ -97,4 +122,4 @@ } | ||
static normalizeOptions(...options) { | ||
return options.reduce((left, right)=> { | ||
for (let key in right) { | ||
return options.reduce((left, right) => { | ||
Object.keys(right || {}).forEach(key => { | ||
let value = right[key]; | ||
@@ -104,3 +129,3 @@ if (value !== null && value !== undefined) { | ||
} | ||
} | ||
}); | ||
return left; | ||
@@ -112,2 +137,12 @@ } | ||
/** | ||
* Generated a configured Cloudinary object. | ||
* @param extendedProps React props combined with custom Cloudinary configuration options | ||
* @return {Cloudinary} configured using extendedProps | ||
*/ | ||
getConfiguredCloudinary(extendedProps){ | ||
const options = Util.extractUrlParams(Util.withSnakeCaseKeys(extendedProps)); | ||
return Cloudinary.new(options); | ||
} | ||
/** | ||
* Generate a Cloudinary resource URL based on the options provided and child Transformation elements | ||
@@ -119,8 +154,17 @@ * @param extendedProps React props combined with custom Cloudinary configuration options | ||
getUrl(extendedProps) { | ||
let transformation = this.getTransformation(extendedProps); | ||
let options = Util.extractUrlParams(Util.withSnakeCaseKeys(extendedProps)); | ||
let cl = Cloudinary.new(options); | ||
return cl.url(extendedProps.publicId, transformation); | ||
const {publicId} = extendedProps; | ||
const cl = getConfiguredCloudinary(extendedProps); | ||
return cl.url(publicId, this.getTransformation(extendedProps)); | ||
} | ||
/** | ||
* Merges context & props | ||
* @param props of this component | ||
* @param context of this component | ||
* @return {Object} | ||
*/ | ||
getExtendedProps = (props = this.props, context = this.getContext()) => { | ||
return CloudinaryComponent.normalizeOptions(context, props); | ||
}; | ||
static contextType = CloudinaryContextType; | ||
@@ -131,3 +175,2 @@ } | ||
CloudinaryComponent.propTypes.publicId = PropTypes.string; | ||
CloudinaryComponent.propTypes.responsive = PropTypes.bool; | ||
@@ -143,3 +186,3 @@ /** | ||
const types = {}; | ||
for (let i =0; i < configParams.length; i++) { | ||
for (let i = 0; i < configParams.length; i++) { | ||
const key = configParams[i]; | ||
@@ -146,0 +189,0 @@ types[camelCase(key)] = PropTypes.any; |
@@ -1,10 +0,7 @@ | ||
import React from 'react'; | ||
import cloudinary, {Util} from 'cloudinary-core'; | ||
import React, {createRef, Fragment} from 'react'; | ||
import CloudinaryComponent from '../CloudinaryComponent'; | ||
import {debounce, firstDefined, closestAbove, requestAnimationFrame, isElement} from '../../Util'; | ||
import {extractCloudinaryProps, getImageTag, makeElementResponsive, getConfiguredCloudinary} from "../../Util"; | ||
import {Util} from "cloudinary-core"; | ||
import PropTypes from "prop-types"; | ||
const defaultBreakpoints = (width, steps = 100) => { | ||
return steps * Math.ceil(width / steps); | ||
}; | ||
/** | ||
@@ -16,205 +13,147 @@ * A component representing a Cloudinary served image | ||
super(props, context); | ||
this.handleResize = this.handleResize.bind(this); | ||
this.attachRef = this.attachRef.bind(this); | ||
this.imgElement = createRef(); | ||
this.state = {isLoaded: false} | ||
this.listenerRemovers = []; | ||
} | ||
let state = {responsive: false, url: undefined, breakpoints: defaultBreakpoints}; | ||
this.state = {...state, ...this.prepareState(props, context)}; | ||
/** | ||
* @return true when this image element should be made responsive, false otherwise. | ||
*/ | ||
isResponsive = () => { | ||
return this.props.responsive && this.imgElement && this.imgElement.current; | ||
} | ||
/** | ||
* Retrieve the window or default view of the current element | ||
* @returns {DocumentView|*} | ||
* @private | ||
* @return merged props & context with aggregated transformation, excluding children and innerRef. | ||
*/ | ||
get window() { | ||
let windowRef = null; | ||
if (typeof window !== "undefined") { | ||
windowRef = window | ||
} | ||
return (this.element && this.element.ownerDocument) ? (this.element.ownerDocument.defaultView || windowRef) : windowRef; | ||
getOptions = () => { | ||
const extendedProps = this.getExtendedProps(); | ||
const {children, innerRef, ...options} = {...extendedProps, ...this.getTransformation(extendedProps)}; | ||
return options; | ||
} | ||
prepareState(props = this.props, context = this.getContext()) { | ||
let extendedProps = CloudinaryComponent.normalizeOptions(context, props); | ||
let url = this.getUrl(extendedProps); | ||
let state = {}; | ||
let updatedOptions = {}; | ||
/** | ||
* @param additionalOptions - extra options to pass to cloudinary.url(), for example: placeholder | ||
* @return attributes for the underlying <img> element. | ||
*/ | ||
getAttributes = (additionalOptions = {}) => { | ||
const {isInView} = this.state; | ||
const {placeholder} = additionalOptions; | ||
const options = {...this.getOptions(), ...additionalOptions}; | ||
const {nonCloudinaryProps} = extractCloudinaryProps(options); | ||
if (extendedProps.breakpoints !== undefined) { | ||
state.breakpoints = extendedProps.breakpoints; | ||
let attributes = {...getImageTag(options).attributes(), ...nonCloudinaryProps}; | ||
// Set placeholder Id | ||
if (placeholder && attributes.id) { | ||
attributes.id = attributes.id + '-cld-placeholder'; | ||
} | ||
if (extendedProps.responsive) { | ||
state.responsive = true; | ||
updatedOptions = this.cloudinaryUpdate(url, state); | ||
url = updatedOptions.url; | ||
} | ||
let currentState = this.state || {}; | ||
// Remove unneeded attributes, | ||
["src", "accessibility", "placeholder"].forEach(attr => { | ||
delete attributes[attr]; | ||
}); | ||
state.width = updatedOptions.width; | ||
// Set src or data-src attribute | ||
const srcAttrName = isInView || !this.shouldLazyLoad(options) ? "src" : "data-src"; | ||
attributes[srcAttrName] = getConfiguredCloudinary(options).url(options.publicId, options); | ||
if (!Util.isEmpty(url) && url !== currentState.url) { | ||
state.url = url; | ||
return attributes; | ||
} | ||
/** | ||
* Update this image using cloudinary-core | ||
*/ | ||
update = () => { | ||
if (this.isResponsive()) { | ||
const removeListener = makeElementResponsive(this.imgElement.current, this.getOptions()); | ||
this.listenerRemovers.push(removeListener); | ||
} | ||
return state; | ||
if (this.shouldLazyLoad(this.getExtendedProps())) { | ||
Util.detectIntersection(this.imgElement.current, this.onIntersect); | ||
} | ||
} | ||
attachRef(element) { | ||
this.element = element; | ||
/** | ||
* Attach both this.imgElement and props.innerRef as ref to the given element | ||
* @param imgElement - the element to attach a ref to | ||
*/ | ||
attachRef = (imgElement) => { | ||
const {innerRef} = this.props; | ||
this.imgElement.current = imgElement; | ||
if (innerRef) { | ||
if (innerRef instanceof Function) { | ||
innerRef(element); | ||
innerRef(imgElement); | ||
} else { | ||
innerRef.current = element; | ||
innerRef.current = imgElement; | ||
} | ||
} | ||
} | ||
}; | ||
handleResize() { | ||
if (!this.props.responsive || this.rqf) return; | ||
this.rqf = requestAnimationFrame(() => { | ||
this.rqf = null; | ||
let newState = this.prepareState(); | ||
if (!Util.isEmpty(newState.url)) { | ||
this.setState(newState); | ||
} | ||
}); | ||
shouldLazyLoad = ({loading}) => { | ||
return loading === "lazy" || loading === "auto"; | ||
} | ||
/** | ||
* Invoked immediately after a component is mounted (inserted into the tree) | ||
*/ | ||
componentDidMount() { | ||
// now that we have a this.element, we need to calculate the URL | ||
this.handleResize(); | ||
this.update(); | ||
} | ||
/** | ||
* Invoked immediately after updating occurs. This method is not called for the initial render. | ||
*/ | ||
componentDidUpdate() { | ||
this.update(); | ||
} | ||
componentWillUnmount() { | ||
this.element = undefined; | ||
if (this.listener) { | ||
this.listener.cancel(); | ||
this.window && this.window.removeEventListener('resize', this.listener); | ||
} | ||
this.listener = undefined; | ||
this.listenerRemovers.forEach(removeListener => { | ||
removeListener(); | ||
}); | ||
} | ||
componentDidUpdate(prevProps) { | ||
this.setState(this.prepareState()); | ||
if (this.state.responsive) { | ||
const wait = firstDefined(this.props.responsiveDebounce, this.getContext().responsiveDebounce, 100); | ||
if (this.listener) { | ||
this.window && this.window.removeEventListener('resize', this.listener); | ||
/** | ||
* Updates this Image's isLoaded state, | ||
* And fires props.onLoad if exists. | ||
*/ | ||
handleImageLoaded = () => { | ||
const {onLoad} = this.props; | ||
this.setState({isLoaded: true}, () => { | ||
if (onLoad) { | ||
onLoad(); | ||
} | ||
this.listener = debounce(this.handleResize, wait); | ||
this.window && this.window.addEventListener('resize', this.listener); | ||
} | ||
} | ||
}); | ||
}; | ||
render() { | ||
const {publicId, responsive, responsiveDebounce, children, innerRef, ...options} = | ||
CloudinaryComponent.normalizeOptions(this.props, this.getContext()); | ||
const attributes = cloudinary.Transformation.new(options).toHtmlAttributes(); | ||
const {url} = this.state; | ||
return ( | ||
<img {...attributes} src={url} ref={this.attachRef}/> | ||
); | ||
} | ||
const {attachRef, getAttributes, handleImageLoaded} = this; | ||
const {children} = this.getExtendedProps(); | ||
const placeholder = this.getChildPlaceholder(children); | ||
const {isLoaded} = this.state; | ||
// Methods from cloudinary_js | ||
const attributes = getAttributes(); | ||
findContainerWidth() { | ||
var containerWidth, style; | ||
containerWidth = 0; | ||
let element = this.element; | ||
while (isElement((element = element != null ? element.parentNode : void 0)) && !containerWidth) { | ||
style = this.window ? this.window.getComputedStyle(element) : ''; | ||
if (!/^inline/.test(style.display)) { | ||
containerWidth = Util.width(element); | ||
} | ||
} | ||
return Math.round(containerWidth); | ||
}; | ||
//If image wasn't loaded and there's a placeholder then we render it alongside the image. | ||
if (!isLoaded && placeholder) { | ||
const placeholderStyle = {display: isLoaded ? 'none' : 'inline'} | ||
attributes.style = {...(attributes.style || {}), opacity: 0, position: 'absolute'} | ||
attributes.onLoad = handleImageLoaded; | ||
const placeholderAttributes = getAttributes({placeholder: placeholder.props.type}); | ||
applyBreakpoints(width, steps, options) { | ||
options = CloudinaryComponent.normalizeOptions(this.getContext(), this.props, options); | ||
let responsiveUseBreakpoints = options.responsiveUseBreakpoints; | ||
if ((!responsiveUseBreakpoints) || (responsiveUseBreakpoints === 'resize' && !options.resizing)) { | ||
return width; | ||
} else { | ||
return this.calc_breakpoint(width, steps); | ||
return ( | ||
<Fragment> | ||
<img ref={attachRef} {...attributes} /> | ||
<div style={placeholderStyle}> | ||
<img {...placeholderAttributes}/> | ||
</div> | ||
</Fragment> | ||
); | ||
} | ||
}; | ||
calc_breakpoint(width, steps) { | ||
var breakpoints, point; | ||
breakpoints = (this.state && this.state.breakpoints) || defaultBreakpoints; | ||
if (Util.isFunction(breakpoints)) { | ||
return breakpoints(width, steps); | ||
} else { | ||
if (Util.isString(breakpoints)) { | ||
breakpoints = ((function () { | ||
var j, len, ref, results; | ||
ref = breakpoints.split(','); | ||
results = []; | ||
for (j = 0, len = ref.length; j < len; j++) { | ||
point = ref[j]; | ||
results.push(parseInt(point)); | ||
} | ||
return results; | ||
})()).sort(function (a, b) { | ||
return a - b; | ||
}); | ||
} | ||
return closestAbove(breakpoints, width); | ||
} | ||
}; | ||
device_pixel_ratio(roundDpr = true) { | ||
var dpr, dprString; | ||
dpr = (typeof this.window !== "undefined" && this.window !== null ? this.window.devicePixelRatio : void 0) || 1; | ||
if (roundDpr) { | ||
dpr = Math.ceil(dpr); | ||
} | ||
if (dpr <= 0 || isNaN(dpr)) { | ||
dpr = 1; | ||
} | ||
dprString = dpr.toString(); | ||
if (dprString.match(/^\d+$/)) { | ||
dprString += '.0'; | ||
} | ||
return dprString; | ||
}; | ||
updateDpr(dataSrc, roundDpr) { | ||
return dataSrc.replace(/\bdpr_(1\.0|auto)\b/g, 'dpr_' + this.device_pixel_ratio(roundDpr)); | ||
}; | ||
maxWidth(requiredWidth) { | ||
return Math.max((this.state && this.state.width) || 0, requiredWidth); | ||
}; | ||
cloudinaryUpdate(url, options = {}) { | ||
var requiredWidth; | ||
var match; | ||
let resultUrl = this.updateDpr(url, options.roundDpr); | ||
if (options.responsive || this.state && this.state.responsive) { | ||
let containerWidth = this.findContainerWidth(); | ||
if (containerWidth !== 0) { | ||
if (/w_auto:breakpoints/.test(resultUrl)) { | ||
requiredWidth = this.maxWidth(containerWidth, this.element); | ||
resultUrl = resultUrl.replace(/w_auto:breakpoints([_0-9]*)(:[0-9]+)?/, | ||
"w_auto:breakpoints$1:" + requiredWidth); | ||
} else { | ||
match = /w_auto(:(\d+))?/.exec(resultUrl); | ||
if (match) { | ||
requiredWidth = this.applyBreakpoints(containerWidth, match[2], options); | ||
requiredWidth = this.maxWidth(requiredWidth, this.element); | ||
resultUrl = resultUrl.replace(/w_auto[^,\/]*/g, "w_" + requiredWidth); | ||
} | ||
} | ||
} else { | ||
resultUrl = ""; | ||
} | ||
} | ||
return {url: resultUrl, width: requiredWidth}; | ||
return <img ref={attachRef} {...attributes}/> | ||
} | ||
@@ -225,3 +164,6 @@ } | ||
Image.propTypes = CloudinaryComponent.propTypes; | ||
Image.propTypes.responsive = PropTypes.bool; | ||
Image.propTypes.loading = PropTypes.string; | ||
Image.propTypes.accessibility = PropTypes.string; | ||
export default Image; |
@@ -20,3 +20,4 @@ import React from 'react'; | ||
Transformation.exposesProps = true; | ||
Transformation.displayName = "CloudinaryTransformation"; | ||
export default Transformation; |
@@ -7,3 +7,4 @@ import React from 'react'; | ||
import Audio from './components/Audio'; | ||
import Placeholder from './components/Placeholder'; | ||
export { CloudinaryContext, Image, Transformation, Video, Audio}; | ||
export { CloudinaryContext, Image, Transformation, Video, Audio, Placeholder}; |
@@ -6,3 +6,3 @@ import {Transformation, Util} from 'cloudinary-core'; | ||
// Map Cloudinary props from array to object for efficient lookup | ||
const CLOUDINARY_PROPS = Transformation.PARAM_NAMES.map(Util.camelCase).reduce( | ||
const CLOUDINARY_PROPS = Transformation.PARAM_NAMES.map(Util.camelCase).concat(['publicId', 'breakpoints']).reduce( | ||
(accumulator, cloudinaryPropName) => { | ||
@@ -9,0 +9,0 @@ accumulator[cloudinaryPropName] = true; |
@@ -1,6 +0,2 @@ | ||
export debounce from './debounce'; | ||
export firstDefined from './firstDefined'; | ||
export closestAbove from './closestAbove'; | ||
export {requestAnimationFrame, cancelAnimationFrame } from './requestAnimationFrame'; | ||
export isElement from './isElement'; | ||
export extractCloudinaryProps from './extractCloudinaryProps'; | ||
export * from './cloudinaryCoreUtils'; |
Sorry, the diff of this file is too big to display
274879
28
45
2031
Updatedcloudinary-core@^2.10.1