You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

functions.any.js 7.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. /* @flow */
  2. import Platform from '../react/Platform';
  3. import { ColorPalette } from './components';
  4. declare type StyleSheet = Object;
  5. export type StyleType = StyleSheet | Array<StyleSheet>;
  6. /**
  7. * RegExp pattern for long HEX color format.
  8. */
  9. const HEX_LONG_COLOR_FORMAT
  10. = /^#([0-9A-F]{2,2})([0-9A-F]{2,2})([0-9A-F]{2,2})$/i;
  11. /**
  12. * RegExp pattern for short HEX color format.
  13. */
  14. const HEX_SHORT_COLOR_FORMAT
  15. = /^#([0-9A-F]{1,1})([0-9A-F]{1,1})([0-9A-F]{1,1})$/i;
  16. /**
  17. * RegExp pattern for RGB color format.
  18. */
  19. const RGB_COLOR_FORMAT = /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/i;
  20. /**
  21. * RegExp pattern for RGBA color format.
  22. */
  23. const RGBA_COLOR_FORMAT
  24. = /^rgba\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3}),\s*([0-9.]+)\)$/i;
  25. /**
  26. * The list of the well-known style properties which may not be numbers on Web
  27. * but must be numbers on React Native.
  28. *
  29. * @private
  30. */
  31. const _WELL_KNOWN_NUMBER_PROPERTIES = [ 'height', 'width' ];
  32. /**
  33. * Function to convert complex StyleType styles into a single flat object,
  34. * so then they can be deconstructed for further processing.
  35. *
  36. * @param {Styletype} st - The complex style type.
  37. * @returns {Object}
  38. */
  39. export function styleTypeToObject(st: StyleType): Object {
  40. if (!st) {
  41. return {};
  42. }
  43. if (Array.isArray(st)) {
  44. const flatStyle = {};
  45. for (const styleElement of st) {
  46. Object.assign(flatStyle, styleTypeToObject(styleElement));
  47. }
  48. return flatStyle;
  49. }
  50. return st;
  51. }
  52. /**
  53. * Combines the given 2 styles into a single one.
  54. *
  55. * @param {StyleType} a - An object or array of styles.
  56. * @param {StyleType} b - An object or array of styles.
  57. * @private
  58. * @returns {StyleType} - The merged styles.
  59. */
  60. export function combineStyles(a: StyleType, b: StyleType): StyleType {
  61. const result: Array<StyleSheet> = [];
  62. if (a) {
  63. if (Array.isArray(a)) {
  64. result.push(...a);
  65. } else {
  66. result.push(a);
  67. }
  68. }
  69. if (b) {
  70. if (Array.isArray(b)) {
  71. result.push(...b);
  72. } else {
  73. result.push(b);
  74. }
  75. }
  76. return result;
  77. }
  78. /**
  79. * Create a style sheet using the provided style definitions.
  80. *
  81. * @param {StyleSheet} styles - A dictionary of named style definitions.
  82. * @param {StyleSheet} [overrides={}] - Optional set of additional (often
  83. * platform-dependent/specific) style definitions that will override the base
  84. * (often platform-independent) styles.
  85. * @returns {StyleSheet}
  86. */
  87. export function createStyleSheet(
  88. styles: StyleSheet, overrides: StyleSheet = {}): StyleSheet {
  89. const combinedStyles = {};
  90. for (const k of Object.keys(styles)) {
  91. combinedStyles[k]
  92. = _shimStyles({
  93. ...styles[k],
  94. ...overrides[k]
  95. });
  96. }
  97. return combinedStyles;
  98. }
  99. /**
  100. * Works around a bug in react-native or react-native-webrtc on Android which
  101. * causes Views overlaying RTCView to be clipped. Even though we (may) display
  102. * multiple RTCViews, it is enough to apply the fix only to a View with a
  103. * bounding rectangle containing all RTCviews and their overlaying Views.
  104. *
  105. * @param {StyleSheet} styles - An object which represents a stylesheet.
  106. * @public
  107. * @returns {StyleSheet}
  108. */
  109. export function fixAndroidViewClipping<T: StyleSheet>(styles: T): T {
  110. if (Platform.OS === 'android') {
  111. styles.borderColor = ColorPalette.appBackground;
  112. styles.borderWidth = 1;
  113. }
  114. return styles;
  115. }
  116. /**
  117. * Returns an rgba format of the provided color if it's in hex or rgb format.
  118. *
  119. * NOTE: The function will return the same color if it's not in one of those
  120. * two formats (e.g. 'white').
  121. *
  122. * @param {string} color - The string representation of the color in rgb or hex
  123. * format.
  124. * @param {number} alpha - The alpha value to apply.
  125. * @returns {string}
  126. */
  127. export function getRGBAFormat(color: string, alpha: number): string {
  128. let match = color.match(HEX_LONG_COLOR_FORMAT);
  129. if (match) {
  130. return `#${match[1]}${match[2]}${match[3]}${_getAlphaInHex(alpha)}`;
  131. }
  132. match = color.match(HEX_SHORT_COLOR_FORMAT);
  133. if (match) {
  134. return `#${match[1]}${match[1]}${match[2]}${match[2]}${match[3]}${
  135. match[3]}${_getAlphaInHex(alpha)}`;
  136. }
  137. match = color.match(RGB_COLOR_FORMAT);
  138. if (match) {
  139. return `rgba(${match[1]}, ${match[2]}, ${match[3]}, ${alpha})`;
  140. }
  141. return color;
  142. }
  143. /**
  144. * Decides if a color is light or dark based on the ITU-R BT.709 and W3C
  145. * recommendations.
  146. *
  147. * NOTE: Please see https://www.w3.org/TR/WCAG20/#relativeluminancedef.
  148. *
  149. * @param {string} color - The color in rgb, rgba or hex format.
  150. * @returns {boolean}
  151. */
  152. export function isDarkColor(color: string): boolean {
  153. const rgb = _getRGBObjectFormat(color);
  154. return ((_getColorLuminance(rgb.r) * 0.2126)
  155. + (_getColorLuminance(rgb.g) * 0.7152)
  156. + (_getColorLuminance(rgb.b) * 0.0722)) <= 0.179;
  157. }
  158. /**
  159. * Converts an [0..1] alpha value into HEX.
  160. *
  161. * @param {number} alpha - The alpha value to convert.
  162. * @returns {string}
  163. */
  164. function _getAlphaInHex(alpha: number): string {
  165. return Number(Math.round(255 * alpha)).toString(16)
  166. .padStart(2, '0');
  167. }
  168. /**
  169. * Calculated the color luminance component for an individual color channel.
  170. *
  171. * NOTE: Please see https://www.w3.org/TR/WCAG20/#relativeluminancedef.
  172. *
  173. * @param {number} c - The color which we need the individual luminance
  174. * for.
  175. * @returns {number}
  176. */
  177. function _getColorLuminance(c: number): number {
  178. return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
  179. }
  180. /**
  181. * Parses a color string into an object containing the RGB values as numbers.
  182. *
  183. * NOTE: Object properties are not alpha-sorted for sanity.
  184. *
  185. * @param {string} color - The color to convert.
  186. * @returns {{
  187. * r: number,
  188. * g: number,
  189. * b: number
  190. * }}
  191. */
  192. function _getRGBObjectFormat(color: string): {r: number, g: number, b: number} {
  193. let match = color.match(HEX_LONG_COLOR_FORMAT);
  194. if (match) {
  195. return {
  196. r: parseInt(match[1], 16) / 255.0,
  197. g: parseInt(match[2], 16) / 255.0,
  198. b: parseInt(match[3], 16) / 255.0
  199. };
  200. }
  201. match = color.match(HEX_SHORT_COLOR_FORMAT);
  202. if (match) {
  203. return {
  204. r: parseInt(`${match[1]}${match[1]}`, 16) / 255.0,
  205. g: parseInt(`${match[2]}${match[2]}`, 16) / 255.0,
  206. b: parseInt(`${match[3]}${match[3]}`, 16) / 255.0
  207. };
  208. }
  209. match = color.match(RGB_COLOR_FORMAT) || color.match(RGBA_COLOR_FORMAT);
  210. if (match) {
  211. return {
  212. r: parseInt(match[1], 10) / 255.0,
  213. g: parseInt(match[2], 10) / 255.0,
  214. b: parseInt(match[3], 10) / 255.0
  215. };
  216. }
  217. return {
  218. r: 0,
  219. g: 0,
  220. b: 0
  221. };
  222. }
  223. /**
  224. * Shims style properties to work correctly on native. Allows us to minimize the
  225. * number of style declarations that need to be set or overridden for specific
  226. * platforms.
  227. *
  228. * @param {StyleSheet} styles - An object which represents a stylesheet.
  229. * @private
  230. * @returns {StyleSheet}
  231. */
  232. function _shimStyles<T: StyleSheet>(styles: T): T {
  233. // Certain style properties may not be numbers on Web but must be numbers on
  234. // React Native. For example, height and width may be expressed in percent
  235. // on Web but React Native will not understand them and we will get errors
  236. // (at least during development). Convert such well-known properties to
  237. // numbers if possible; otherwise, remove them to avoid runtime errors.
  238. for (const k of _WELL_KNOWN_NUMBER_PROPERTIES) {
  239. const v = styles[k];
  240. const typeofV = typeof v;
  241. if (typeofV !== 'undefined' && typeofV !== 'number') {
  242. const numberV = Number(v);
  243. if (Number.isNaN(numberV)) {
  244. delete styles[k];
  245. } else {
  246. styles[k] = numberV;
  247. }
  248. }
  249. }
  250. return styles;
  251. }