rn-css 1.5.3 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,10 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.convertValue = exports.parseValue = void 0;
4
4
  const maths_1 = require("./cssToRN/maths");
5
+ const react_native_1 = require("react-native");
5
6
  /** Take a css value like 12em and return [12, 'em'] */
6
7
  function parseValue(value) {
7
8
  // Match a single unit
8
- const unit = value.match(/([+-]?\b\d+(\.\d+)?)([a-z]+\b|%)/i);
9
+ const unit = value.match(/([+-]?\b\d+(\.\d+)?)([a-z]+\b|%)?/i);
9
10
  return [parseFloat(unit[1]), unit[3]];
10
11
  }
11
12
  exports.parseValue = parseValue;
@@ -21,6 +22,8 @@ function convertValue(key, value, units) {
21
22
  // Percentage values need to rely on an other unit as reference
22
23
  const finalUnits = { ...units };
23
24
  if (value.includes('%')) {
25
+ if (react_native_1.Platform.OS === 'web')
26
+ return value;
24
27
  if (['marginTop', 'marginBottom', 'translateY'].includes(key))
25
28
  finalUnits['%'] = units.height / 100;
26
29
  else if (['marginLeft', 'marginRight', 'translateX'].includes(key))
@@ -1,31 +1,28 @@
1
- import React, { MouseEvent } from 'react';
1
+ import { MouseEvent } from 'react';
2
2
  import type { Style, Units, MediaQuery } from './types';
3
3
  import { LayoutChangeEvent } from 'react-native';
4
- export declare const FontSizeContext: React.Context<number>;
5
- export declare const zIndexContext: React.Context<(_zIndex: number) => void>;
6
- /** HOC that will apply the screen size to the styles defined with vmin, vmax, vw, vh units, and handle media queries constraints */
4
+ /** Hook that will apply the screen size to the styles defined with vmin, vmax, vw, vh units, and handle media queries constraints */
7
5
  export declare const useScreenSize: () => {
8
6
  vw: number;
9
7
  vh: number;
10
8
  vmin: number;
11
9
  vmax: number;
12
10
  };
13
- /** HOC that will apply the style reserved for hover state if needed */
14
- export declare const useHover: (rnStyle: Style, onMouseEnter?: ((event: MouseEvent) => void) | undefined, onMouseLeave?: ((event: MouseEvent) => void) | undefined) => {
15
- style: import("./types").PartialStyle | undefined;
11
+ /** Hook that will apply the style reserved for hover state if needed */
12
+ export declare const useHover: (onMouseEnter: ((event: MouseEvent) => void) | undefined, onMouseLeave: ((event: MouseEvent) => void | undefined) | undefined, needsHover: boolean) => {
13
+ hover: boolean;
16
14
  onMouseEnter: ((event: MouseEvent) => void) | undefined;
17
- onMouseLeave: ((event: MouseEvent) => void) | undefined;
15
+ onMouseLeave: ((event: MouseEvent) => void | undefined) | undefined;
18
16
  };
19
- /** HOC that will apply the style provided in the media queries */
17
+ /** Hook that will apply the style provided in the media queries */
20
18
  export declare const useMediaQuery: (media: undefined | MediaQuery[], units: Units) => Style | undefined;
21
- /** HOC that will measure the layout to handle styles that use % units */
22
- export declare const useLayout: (onLayout?: ((event: LayoutChangeEvent) => void) | undefined) => {
19
+ /** Hook that will measure the layout to handle styles that use % units */
20
+ export declare const useLayout: (onLayout: ((event: LayoutChangeEvent) => void) | undefined, needsLayout: boolean) => {
23
21
  width: number;
24
22
  height: number;
25
- onLayout: (event: LayoutChangeEvent) => void;
23
+ onLayout: ((event: LayoutChangeEvent) => void) | undefined;
26
24
  };
27
25
  /** Apply the new fontSize to the component before we can calculate em units */
28
- export declare const useFontSize: (setFontSize?: string | undefined, rem?: number) => {
26
+ export declare const useFontSize: (fontSizeTarget: string | undefined, rem: number, em: number) => {
29
27
  em: number;
30
28
  };
31
- export declare const useZIndex: (zIndexFromStyle: number) => number | "auto" | undefined;
package/dist/features.js CHANGED
@@ -3,94 +3,84 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.useZIndex = exports.useFontSize = exports.useLayout = exports.useMediaQuery = exports.useHover = exports.useScreenSize = exports.zIndexContext = exports.FontSizeContext = void 0;
6
+ exports.useFontSize = exports.useLayout = exports.useMediaQuery = exports.useHover = exports.useScreenSize = void 0;
7
7
  /* eslint-disable react/display-name */
8
8
  const react_1 = __importDefault(require("react"));
9
9
  const react_native_1 = require("react-native");
10
10
  const convertUnits_1 = require("./convertUnits");
11
11
  const mediaQueries_1 = require("./cssToRN/mediaQueries");
12
- exports.FontSizeContext = react_1.default.createContext(16);
13
- // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
14
- exports.zIndexContext = react_1.default.createContext((_zIndex) => { });
15
- /** HOC that will apply the screen size to the styles defined with vmin, vmax, vw, vh units, and handle media queries constraints */
12
+ /** Hook that will apply the screen size to the styles defined with vmin, vmax, vw, vh units, and handle media queries constraints */
16
13
  const useScreenSize = () => {
17
14
  const { width, height } = (0, react_native_1.useWindowDimensions)();
18
- return { vw: width / 100, vh: height / 100, vmin: Math.min(width, height) / 100, vmax: Math.max(width, height) / 100 };
15
+ return react_1.default.useMemo(() => ({
16
+ vw: width / 100, vh: height / 100, vmin: Math.min(width, height) / 100, vmax: Math.max(width, height) / 100
17
+ }), [height, width]);
19
18
  };
20
19
  exports.useScreenSize = useScreenSize;
21
- /** HOC that will apply the style reserved for hover state if needed */
22
- const useHover = (rnStyle, onMouseEnter, onMouseLeave) => {
20
+ /** Hook that will apply the style reserved for hover state if needed */
21
+ const useHover = (onMouseEnter, onMouseLeave, needsHover) => {
23
22
  const [hover, setHover] = react_1.default.useState(false);
24
- const hoverStart = react_1.default.useCallback((event) => {
23
+ const hoverStart = react_1.default.useMemo(() => needsHover ? (event) => {
25
24
  if (onMouseEnter)
26
25
  onMouseEnter(event);
27
26
  setHover(true);
28
- }, [onMouseEnter]);
29
- const hoverStop = react_1.default.useCallback((event) => {
27
+ } : undefined, [needsHover, onMouseEnter]);
28
+ const hoverStop = react_1.default.useMemo(() => needsHover ? (event) => {
30
29
  if (onMouseLeave)
31
30
  onMouseLeave(event);
32
31
  setHover(false);
33
- }, [onMouseLeave]);
34
- return { style: (hover && rnStyle.hover) ? rnStyle.hover : undefined, onMouseEnter: rnStyle.hover ? hoverStart : onMouseEnter, onMouseLeave: rnStyle.hover ? hoverStop : onMouseLeave };
32
+ } : undefined, [needsHover, onMouseLeave]);
33
+ return { hover, onMouseEnter: hoverStart || onMouseEnter, onMouseLeave: hoverStop || onMouseLeave };
35
34
  };
36
35
  exports.useHover = useHover;
37
- /** HOC that will apply the style provided in the media queries */
36
+ /** Hook that will apply the style provided in the media queries */
38
37
  const useMediaQuery = (media, units) => {
39
- if (media) {
40
- const context = (0, mediaQueries_1.createContext)(units);
41
- const mediaStyles = media.map(m => m(context)).filter(m => m);
42
- if (!mediaStyles.length)
43
- return;
44
- const mq = {};
45
- Object.assign(mq, ...mediaStyles);
46
- return mq;
47
- }
38
+ const mediaStyle = react_1.default.useMemo(() => {
39
+ if (media) {
40
+ const context = (0, mediaQueries_1.createContext)(units);
41
+ const mediaStyles = media.map(m => m(context)).filter(m => !!m);
42
+ if (!mediaStyles.length)
43
+ return;
44
+ const mq = {};
45
+ Object.assign(mq, ...mediaStyles);
46
+ return mq;
47
+ }
48
+ }, [media, units]);
49
+ return mediaStyle;
48
50
  };
49
51
  exports.useMediaQuery = useMediaQuery;
50
- /** HOC that will measure the layout to handle styles that use % units */
51
- const useLayout = (onLayout) => {
52
+ /** Hook that will measure the layout to handle styles that use % units */
53
+ const useLayout = (onLayout, needsLayout) => {
52
54
  const [layout, setLayout] = react_1.default.useState({ width: 0, height: 0 });
53
55
  // Prevent calling setState if the component is unmounted
54
56
  const unmounted = react_1.default.useRef(false);
55
57
  react_1.default.useEffect(() => () => { unmounted.current = true; }, []);
56
- const updateLayout = react_1.default.useCallback((event) => {
57
- if (onLayout)
58
- onLayout(event);
58
+ const updateLayout = react_1.default.useMemo(() => needsLayout ? (event) => {
59
59
  if (unmounted.current)
60
60
  return;
61
+ if (onLayout)
62
+ onLayout(event);
61
63
  const { width, height } = event.nativeEvent.layout;
62
64
  setLayout(layout => layout.width === width && layout.height === height ? layout : { width, height });
63
- }, [onLayout]);
64
- return { onLayout: updateLayout, ...layout };
65
+ } : undefined, [needsLayout, onLayout]);
66
+ return { onLayout: updateLayout || onLayout, ...layout };
65
67
  };
66
68
  exports.useLayout = useLayout;
67
69
  /** Apply the new fontSize to the component before we can calculate em units */
68
- const useFontSize = (setFontSize, rem = 16) => {
69
- const em = react_1.default.useContext(exports.FontSizeContext);
70
- if (!setFontSize)
71
- return { em };
72
- const [fontSize, fontUnit] = (0, convertUnits_1.parseValue)(setFontSize);
73
- const isRelative = ['rem', 'em', '%'].includes(fontUnit || '');
74
- if (isRelative) {
75
- const newSize = fontUnit === 'em' ? em * fontSize
76
- : fontUnit === 'rem' ? fontSize * rem
77
- : fontUnit === '%' ? em * (1 + fontSize / 100)
78
- : fontSize;
79
- return { em: newSize };
80
- }
81
- else {
82
- return { em: fontSize };
83
- }
70
+ const useFontSize = (fontSizeTarget, rem, em) => {
71
+ const [fontSize, fontUnit] = react_1.default.useMemo(() => fontSizeTarget === undefined ? [fontSizeTarget] : (0, convertUnits_1.parseValue)(fontSizeTarget), [fontSizeTarget]);
72
+ const isRelative = fontUnit && ['rem', 'em', '%'].includes(fontUnit);
73
+ const newSize = react_1.default.useMemo(() => {
74
+ if (fontSize && isRelative) {
75
+ const newSize = fontUnit === 'em' ? em * fontSize
76
+ : fontUnit === 'rem' ? fontSize * rem
77
+ : fontUnit === '%' ? em * (1 + fontSize / 100)
78
+ : fontSize;
79
+ return newSize;
80
+ }
81
+ else
82
+ return fontSize || em;
83
+ }, [em, fontSize, fontUnit, isRelative, rem]);
84
+ return { em: newSize };
84
85
  };
85
86
  exports.useFontSize = useFontSize;
86
- const useZIndex = (zIndexFromStyle) => {
87
- const [zIndex, setZIndex] = react_1.default.useState();
88
- const updateParentZIndex = react_1.default.useContext(exports.zIndexContext);
89
- react_1.default.useEffect(() => {
90
- setZIndex(zIndexFromStyle);
91
- if (react_native_1.Platform.OS === 'ios' && zIndexFromStyle && zIndexFromStyle !== zIndex)
92
- updateParentZIndex(zIndexFromStyle);
93
- }, [zIndexFromStyle, updateParentZIndex]);
94
- return react_native_1.Platform.OS === 'web' ? (zIndex || 'auto') : zIndex;
95
- };
96
- exports.useZIndex = useZIndex;
package/dist/index.d.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  import React from 'react';
2
2
  import * as RN from 'react-native';
3
3
  export { cssToRNStyle } from './cssToRN';
4
- export { FontSizeContext } from './features';
5
- export { SharedValue } from './styleComponent';
4
+ export { SharedValue, FontSizeContext, RemContext } from './styleComponent';
6
5
  export * from './useTheme';
7
6
  declare const styled: {
8
7
  <T>(Component: React.ComponentType<T>): {
package/dist/index.js CHANGED
@@ -22,15 +22,15 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
22
22
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
23
23
  };
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
- exports.SharedValue = exports.FontSizeContext = exports.cssToRNStyle = void 0;
25
+ exports.RemContext = exports.FontSizeContext = exports.SharedValue = exports.cssToRNStyle = void 0;
26
26
  const RN = __importStar(require("react-native"));
27
27
  const styleComponent_1 = __importStar(require("./styleComponent"));
28
28
  var cssToRN_1 = require("./cssToRN");
29
29
  Object.defineProperty(exports, "cssToRNStyle", { enumerable: true, get: function () { return cssToRN_1.cssToRNStyle; } });
30
- var features_1 = require("./features");
31
- Object.defineProperty(exports, "FontSizeContext", { enumerable: true, get: function () { return features_1.FontSizeContext; } });
32
30
  var styleComponent_2 = require("./styleComponent");
33
31
  Object.defineProperty(exports, "SharedValue", { enumerable: true, get: function () { return styleComponent_2.SharedValue; } });
32
+ Object.defineProperty(exports, "FontSizeContext", { enumerable: true, get: function () { return styleComponent_2.FontSizeContext; } });
33
+ Object.defineProperty(exports, "RemContext", { enumerable: true, get: function () { return styleComponent_2.RemContext; } });
34
34
  __exportStar(require("./useTheme"), exports);
35
35
  const styled = (Component) => (0, styleComponent_1.default)(Component);
36
36
  styled.ActivityIndicator = styled(RN.ActivityIndicator);
@@ -1,5 +1,9 @@
1
1
  import React, { MouseEvent } from 'react';
2
2
  import { FlatListProps, LayoutChangeEvent, SectionListProps, StyleProp, VirtualizedListProps } from 'react-native';
3
+ import type { Units } from './types';
4
+ export declare const defaultUnits: Units;
5
+ export declare const RemContext: React.Context<number>;
6
+ export declare const FontSizeContext: React.Context<number>;
3
7
  export declare const SharedValue: React.Context<unknown>;
4
8
  declare type Primitive = number | string | null | undefined | boolean;
5
9
  declare type Functs<T> = (arg: T & {
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.styledVirtualizedList = exports.styledSectionList = exports.styledFlatList = exports.SharedValue = void 0;
6
+ exports.styledVirtualizedList = exports.styledSectionList = exports.styledFlatList = exports.SharedValue = exports.FontSizeContext = exports.RemContext = exports.defaultUnits = void 0;
7
7
  /* eslint-disable react/prop-types */
8
8
  /* eslint-disable react/display-name */
9
9
  const react_1 = __importDefault(require("react"));
@@ -12,6 +12,9 @@ const convertStyle_1 = __importDefault(require("./convertStyle"));
12
12
  const cssToRN_1 = __importDefault(require("./cssToRN"));
13
13
  const features_1 = require("./features");
14
14
  const generateHash_1 = __importDefault(require("./generateHash"));
15
+ exports.defaultUnits = { em: 16, vw: 1, vh: 1, vmin: 1, vmax: 1, rem: 16, px: 1, pt: 72 / 96, in: 96, pc: 9, cm: 96 / 2.54, mm: 96 / 25.4 };
16
+ exports.RemContext = react_1.default.createContext(exports.defaultUnits.rem);
17
+ exports.FontSizeContext = react_1.default.createContext(exports.defaultUnits.em);
15
18
  // We use this to cache the computed styles
16
19
  const styleMap = {};
17
20
  // We use this to share value within the component (Theme, Translation, whatever)
@@ -25,15 +28,13 @@ function buildCSSString(chunks, functs, props, shared) {
25
28
  const styled = (Component) => {
26
29
  const styledComponent = (chunks, ...functs) => {
27
30
  const ForwardRefComponent = react_1.default.forwardRef((props, ref) => {
28
- const units = react_1.default.useRef({ em: 16, vw: 1, vh: 1, vmin: 1, vmax: 1, rem: 16, px: 1, pt: 72 / 96, in: 96, pc: 9, cm: 96 / 2.54, mm: 96 / 25.4 });
31
+ const rem = react_1.default.useContext(exports.RemContext);
29
32
  const shared = react_1.default.useContext(exports.SharedValue);
30
- // Store the style for mutualization
31
- const cssString = react_1.default.useRef(buildCSSString(chunks, functs, props, shared));
32
- const [rnStyle, setRNStyle] = react_1.default.useState((0, cssToRN_1.default)(cssString.current));
33
+ // Build the css string with the context
34
+ const css = react_1.default.useMemo(() => buildCSSString(chunks, functs, props, shared), [props, shared]);
35
+ // Store the style in RN format
36
+ const [rnStyle, setRNStyle] = react_1.default.useState({});
33
37
  react_1.default.useEffect(() => {
34
- // Build the css string with the context
35
- const css = buildCSSString(chunks, functs, props, shared);
36
- cssString.current = css;
37
38
  // Try to load an existing style from the style map or save it for next time
38
39
  const hash = (0, generateHash_1.default)(css);
39
40
  const style = styleMap[hash];
@@ -53,75 +54,67 @@ const styled = (Component) => {
53
54
  if (style.usages <= 0)
54
55
  delete styleMap[hash];
55
56
  };
56
- }, [props, shared]);
57
- // const [needsFontSize, setNeedsFontSize] = React.useState(false)
58
- // const [needsScreenSize, setNeedsScreenSize] = React.useState(false)
59
- const [needsLayout, setNeedsLayout] = react_1.default.useState(false);
60
- // const [needsHover, setNeedsHover] = React.useState(false)
61
- react_1.default.useEffect(() => {
62
- const css = cssString.current;
63
- // setNeedsFontSize(!!css.match(/\b(\d+)(\.\d+)?em\b/)) // Do we need em units
64
- // setNeedsScreenSize(!!css.match(/\b(\d+)(\.\d+)?v([hw]|min|max)\b/)) // Do we need vx units
65
- setNeedsLayout(!!css.match(/\d%/)); // Do we need % units
66
- // setNeedsHover(!!css.match(/&:hover/)) // Do we need to track the mouse
67
- }, [cssString.current]);
68
- const finalStyle = { ...rnStyle };
69
- delete finalStyle.media;
70
- delete finalStyle.hover;
71
- // Read all the data we might need
57
+ }, [css]);
58
+ const { needsLayout, needsHover } = react_1.default.useMemo(() => ({
59
+ // needsFontSize: !!css.match(/\b(\d+)(\.\d+)?em\b/)
60
+ // needsScreenSize: !!cssString.current.match(/\b(\d+)(\.\d+)?v([hw]|min|max)\b/) || !!rnStyle.media,
61
+ needsLayout: !!css.match(/\d%/),
62
+ needsHover: !!rnStyle.hover
63
+ }), [css, rnStyle.hover]);
72
64
  // Handle hover
73
- const { onMouseEnter, onMouseLeave, style: hoverStyle } = (0, features_1.useHover)(rnStyle, props.onMouseEnter, props.onMouseLeave);
74
- if (hoverStyle)
75
- Object.assign(finalStyle, hoverStyle);
65
+ const { onMouseEnter, onMouseLeave, hover } = (0, features_1.useHover)(props.onMouseEnter, props.onMouseLeave, needsHover);
66
+ const tempStyle = react_1.default.useMemo(() => {
67
+ const style = { ...rnStyle };
68
+ delete style.media;
69
+ delete style.hover;
70
+ if (hover)
71
+ Object.assign(style, rnStyle.hover);
72
+ return style;
73
+ }, [hover, rnStyle]);
76
74
  // Calculate current em unit for media-queries
77
- const { em: tempEm } = (0, features_1.useFontSize)(finalStyle.fontSize, units.current.rem);
78
- if (units.current.em !== tempEm)
79
- units.current = { ...units.current, em: tempEm };
75
+ const parentEm = react_1.default.useContext(exports.FontSizeContext);
76
+ const { em: tempEm } = (0, features_1.useFontSize)(tempStyle.fontSize, rem, parentEm);
80
77
  // Handle layout data needed for % units
81
- const { width, height, onLayout } = (0, features_1.useLayout)(props.onLayout);
82
- if (needsLayout && (units.current.width !== width || units.current.height !== height)) {
83
- units.current = { ...units.current, width, height };
84
- }
78
+ const { width, height, onLayout } = (0, features_1.useLayout)(props.onLayout, needsLayout);
85
79
  // Handle screen size needed for vw and wh units
86
80
  const screenUnits = (0, features_1.useScreenSize)();
87
- if ( /* needsScreenSize && */Object.keys(screenUnits).find(key => units.current[key] !== screenUnits[key])) {
88
- units.current = { ...units.current, ...screenUnits };
89
- }
81
+ /** The units before re-updating the em */
82
+ const baseUnits = react_1.default.useMemo(() => ({
83
+ ...exports.defaultUnits,
84
+ rem,
85
+ em: tempEm,
86
+ width,
87
+ height,
88
+ ...screenUnits
89
+ }), [height, rem, screenUnits, tempEm, width]);
90
90
  // apply media queries
91
- const mediaQuery = (0, features_1.useMediaQuery)(rnStyle.media, units.current);
92
- if (mediaQuery)
93
- Object.assign(finalStyle, mediaQuery);
91
+ const mediaQuery = (0, features_1.useMediaQuery)(rnStyle.media, baseUnits);
94
92
  // Handle em units
95
- const { em } = (0, features_1.useFontSize)(finalStyle.fontSize, units.current.rem);
96
- if (units.current.em !== em)
97
- units.current = { ...units.current, em };
98
- if (finalStyle.fontSize)
99
- finalStyle.fontSize = em + 'px';
100
- // We memoïze the style to keep the same reference if possible and change it only if the style changed
101
- const calculatedStyle = react_1.default.useRef(finalStyle);
102
- if (Object.keys(finalStyle).length !== Object.keys(calculatedStyle.current).length || Object.keys(finalStyle).find(key => calculatedStyle.current[key] !== finalStyle[key])) {
103
- calculatedStyle.current = finalStyle;
104
- }
105
- const styleConvertedFromCSS = react_1.default.useMemo(() => (0, convertStyle_1.default)(calculatedStyle.current, units.current), [calculatedStyle.current, units.current]);
106
- const zIndex = (0, features_1.useZIndex)(react_native_1.StyleSheet.flatten([props.style, styleConvertedFromCSS]).zIndex);
107
- const style = react_1.default.useMemo(() => {
108
- const style = [];
109
- style.push(styleConvertedFromCSS);
110
- if (props.style)
111
- style.push(props.style);
112
- if (zIndex)
113
- style.push({ zIndex });
114
- return style.length > 1 ? style : style[0];
115
- }, [props.style, styleConvertedFromCSS, zIndex]);
116
- const newProps = { style, onMouseEnter, onMouseLeave, onLayout };
117
- // Handle ellipsis
118
- if (react_native_1.StyleSheet.flatten(style).textOverflow === 'ellipsis')
119
- Object.assign(newProps, { numberOfLines: 1 });
93
+ const fontSize = mediaQuery?.fontSize || tempStyle.fontSize;
94
+ const { em } = (0, features_1.useFontSize)(fontSize, baseUnits.rem, parentEm);
95
+ const finalStyle = react_1.default.useMemo(() => {
96
+ const style = { ...tempStyle, ...mediaQuery };
97
+ if (style.fontSize)
98
+ style.fontSize = em + 'px';
99
+ if (style.zIndex === undefined && react_native_1.Platform.OS === 'web')
100
+ style.zIndex = 'auto';
101
+ return style;
102
+ }, [em, mediaQuery, tempStyle]);
103
+ const units = react_1.default.useMemo(() => ({ ...baseUnits, em }), [baseUnits, em]);
104
+ const styleConvertedFromCSS = react_1.default.useMemo(() => (0, convertStyle_1.default)(finalStyle, units), [finalStyle, units]);
105
+ const style = react_1.default.useMemo(() => react_native_1.StyleSheet.flatten([props.style, styleConvertedFromCSS]), [props.style, styleConvertedFromCSS]);
106
+ const newProps = react_1.default.useMemo(() => {
107
+ const newProps = { style, onMouseEnter, onMouseLeave, onLayout };
108
+ if (style.textOverflow === 'ellipsis')
109
+ Object.assign(newProps, { numberOfLines: 1 });
110
+ return newProps;
111
+ }, [onLayout, onMouseEnter, onMouseLeave, style]);
112
+ console.log(style, styleConvertedFromCSS, tempStyle, rnStyle, css);
120
113
  // The lines below can improve perfs, but it causes the component to remount when the font size changes
121
114
  // const currentFontSize = React.useContext(FontSizeContext)
122
115
  // if (em !== currentFontSize) {
123
116
  if (finalStyle.fontSize) {
124
- return react_1.default.createElement(features_1.FontSizeContext.Provider, { value: em },
117
+ return react_1.default.createElement(exports.FontSizeContext.Provider, { value: em },
125
118
  react_1.default.createElement(Component, { ref: ref, ...props, ...newProps }));
126
119
  }
127
120
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rn-css",
3
- "version": "1.5.3",
3
+ "version": "1.6.0",
4
4
  "scripts": {
5
5
  "test": "jest",
6
6
  "prepare": "tsc",
@@ -35,18 +35,19 @@
35
35
  "eslint-plugin-node": "^11.1.0",
36
36
  "eslint-plugin-promise": "^5.2.0",
37
37
  "eslint-plugin-react": "^7.27.1",
38
+ "eslint-plugin-react-hooks": "^4.4.0",
38
39
  "eslint-plugin-standard": "^5.0.0",
39
40
  "html-loader": "^3.0.1",
40
41
  "html-webpack-plugin": "^5.5.0",
41
42
  "husky": "^7.0.4",
42
- "jest": "^27.4.5",
43
+ "jest": "^27.5.1",
43
44
  "lint-staged": "^12.1.2",
44
- "metro-react-native-babel-preset": "^0.66.2",
45
+ "metro-react-native-babel-preset": "^0.70.0",
45
46
  "react": "^17.0.2",
46
47
  "react-dom": "^17.0.2",
47
- "react-native": "^0.66.4",
48
+ "react-native": "^0.68.0",
48
49
  "react-native-typescript-transformer": "^1.2.13",
49
- "react-native-web": "^0.17.5",
50
+ "react-native-web": "^0.17.7",
50
51
  "react-test-renderer": "17.0.2",
51
52
  "release-it": "^14.11.8",
52
53
  "ts-jest": "^27.1.1",
@@ -1,10 +1,11 @@
1
1
  import type { PartialStyle, Transform, Units } from './types'
2
2
  import { calculate, min, max } from './cssToRN/maths'
3
+ import { Platform } from 'react-native'
3
4
 
4
5
  /** Take a css value like 12em and return [12, 'em'] */
5
6
  export function parseValue (value: string): [number, string | undefined] {
6
7
  // Match a single unit
7
- const unit = value.match(/([+-]?\b\d+(\.\d+)?)([a-z]+\b|%)/i)
8
+ const unit = value.match(/([+-]?\b\d+(\.\d+)?)([a-z]+\b|%)?/i)
8
9
  return [parseFloat(unit![1]), unit![3] as (string | undefined)]
9
10
  }
10
11
 
@@ -20,6 +21,7 @@ export function convertValue (key: keyof PartialStyle | keyof Transform, value:
20
21
  // Percentage values need to rely on an other unit as reference
21
22
  const finalUnits = { ...units }
22
23
  if (value.includes('%')) {
24
+ if (Platform.OS === 'web') return value
23
25
  if (['marginTop', 'marginBottom', 'translateY'].includes(key)) finalUnits['%'] = units.height! / 100
24
26
  else if (['marginLeft', 'marginRight', 'translateX'].includes(key)) finalUnits['%'] = units.width! / 100
25
27
  else if (key.startsWith('border') && key.endsWith('Radius')) finalUnits['%'] = (units.width! + units.height!) / 200
package/src/features.tsx CHANGED
@@ -1,85 +1,75 @@
1
1
  /* eslint-disable react/display-name */
2
2
  import React, { MouseEvent } from 'react'
3
- import type { Style, Units, MediaQuery } from './types'
4
- import { useWindowDimensions, LayoutChangeEvent, Platform } from 'react-native'
3
+ import type { Style, Units, MediaQuery, PartialStyle } from './types'
4
+ import { useWindowDimensions, LayoutChangeEvent } from 'react-native'
5
5
  import { parseValue } from './convertUnits'
6
6
  import { createContext } from './cssToRN/mediaQueries'
7
7
 
8
- export const FontSizeContext = React.createContext(16)
9
- // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
10
- export const zIndexContext = React.createContext((_zIndex: number) => {})
11
-
12
- /** HOC that will apply the screen size to the styles defined with vmin, vmax, vw, vh units, and handle media queries constraints */
8
+ /** Hook that will apply the screen size to the styles defined with vmin, vmax, vw, vh units, and handle media queries constraints */
13
9
  export const useScreenSize = () => {
14
10
  const { width, height } = useWindowDimensions()
15
- return { vw: width / 100, vh: height / 100, vmin: Math.min(width, height) / 100, vmax: Math.max(width, height) / 100 }
11
+ return React.useMemo(() => ({
12
+ vw: width / 100, vh: height / 100, vmin: Math.min(width, height) / 100, vmax: Math.max(width, height) / 100
13
+ }), [height, width])
16
14
  }
17
15
 
18
- /** HOC that will apply the style reserved for hover state if needed */
19
- export const useHover = (rnStyle: Style, onMouseEnter?: (event: MouseEvent) => void, onMouseLeave?: (event: MouseEvent) => void) => {
16
+ /** Hook that will apply the style reserved for hover state if needed */
17
+ export const useHover = (onMouseEnter: undefined | ((event: MouseEvent) => void), onMouseLeave: undefined | ((event: MouseEvent) => void | undefined), needsHover: boolean) => {
20
18
  const [hover, setHover] = React.useState(false)
21
- const hoverStart = React.useCallback((event: MouseEvent) => {
19
+ const hoverStart = React.useMemo(() => needsHover ? (event: MouseEvent) => {
22
20
  if (onMouseEnter) onMouseEnter(event)
23
21
  setHover(true)
24
- }, [onMouseEnter])
25
- const hoverStop = React.useCallback((event: MouseEvent) => {
22
+ } : undefined, [needsHover, onMouseEnter])
23
+ const hoverStop = React.useMemo(() => needsHover ? (event: MouseEvent) => {
26
24
  if (onMouseLeave) onMouseLeave(event)
27
25
  setHover(false)
28
- }, [onMouseLeave])
29
- return { style: (hover && rnStyle.hover) ? rnStyle.hover : undefined, onMouseEnter: rnStyle.hover ? hoverStart : onMouseEnter, onMouseLeave: rnStyle.hover ? hoverStop : onMouseLeave }
26
+ } : undefined, [needsHover, onMouseLeave])
27
+ return { hover, onMouseEnter: hoverStart || onMouseEnter, onMouseLeave: hoverStop || onMouseLeave }
30
28
  }
31
29
 
32
- /** HOC that will apply the style provided in the media queries */
30
+ /** Hook that will apply the style provided in the media queries */
33
31
  export const useMediaQuery = (media: undefined | MediaQuery[], units: Units): Style | undefined => {
34
- if (media) {
35
- const context = createContext(units)
36
- const mediaStyles = media.map(m => m(context)).filter(m => m)
37
- if (!mediaStyles.length) return
38
- const mq = {} as Style
39
- Object.assign(mq, ...mediaStyles)
40
- return mq
41
- }
32
+ const mediaStyle = React.useMemo(() => {
33
+ if (media) {
34
+ const context = createContext(units)
35
+ const mediaStyles = media.map(m => m(context)).filter(m => !!m) as PartialStyle[]
36
+ if (!mediaStyles.length) return
37
+ const mq = {} as Style
38
+ Object.assign(mq, ...mediaStyles)
39
+ return mq
40
+ }
41
+ }, [media, units])
42
+ return mediaStyle
42
43
  }
43
44
 
44
- /** HOC that will measure the layout to handle styles that use % units */
45
- export const useLayout = (onLayout?: (event: LayoutChangeEvent) => void) => {
45
+ /** Hook that will measure the layout to handle styles that use % units */
46
+ export const useLayout = (onLayout: undefined | ((event: LayoutChangeEvent) => void), needsLayout: boolean) => {
46
47
  const [layout, setLayout] = React.useState({ width: 0, height: 0 })
47
48
  // Prevent calling setState if the component is unmounted
48
49
  const unmounted = React.useRef(false)
49
50
  React.useEffect(() => () => { unmounted.current = true }, [])
50
- const updateLayout = React.useCallback((event: LayoutChangeEvent) => {
51
- if (onLayout) onLayout(event)
51
+ const updateLayout = React.useMemo(() => needsLayout ? (event: LayoutChangeEvent) => {
52
52
  if (unmounted.current) return
53
+ if (onLayout) onLayout(event)
53
54
  const { width, height } = event.nativeEvent.layout
54
55
  setLayout(layout => layout.width === width && layout.height === height ? layout : { width, height })
55
- }, [onLayout])
56
- return { onLayout: updateLayout, ...layout }
56
+ } : undefined, [needsLayout, onLayout])
57
+ return { onLayout: updateLayout || onLayout, ...layout }
57
58
  }
58
59
 
59
60
  /** Apply the new fontSize to the component before we can calculate em units */
60
- export const useFontSize = (setFontSize?: string, rem = 16): { em: number } => {
61
- const em = React.useContext(FontSizeContext)
62
- if (!setFontSize) return { em }
63
- const [fontSize, fontUnit] = parseValue(setFontSize)
64
- const isRelative = ['rem', 'em', '%'].includes(fontUnit || '')
65
- if (isRelative) {
66
- const newSize = fontUnit === 'em' ? em * fontSize
67
- : fontUnit === 'rem' ? fontSize * rem
68
- : fontUnit === '%' ? em * (1 + fontSize / 100)
69
- : fontSize
70
- return { em: newSize }
71
- }
72
- else {
73
- return { em: fontSize }
74
- }
75
- }
76
-
77
- export const useZIndex = (zIndexFromStyle: number) => {
78
- const [zIndex, setZIndex] = React.useState<number>()
79
- const updateParentZIndex = React.useContext(zIndexContext)
80
- React.useEffect(() => {
81
- setZIndex(zIndexFromStyle)
82
- if (Platform.OS === 'ios' && zIndexFromStyle && zIndexFromStyle !== zIndex) updateParentZIndex(zIndexFromStyle)
83
- }, [zIndexFromStyle, updateParentZIndex])
84
- return Platform.OS === 'web' ? (zIndex || 'auto') : zIndex
61
+ export const useFontSize = (fontSizeTarget: string | undefined, rem: number, em: number): { em: number } => {
62
+ const [fontSize, fontUnit] = React.useMemo(() => fontSizeTarget === undefined ? [fontSizeTarget] : parseValue(fontSizeTarget), [fontSizeTarget])
63
+ const isRelative = fontUnit && ['rem', 'em', '%'].includes(fontUnit)
64
+ const newSize = React.useMemo(() => {
65
+ if (fontSize && isRelative) {
66
+ const newSize = fontUnit === 'em' ? em * fontSize
67
+ : fontUnit === 'rem' ? fontSize * rem
68
+ : fontUnit === '%' ? em * (1 + fontSize / 100)
69
+ : fontSize
70
+ return newSize
71
+ }
72
+ else return fontSize || em
73
+ }, [em, fontSize, fontUnit, isRelative, rem])
74
+ return { em: newSize }
85
75
  }
package/src/index.tsx CHANGED
@@ -2,8 +2,7 @@ import React from 'react'
2
2
  import * as RN from 'react-native'
3
3
  import styledComponent, { styledFlatList, styledSectionList, styledVirtualizedList } from './styleComponent'
4
4
  export { cssToRNStyle } from './cssToRN'
5
- export { FontSizeContext } from './features'
6
- export { SharedValue } from './styleComponent'
5
+ export { SharedValue, FontSizeContext, RemContext } from './styleComponent'
7
6
  export * from './useTheme'
8
7
 
9
8
  const styled = <T, >(Component: React.ComponentType<T>) => styledComponent<T>(Component)
@@ -1,13 +1,17 @@
1
1
  /* eslint-disable react/prop-types */
2
2
  /* eslint-disable react/display-name */
3
3
  import React, { MouseEvent } from 'react'
4
- import { FlatList, FlatListProps, LayoutChangeEvent, RecursiveArray, SectionList, SectionListProps, StyleProp, StyleSheet, VirtualizedList, VirtualizedListProps } from 'react-native'
4
+ import { FlatList, FlatListProps, LayoutChangeEvent, Platform, SectionList, SectionListProps, StyleProp, StyleSheet, VirtualizedList, VirtualizedListProps } from 'react-native'
5
5
  import convertStyle from './convertStyle'
6
6
  import cssToStyle from './cssToRN'
7
- import { FontSizeContext, useFontSize, useHover, useLayout, useScreenSize, useZIndex, useMediaQuery } from './features'
7
+ import { useFontSize, useHover, useLayout, useScreenSize, useMediaQuery } from './features'
8
8
  import calculHash from './generateHash'
9
9
  import type { Style, StyleMap, Units } from './types'
10
10
 
11
+ export const defaultUnits: Units = { em: 16, vw: 1, vh: 1, vmin: 1, vmax: 1, rem: 16, px: 1, pt: 72 / 96, in: 96, pc: 9, cm: 96 / 2.54, mm: 96 / 25.4 }
12
+ export const RemContext = React.createContext<number>(defaultUnits.rem)
13
+ export const FontSizeContext = React.createContext(defaultUnits.em)
14
+
11
15
  // We use this to cache the computed styles
12
16
  const styleMap: StyleMap = {}
13
17
 
@@ -32,15 +36,13 @@ function buildCSSString<T extends { rnCSS?: string }> (chunks: TemplateStringsAr
32
36
  const styled = <Props, >(Component: React.ComponentType<Props>) => {
33
37
  const styledComponent = <S, >(chunks: TemplateStringsArray, ...functs: (Primitive | Functs<S & Props>)[]) => {
34
38
  const ForwardRefComponent = React.forwardRef<React.ComponentType<S & Props & OptionalProps>, S & Props & OptionalProps>((props: S & Props & OptionalProps, ref) => {
35
- const units = React.useRef<Units>({ em: 16, vw: 1, vh: 1, vmin: 1, vmax: 1, rem: 16, px: 1, pt: 72 / 96, in: 96, pc: 9, cm: 96 / 2.54, mm: 96 / 25.4 })
39
+ const rem = React.useContext(RemContext)
36
40
  const shared = React.useContext(SharedValue)
37
- // Store the style for mutualization
38
- const cssString = React.useRef(buildCSSString(chunks, functs, props, shared))
39
- const [rnStyle, setRNStyle] = React.useState<Style>(cssToStyle(cssString.current))
40
- React.useEffect(() => {
41
41
  // Build the css string with the context
42
- const css = buildCSSString(chunks, functs, props, shared)
43
- cssString.current = css
42
+ const css = React.useMemo(() => buildCSSString(chunks, functs, props, shared), [props, shared])
43
+ // Store the style in RN format
44
+ const [rnStyle, setRNStyle] = React.useState<Style>({})
45
+ React.useEffect(() => {
44
46
  // Try to load an existing style from the style map or save it for next time
45
47
  const hash = calculHash(css)
46
48
  const style = styleMap[hash]
@@ -59,72 +61,67 @@ const styled = <Props, >(Component: React.ComponentType<Props>) => {
59
61
  style.usages--
60
62
  if (style.usages <= 0) delete styleMap[hash]
61
63
  }
62
- }, [props, shared])
64
+ }, [css])
63
65
 
64
- // const [needsFontSize, setNeedsFontSize] = React.useState(false)
65
- // const [needsScreenSize, setNeedsScreenSize] = React.useState(false)
66
- const [needsLayout, setNeedsLayout] = React.useState(false)
67
- // const [needsHover, setNeedsHover] = React.useState(false)
68
- React.useEffect(() => {
69
- const css = cssString.current
70
- // setNeedsFontSize(!!css.match(/\b(\d+)(\.\d+)?em\b/)) // Do we need em units
71
- // setNeedsScreenSize(!!css.match(/\b(\d+)(\.\d+)?v([hw]|min|max)\b/)) // Do we need vx units
72
- setNeedsLayout(!!css.match(/\d%/)) // Do we need % units
73
- // setNeedsHover(!!css.match(/&:hover/)) // Do we need to track the mouse
74
- }, [cssString.current])
75
-
76
- const finalStyle = { ...rnStyle }
77
- delete finalStyle.media
78
- delete finalStyle.hover
79
- // Read all the data we might need
66
+ const { needsLayout, needsHover } = React.useMemo(() => ({
67
+ // needsFontSize: !!css.match(/\b(\d+)(\.\d+)?em\b/)
68
+ // needsScreenSize: !!cssString.current.match(/\b(\d+)(\.\d+)?v([hw]|min|max)\b/) || !!rnStyle.media,
69
+ needsLayout: !!css.match(/\d%/),
70
+ needsHover: !!rnStyle.hover
71
+ }), [css, rnStyle.hover])
80
72
 
81
73
  // Handle hover
82
- const { onMouseEnter, onMouseLeave, style: hoverStyle } = useHover(rnStyle, props.onMouseEnter, props.onMouseLeave)
83
- if (hoverStyle) Object.assign(finalStyle, hoverStyle)
74
+ const { onMouseEnter, onMouseLeave, hover } = useHover(props.onMouseEnter, props.onMouseLeave, needsHover)
75
+ const tempStyle = React.useMemo<Style>(() => {
76
+ const style = { ...rnStyle }
77
+ delete style.media
78
+ delete style.hover
79
+ if (hover) Object.assign(style, rnStyle.hover)
80
+ return style
81
+ }, [hover, rnStyle])
84
82
 
85
83
  // Calculate current em unit for media-queries
86
- const { em: tempEm } = useFontSize(finalStyle.fontSize, units.current.rem)
87
- if (units.current.em !== tempEm) units.current = { ...units.current, em: tempEm }
88
-
84
+ const parentEm = React.useContext(FontSizeContext)
85
+ const { em: tempEm } = useFontSize(tempStyle.fontSize, rem, parentEm)
89
86
  // Handle layout data needed for % units
90
- const { width, height, onLayout } = useLayout(props.onLayout)
91
- if (needsLayout && (units.current.width !== width || units.current.height !== height)) {
92
- units.current = { ...units.current, width, height }
93
- }
94
-
87
+ const { width, height, onLayout } = useLayout(props.onLayout, needsLayout)
95
88
  // Handle screen size needed for vw and wh units
96
89
  const screenUnits = useScreenSize()
97
- if (/* needsScreenSize && */(Object.keys(screenUnits) as (keyof typeof screenUnits)[]).find(key => units.current[key] !== screenUnits[key])) {
98
- units.current = { ...units.current, ...screenUnits }
99
- }
100
90
 
101
- // apply media queries
102
- const mediaQuery = useMediaQuery(rnStyle.media, units.current)
103
- if (mediaQuery) Object.assign(finalStyle, mediaQuery)
91
+ /** The units before re-updating the em */
92
+ const baseUnits = React.useMemo<Units>(() => ({
93
+ ...defaultUnits,
94
+ rem,
95
+ em: tempEm,
96
+ width,
97
+ height,
98
+ ...screenUnits
99
+ }), [height, rem, screenUnits, tempEm, width])
104
100
 
101
+ // apply media queries
102
+ const mediaQuery = useMediaQuery(rnStyle.media, baseUnits)
105
103
  // Handle em units
106
- const { em } = useFontSize(finalStyle.fontSize, units.current.rem)
107
- if (units.current.em !== em) units.current = { ...units.current, em }
108
- if (finalStyle.fontSize) finalStyle.fontSize = em + 'px'
109
-
110
- // We memoïze the style to keep the same reference if possible and change it only if the style changed
111
- const calculatedStyle = React.useRef(finalStyle)
112
- if (Object.keys(finalStyle).length !== Object.keys(calculatedStyle.current).length || (Object.keys(finalStyle) as (keyof typeof finalStyle)[]).find(key => calculatedStyle.current[key] !== finalStyle[key])) {
113
- calculatedStyle.current = finalStyle
114
- }
115
- const styleConvertedFromCSS = React.useMemo(() => convertStyle(calculatedStyle.current, units.current), [calculatedStyle.current, units.current])
116
- const zIndex = useZIndex(StyleSheet.flatten([props.style, styleConvertedFromCSS]).zIndex)
117
- const style: StyleProp<any> = React.useMemo(() => {
118
- const style = [] as RecursiveArray<any>
119
- style.push(styleConvertedFromCSS)
120
- if (props.style) style.push(props.style)
121
- if (zIndex) style.push({ zIndex })
122
- return style.length > 1 ? style : style[0]
123
- }, [props.style, styleConvertedFromCSS, zIndex])
124
- const newProps = { style, onMouseEnter, onMouseLeave, onLayout }
125
-
126
- // Handle ellipsis
127
- if (StyleSheet.flatten(style).textOverflow === 'ellipsis') Object.assign(newProps, { numberOfLines: 1 })
104
+ const fontSize = mediaQuery?.fontSize || tempStyle.fontSize
105
+ const { em } = useFontSize(fontSize, baseUnits.rem, parentEm)
106
+
107
+ const finalStyle = React.useMemo<Style>(() => {
108
+ const style: Style = { ...tempStyle, ...mediaQuery }
109
+ if (style.fontSize) style.fontSize = em + 'px'
110
+ if (style.zIndex === undefined && Platform.OS === 'web') style.zIndex = 'auto'
111
+ return style
112
+ }, [em, mediaQuery, tempStyle])
113
+
114
+ const units = React.useMemo<Units>(() => ({ ...baseUnits, em }), [baseUnits, em])
115
+
116
+ const styleConvertedFromCSS = React.useMemo(() => convertStyle(finalStyle, units), [finalStyle, units])
117
+ const style: StyleProp<any> = React.useMemo(() => StyleSheet.flatten([props.style, styleConvertedFromCSS]), [props.style, styleConvertedFromCSS])
118
+ const newProps = React.useMemo(() => {
119
+ const newProps: OptionalProps = { style, onMouseEnter, onMouseLeave, onLayout }
120
+ if (style.textOverflow === 'ellipsis') Object.assign(newProps, { numberOfLines: 1 })
121
+ return newProps
122
+ }, [onLayout, onMouseEnter, onMouseLeave, style])
123
+
124
+ console.log(style, styleConvertedFromCSS, tempStyle, rnStyle, css)
128
125
 
129
126
  // The lines below can improve perfs, but it causes the component to remount when the font size changes
130
127
  // const currentFontSize = React.useContext(FontSizeContext)