diff --git a/CHANGELOG.md b/CHANGELOG.md index 1902e39..3d20d76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,29 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p --- +## [0.3.0](https://github.com/FortAwesome/react-native-fontawesome/releases/tag/0.3.0) - 2022-06-07 + +**This release has a couple of breaking changes.** + +1. Minimum supported version of React Native is 0.67 +1. Minimum supported react-native-svg is 11.x +1. Using `width` or `height` props are not allowed (they were deprecated in 0.2.x) +1. The `secondaryOpacity` will default to 40% (0.4) instead of 100% to match other Font Awesome implementations + +### Added + +- Support for specifying icons as strings like `icon="fa-solid fa-mug-empty"` +- Optional testId to TypeScript .d.ts file +- Prop `maskId` allows Jest snapshot testing to have consistent results when using masks + +### Fixed + +- Full support for version 6 of Font Awesome +- Using icons with masks should now be fully functional +- Duotone icons have also been fixed + +--- + ## [0.2.7](https://github.com/FortAwesome/react-native-fontawesome/releases/tag/0.2.7) - 2021-07-22 ### Changed diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 9b6e5b8..c8d6f57 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -23,7 +23,7 @@ to get your environment set up in your OS. For Mac OS X, that would look like: ## Launch the Example App -In the `examples/Hello` subdirectory, the following script commands are available: +In the `examples/react-native-expo` subdirectory, the following script commands are available: | Command | Purpose | | ------- | ------------------------------------- | @@ -34,15 +34,15 @@ In the `examples/Hello` subdirectory, the following script commands are availabl | clean | clean out build cache. Useful when the build isn't working and you're pretty sure it should be | In one terminal tab: -1. `cd examples/Hello` +1. `cd examples/react-native-expo` 1. `npm install` 1. `npm run start` This will get the JavaScript bundler running and listening for connections from a device or iOS Simulator. In another terminal tab: -1 `cd examples/Hello` -1. `react-native link react-native-svg` # to link the native components in the ios project +1 `cd examples/react-native-expo` +1. `react-native link react-native-svg` # to link the native components in the ios project 1. `npm run ios` This will build the project via XCode, launch the iOS Simulator, and when the project builds successfully, @@ -56,7 +56,7 @@ Seems like things don't always go smoothly and you have to [use some hackery](ht Try this: 1. shutdown any instances of the bundler you have running. 1. `npm run clean` -1. `npm run start-with-cache-reset` # from the examples/Hello directory +1. `npm run start-with-cache-reset` # from the examples/react-native-expo directory From another terminal tab: 1. `npm run ios` @@ -67,13 +67,13 @@ For now, we're just using the GitHub repo as our source for development versions test changes to the component using the example app, you'll have to push the component changes to a development branch and then update the `package.json` of the example app to pull the component from that branch. -Here's the step-by-step: +Here's the step-by-step: 1. make changes to this component 1. `npm run dist` # to transpile via babel into `dist/` 1. `git commit` # whatever changes you're trying to commit 1. `git push origin my-dev` # to whatever topic branch you're working on, say "my-dev" -1. Modify `examples/Hello/package.json` and find the line that looks like this: +1. Modify `examples/react-native-expo/package.json` and find the line that looks like this: `"@fortawesome/react-native-fontawesome": "^0.0.1"` And change it to something like this: `"@fortawesome/react-native-fontawesome": "https://github.com/FortAwesome/react-native-fontawesome#my-dev"` diff --git a/README.md b/README.md index 46a53c5..6beb773 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![npm](https://img.shields.io/npm/v/@fortawesome/react-native-fontawesome.svg?style=flat-square)](https://www.npmjs.com/package/@fortawesome/react-native-fontawesome) -> Font Awesome 5 React Native component using SVG with JS +> Font Awesome React Native component using SVG with JS @@ -126,7 +126,7 @@ You can use Font Awesome icons in your React Native components as simply as this That simple usage is made possible when you add the `"mug-saucer"` icon, to the _library_. -This is one of the two ways you can use Font Awesome 5 with React Native. We'll +This is one of the two ways you can use Font Awesome with React Native. We'll summarize both ways briefly and then get into the details of each below. 1. **Explicit Import** diff --git a/dist/components/FontAwesomeIcon.js b/dist/components/FontAwesomeIcon.js index d0995b9..3d1274d 100644 --- a/dist/components/FontAwesomeIcon.js +++ b/dist/components/FontAwesomeIcon.js @@ -3,8 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true }); +exports.DEFAULT_SIZE = exports.DEFAULT_SECONDARY_OPACITY = exports.DEFAULT_COLOR = void 0; exports["default"] = FontAwesomeIcon; -exports.DEFAULT_SECONDARY_OPACITY = exports.DEFAULT_COLOR = exports.DEFAULT_SIZE = void 0; var _react = _interopRequireDefault(require("react")); @@ -18,45 +18,44 @@ var _fontawesomeSvgCore = require("@fortawesome/fontawesome-svg-core"); var _logger = _interopRequireDefault(require("../logger")); +var _excluded = ["color"]; + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": 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 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; } +function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } -function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } -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); } +function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } 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; } -var _Dimensions$get = _reactNative.Dimensions.get('window'), - windowWidth = _Dimensions$get.width, - windowHeight = _Dimensions$get.height; - var DEFAULT_SIZE = 16; exports.DEFAULT_SIZE = DEFAULT_SIZE; var DEFAULT_COLOR = '#000'; exports.DEFAULT_COLOR = DEFAULT_COLOR; -var DEFAULT_SECONDARY_OPACITY = 0.4; // Deprecated height and width defaults - +var DEFAULT_SECONDARY_OPACITY = 0.4; exports.DEFAULT_SECONDARY_OPACITY = DEFAULT_SECONDARY_OPACITY; -var DEFAULT_HEIGHT = windowHeight * 0.1; -var DEFAULT_WIDTH = windowWidth * 0.1; function objectWithKey(key, value) { return Array.isArray(value) && value.length > 0 || !Array.isArray(value) && value ? _defineProperty({}, key, value) : {}; } function normalizeIconArgs(icon) { - if (icon === null) { - return null; + if (icon && _typeof(icon) === 'object' && icon.prefix && icon.iconName && icon.icon) { + return icon; } - if (_typeof(icon) === 'object' && icon.prefix && icon.iconName) { - return icon; + if (_fontawesomeSvgCore.parse.icon) { + return _fontawesomeSvgCore.parse.icon(icon); + } + + if (icon === null) { + return null; } if (Array.isArray(icon) && icon.length === 2) { @@ -77,6 +76,7 @@ function normalizeIconArgs(icon) { function FontAwesomeIcon(props) { var iconArgs = props.icon, maskArgs = props.mask, + maskId = props.maskId, height = props.height, width = props.width, size = props.size; @@ -86,10 +86,12 @@ function FontAwesomeIcon(props) { var iconLookup = normalizeIconArgs(iconArgs); var transform = objectWithKey('transform', typeof props.transform === 'string' ? _fontawesomeSvgCore.parse.transform(props.transform) : props.transform); var mask = objectWithKey('mask', normalizeIconArgs(maskArgs)); - var renderedIcon = (0, _fontawesomeSvgCore.icon)(iconLookup, _objectSpread(_objectSpread({}, transform), mask)); + var renderedIcon = (0, _fontawesomeSvgCore.icon)(iconLookup, _objectSpread(_objectSpread(_objectSpread({}, transform), mask), {}, { + maskId: maskId + })); if (!renderedIcon) { - (0, _logger["default"])("ERROR: icon not found for icon = ", iconArgs); + (0, _logger["default"])('ERROR: icon not found for icon = ', iconArgs); return null; } @@ -98,44 +100,29 @@ function FontAwesomeIcon(props) { var color = props.color || style.color || DEFAULT_COLOR; // This is the color that will be passed to the "fill" prop of the secondary Path element child (in Duotone Icons) // `null` value will result in using the primary color, at 40% opacity - var secondaryColor = props.secondaryColor || null; // Secondary layer opacity should default to 0.4, unless a specific opacity value or a specific secondary color was given + var secondaryColor = props.secondaryColor || color; // Secondary layer opacity should default to 0.4, unless a specific opacity value or a specific secondary color was given - var secondaryOpacity = props.secondaryOpacity || (secondaryColor ? 1 : DEFAULT_SECONDARY_OPACITY); // To avoid confusion down the line, we'll remove properties from the StyleSheet, like color, that are being overridden + var secondaryOpacity = props.secondaryOpacity || DEFAULT_SECONDARY_OPACITY; // To avoid confusion down the line, we'll remove properties from the StyleSheet, like color, that are being overridden // or resolved in other ways, to avoid ambiguity as to which inputs cause which outputs in the underlying rendering process. // In other words, we don't want color (for example) to be specified via two different inputs. var styleColor = style.color, - modifiedStyle = _objectWithoutProperties(style, ["color"]); + modifiedStyle = _objectWithoutProperties(style, _excluded); var resolvedHeight, resolvedWidth; if (height || width) { - if (size) { - console.warn("DEPRECATION: height and width props on ".concat(FontAwesomeIcon.displayName, " have been deprecated. ") + "Since you've also provided a size prop, we'll use it to override the height and width props given. " + "You should probably go ahead and remove the height and width props to avoid confusion and resolve this warning."); - resolvedHeight = resolvedWidth = size; - } else { - console.warn("DEPRECATION: height and width props on ".concat(FontAwesomeIcon.displayName, " have been deprecated. ") + "Use the size prop instead."); - resolvedHeight = height || DEFAULT_HEIGHT; - resolvedWidth = width || DEFAULT_WIDTH; - } + throw new Error("Prop height and width for component ".concat(FontAwesomeIcon.displayName, " have been deprecated. ") + "Use the size prop instead like <".concat(FontAwesomeIcon.displayName, " size={").concat(width, "} />.")); } else { resolvedHeight = resolvedWidth = size || DEFAULT_SIZE; } - var extraProps = { - height: resolvedHeight, - width: resolvedWidth, - fill: color, - secondaryFill: secondaryColor, - secondaryOpacity: secondaryOpacity, - style: modifiedStyle - }; - Object.keys(props).forEach(function (key) { - if (!FontAwesomeIcon.defaultProps.hasOwnProperty(key)) { - extraProps[key] = props[key]; - } - }); - return convertCurry(_abstract[0], extraProps); + var rootAttributes = _abstract[0].attributes; + rootAttributes.height = resolvedHeight; + rootAttributes.width = resolvedWidth; + rootAttributes.style = modifiedStyle; + replaceCurrentColor(_abstract[0], color, secondaryColor, secondaryOpacity); + return convertCurry(_abstract[0]); } FontAwesomeIcon.displayName = 'FontAwesomeIcon'; @@ -151,20 +138,50 @@ FontAwesomeIcon.propTypes = { }), _propTypes["default"].array]), icon: _propTypes["default"].oneOfType([_propTypes["default"].object, _propTypes["default"].array, _propTypes["default"].string]), mask: _propTypes["default"].oneOfType([_propTypes["default"].object, _propTypes["default"].array, _propTypes["default"].string]), + maskId: _propTypes["default"].string, transform: _propTypes["default"].oneOfType([_propTypes["default"].string, _propTypes["default"].object]) }; FontAwesomeIcon.defaultProps = { icon: null, mask: null, + maskId: null, transform: null, style: {}, color: null, secondaryColor: null, secondaryOpacity: null, - height: undefined, - width: undefined // Once the deprecation of height and width props is complete, let's put the real default prop value for size here. - // For now, adding it breaks the default/override logic for height/width/size. - + size: DEFAULT_SIZE }; -var convertCurry = _converter["default"].bind(null, _react["default"].createElement); \ No newline at end of file +var convertCurry = _converter["default"].bind(null, _react["default"].createElement); + +function replaceCurrentColor(obj, primaryColor, secondaryColor, secondaryOpacity) { + obj.children.forEach(function (child, childIndex) { + replaceFill(child, primaryColor, secondaryColor, secondaryOpacity); + + if (Object.prototype.hasOwnProperty.call(child, 'attributes')) { + replaceFill(child.attributes, primaryColor, secondaryColor, secondaryOpacity); + } + + if (Array.isArray(child.children) && child.children.length > 0) { + replaceCurrentColor(child, primaryColor, secondaryColor, secondaryOpacity); + } + }); +} + +function replaceFill(obj, primaryColor, secondaryColor, secondaryOpacity) { + if (hasPropertySetToValue(obj, 'fill', 'currentColor')) { + if (hasPropertySetToValue(obj, 'class', 'fa-primary')) { + obj.fill = primaryColor; + } else if (hasPropertySetToValue(obj, 'class', 'fa-secondary')) { + obj.fill = secondaryColor; + obj.fillOpacity = secondaryOpacity; + } else { + obj.fill = primaryColor; + } + } +} + +function hasPropertySetToValue(obj, property, value) { + return Object.prototype.hasOwnProperty.call(obj, property) && obj[property] === value; +} \ No newline at end of file diff --git a/dist/converter.js b/dist/converter.js index 5b737a8..ce4a238 100644 --- a/dist/converter.js +++ b/dist/converter.js @@ -11,50 +11,41 @@ var _reactNativeSvg = require("react-native-svg"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } +function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } + +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } + +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 _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } -function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter); } +function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); } function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } -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; } - -function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } - -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; } - var svgObjectMap = { - "svg": _reactNativeSvg.Svg, - "path": _reactNativeSvg.Path, - "rect": _reactNativeSvg.Rect, - "defs": _reactNativeSvg.Defs, - "mask": _reactNativeSvg.Mask, - "g": _reactNativeSvg.G, - "clipPath": _reactNativeSvg.ClipPath + svg: _reactNativeSvg.Svg, + path: _reactNativeSvg.Path, + rect: _reactNativeSvg.Rect, + defs: _reactNativeSvg.Defs, + mask: _reactNativeSvg.Mask, + g: _reactNativeSvg.G, + clipPath: _reactNativeSvg.ClipPath }; function convert(createElement, element) { - var extraProps = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; - if (typeof element === 'string') { return element; } - var isDuotone = (element.children || []).length === 2; var children = (element.children || []).map(function (child, childIndex) { - var isDuotoneSecondLayer = isDuotone && childIndex === 0; - var fill = isDuotoneSecondLayer ? extraProps.secondaryFill : extraProps.fill; - var fillOpacity = isDuotoneSecondLayer ? extraProps.secondaryOpacity : 1; - return convert(createElement, child, _objectSpread(_objectSpread({}, extraProps), {}, { - fill: fill, - fillOpacity: fillOpacity - })); + return convert(createElement, child); }); var mixins = Object.keys(element.attributes || {}).reduce(function (acc, key) { var val = element.attributes[key]; @@ -62,17 +53,16 @@ function convert(createElement, element) { switch (key) { case 'class': case 'role': - case 'style': case 'xmlns': delete element.attributes[key]; break; case 'focusable': - acc.attrs[key] = val === 'true' ? true : false; + acc.attrs[key] = val === 'true'; break; default: - if (key.indexOf('aria-') === 0 || key.indexOf('data-') === 0 || 'fill' === key && 'currentColor' === val) { + if (key.indexOf('aria-') === 0 || key.indexOf('data-') === 0 || key === 'fill' && val === 'currentColor') { delete element.attributes[key]; } else { acc.attrs[_humps["default"].camelize(key)] = val; @@ -84,7 +74,7 @@ function convert(createElement, element) { }, { attrs: {} }); - return createElement.apply(void 0, [svgObjectMap[element.tag], _objectSpread(_objectSpread({}, mixins.attrs), extraProps)].concat(_toConsumableArray(children))); + return createElement.apply(void 0, [svgObjectMap[element.tag], _objectSpread({}, mixins.attrs)].concat(_toConsumableArray(children))); } var _default = convert; diff --git a/package.json b/package.json index 4af4524..f1a3f41 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@fortawesome/react-native-fontawesome", "version": "0.3.0", - "description": "Official React Native component for Font Awesome 5", + "description": "Official React Native component for Font Awesome", "main": "index.js", "scripts": { "test": "jest --forceExit",