@codeandmoney/soelma 0.0.0-dev.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.
- package/.eslintignore +1 -0
- package/.eslintrc.js +31 -0
- package/.github/workflows/nodejs.yml +33 -0
- package/.size-limit.json +6 -0
- package/CHANGELOG.md +90 -0
- package/LICENSE +21 -0
- package/README.md +169 -0
- package/babel.config.js +3 -0
- package/docs/api.md +103 -0
- package/docs/appearance.md +54 -0
- package/docs/dark-mode.md +46 -0
- package/docs/dimensions.md +29 -0
- package/docs/i18n.md +67 -0
- package/docs/logo.png +0 -0
- package/docs/media-query.md +274 -0
- package/docs/orientation.md +44 -0
- package/docs/safe-area.md +62 -0
- package/docs/testting.md +51 -0
- package/docs/ts.md +127 -0
- package/example/AppStyleX/.watchmanconfig +1 -0
- package/example/AppStyleX/android/build.gradle +26 -0
- package/example/AppStyleX/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/example/AppStyleX/android/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/example/AppStyleX/android/gradle.properties +53 -0
- package/example/AppStyleX/android/gradlew +249 -0
- package/example/AppStyleX/android/gradlew.bat +92 -0
- package/example/AppStyleX/android/settings.gradle +12 -0
- package/example/AppStyleX/app.json +28 -0
- package/example/AppStyleX/babel.config.js +3 -0
- package/example/AppStyleX/index.js +9 -0
- package/example/AppStyleX/ios/Podfile +7 -0
- package/example/AppStyleX/ios/Podfile.lock +1252 -0
- package/example/AppStyleX/metro.config.js +54 -0
- package/example/AppStyleX/package.json +43 -0
- package/example/AppStyleX/react-native.config.js +23 -0
- package/example/AppStyleX/src/App.tsx +25 -0
- package/example/AppStyleX/src/BottomNav/index.tsx +32 -0
- package/example/AppStyleX/src/BottomNav/styles.ts +42 -0
- package/example/AppStyleX/src/Circle/index.tsx +53 -0
- package/example/AppStyleX/src/Circle/styles.ts +22 -0
- package/example/AppStyleX/src/Root/index.tsx +41 -0
- package/example/AppStyleX/src/Root/styles.ts +18 -0
- package/example/AppStyleX/src/ToggleButton/index.tsx +66 -0
- package/example/AppStyleX/src/ToggleButton/styles.ts +69 -0
- package/example/AppStyleX/src/style-system/hooks/useAnimatedBgColor.ts +5 -0
- package/example/AppStyleX/src/style-system/hooks/useAnimatedTextColor.ts +5 -0
- package/example/AppStyleX/src/style-system/hooks/useIsDark.ts +8 -0
- package/example/AppStyleX/src/style-system/palette.ts +11 -0
- package/example/AppStyleX/src/style-system/theme.ts +14 -0
- package/example/AppStyleX/src/style-system/utils.ts +11 -0
- package/example/AppStyleX/src/stylex.d.ts +6 -0
- package/example/AppStyleX/tsconfig.json +3 -0
- package/example/AppStyleX/yarn.lock +6767 -0
- package/jest.config.js +19 -0
- package/package.json +59 -0
- package/src/DefaultTheme.ts +4 -0
- package/src/__tests__/createBreakpoints.test.ts +152 -0
- package/src/__tests__/createBreakpointsMatcher.test.ts +188 -0
- package/src/__tests__/createBreakpointsMatcher.types-test.ts +81 -0
- package/src/__tests__/createEventEmitter.test.ts +37 -0
- package/src/__tests__/dark-mode.test.ts +56 -0
- package/src/__tests__/dependencyRegistry.test.ts +16 -0
- package/src/__tests__/dependencyUsage.test.ts +13 -0
- package/src/__tests__/dimensions.test.ts +36 -0
- package/src/__tests__/makeUseStyles.types-test.ts +69 -0
- package/src/__tests__/media-query.test.ts +204 -0
- package/src/__tests__/orientation.test.ts +61 -0
- package/src/__tests__/useTheme.test.ts +26 -0
- package/src/__tests__/withStyles.types-test.tsx +173 -0
- package/src/appearance/consts.ts +1 -0
- package/src/appearance/index.ts +37 -0
- package/src/appearance/init.ts +12 -0
- package/src/context.ts +9 -0
- package/src/createEventEmitter.ts +26 -0
- package/src/dark-mode/consts.ts +1 -0
- package/src/dark-mode/index.ts +29 -0
- package/src/dark-mode/init.ts +19 -0
- package/src/dark-mode/state.ts +5 -0
- package/src/dependencyRegistry.ts +21 -0
- package/src/dependencyUsage.ts +31 -0
- package/src/dimensions/consts.ts +2 -0
- package/src/dimensions/index.ts +20 -0
- package/src/dimensions/init.ts +37 -0
- package/src/dimensions/utils.ts +11 -0
- package/src/i18n.ts +18 -0
- package/src/index.ts +7 -0
- package/src/makeUseStyles/createUseStylesTheme.js +42 -0
- package/src/makeUseStyles/createUseStylesTheme.test.js +137 -0
- package/src/makeUseStyles/createUseStylesWithoutTheme.js +38 -0
- package/src/makeUseStyles/createUseStylesWithoutTheme.test.js +63 -0
- package/src/makeUseStyles/index.d.ts +7 -0
- package/src/makeUseStyles/index.js +12 -0
- package/src/makeUseStyles/index.test.js +28 -0
- package/src/makeUseStyles/test-type.js +28 -0
- package/src/makeUseStyles/utils.js +67 -0
- package/src/media-query/base.ts +43 -0
- package/src/media-query/breakpoints.ts +121 -0
- package/src/media-query/index.ts +12 -0
- package/src/orientation.ts +17 -0
- package/src/safe-area/SafeAreaProvider.tsx +17 -0
- package/src/safe-area/StylexSaveAreaConsumer.ts +23 -0
- package/src/safe-area/consts.ts +1 -0
- package/src/safe-area/eventEmitter.ts +4 -0
- package/src/safe-area/index.tsx +16 -0
- package/src/safe-area/init.tsx +6 -0
- package/src/safe-area/state.ts +12 -0
- package/src/safe-area/types.ts +10 -0
- package/src/useColorTransition.ts +50 -0
- package/src/useTheme.ts +16 -0
- package/src/withStyles.tsx +35 -0
- package/tsconfig.json +30 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
type UnSubscribeFn = () => void;
|
|
2
|
+
type SubscribeFn = (handler: () => void) => UnSubscribeFn;
|
|
3
|
+
|
|
4
|
+
interface Registry {
|
|
5
|
+
[dependencyName: string]: SubscribeFn;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const registry: Registry = {};
|
|
9
|
+
|
|
10
|
+
export function addDependency(name: string, onChange: SubscribeFn): void {
|
|
11
|
+
if (!registry[name]) {
|
|
12
|
+
Object.defineProperty(registry, name, {
|
|
13
|
+
value: onChange,
|
|
14
|
+
writable: false,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function getDependency(name: string): SubscribeFn | undefined {
|
|
20
|
+
return registry[name];
|
|
21
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
interface UsingSpec {
|
|
2
|
+
[dependencyName: string]: boolean;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
interface Ref {
|
|
6
|
+
current: UsingSpec;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const ref: Ref = { current: {} };
|
|
10
|
+
|
|
11
|
+
export function resetUsing(): void {
|
|
12
|
+
ref.current = {};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function getUsing(): UsingSpec {
|
|
16
|
+
return { ...ref.current };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function onUse(name: string): void {
|
|
20
|
+
ref.current[name] = true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface Button {
|
|
24
|
+
variant: "primary" | "danger"
|
|
25
|
+
onAction: () => void
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// onAction -> onClick
|
|
29
|
+
// onAction -> onPress
|
|
30
|
+
|
|
31
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import "./init";
|
|
2
|
+
|
|
3
|
+
import { Dimensions, ScaledSize } from "react-native";
|
|
4
|
+
|
|
5
|
+
import { onUse } from "../dependencyUsage";
|
|
6
|
+
import { WINDOW_DEPENDENCY_KEY, SCREEN_DEPENDENCY_KEY } from "./consts";
|
|
7
|
+
|
|
8
|
+
const get = (dim: "window" | "screen") => Dimensions.get(dim);
|
|
9
|
+
|
|
10
|
+
export function getWindowDimensions(): ScaledSize {
|
|
11
|
+
onUse(WINDOW_DEPENDENCY_KEY);
|
|
12
|
+
|
|
13
|
+
return get("window");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function getScreenDimensions(): ScaledSize {
|
|
17
|
+
onUse(SCREEN_DEPENDENCY_KEY);
|
|
18
|
+
|
|
19
|
+
return get("screen");
|
|
20
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Dimensions, ScaledSize } from "react-native";
|
|
2
|
+
|
|
3
|
+
import { createEventEmitter } from "../createEventEmitter";
|
|
4
|
+
import { addDependency } from "../dependencyRegistry";
|
|
5
|
+
import { SCREEN_DEPENDENCY_KEY, WINDOW_DEPENDENCY_KEY } from "./consts";
|
|
6
|
+
|
|
7
|
+
const { get, addEventListener } = Dimensions;
|
|
8
|
+
|
|
9
|
+
const state = { window: get("window"), screen: get("screen") };
|
|
10
|
+
|
|
11
|
+
const windowEventEmitter = createEventEmitter(WINDOW_DEPENDENCY_KEY);
|
|
12
|
+
const screenEventEmitter = createEventEmitter(SCREEN_DEPENDENCY_KEY);
|
|
13
|
+
|
|
14
|
+
const isNotEqual = (a: ScaledSize, b: ScaledSize) =>
|
|
15
|
+
a.width !== b.width || a.height !== b.height;
|
|
16
|
+
|
|
17
|
+
addDependency(WINDOW_DEPENDENCY_KEY, (handler: () => void) =>
|
|
18
|
+
windowEventEmitter.on(handler)
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
addDependency(SCREEN_DEPENDENCY_KEY, (handler: () => void) =>
|
|
22
|
+
screenEventEmitter.on(handler)
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
addEventListener("change", ({ window, screen }) => {
|
|
26
|
+
if (isNotEqual(screen, state.screen)) {
|
|
27
|
+
state.screen = screen;
|
|
28
|
+
|
|
29
|
+
screenEventEmitter.emit();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (isNotEqual(window, state.window)) {
|
|
33
|
+
state.window = window;
|
|
34
|
+
|
|
35
|
+
windowEventEmitter.emit();
|
|
36
|
+
}
|
|
37
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { SCREEN_DEPENDENCY_KEY, WINDOW_DEPENDENCY_KEY } from "./consts";
|
|
2
|
+
|
|
3
|
+
export const optimizeDependencies = (
|
|
4
|
+
keys: Record<string, boolean>
|
|
5
|
+
): Record<string, boolean> => {
|
|
6
|
+
if (keys[WINDOW_DEPENDENCY_KEY] && keys[SCREEN_DEPENDENCY_KEY]) {
|
|
7
|
+
delete keys[SCREEN_DEPENDENCY_KEY];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return keys;
|
|
11
|
+
};
|
package/src/i18n.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { I18nManager } from "react-native";
|
|
2
|
+
|
|
3
|
+
type LayoutDirectionType = "rtl" | "ltr";
|
|
4
|
+
|
|
5
|
+
export function i18n<T>({
|
|
6
|
+
rtl,
|
|
7
|
+
ltr,
|
|
8
|
+
}: { [direction in LayoutDirectionType]?: T }): T | undefined {
|
|
9
|
+
if (I18nManager.isRTL) {
|
|
10
|
+
return rtl;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return ltr;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const rtl = <T>(styles: T): T | undefined => i18n<T>({ rtl: styles });
|
|
17
|
+
|
|
18
|
+
export const ltr = <T>(styles: T): T | undefined => i18n<T>({ ltr: styles });
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { useTheme } from "./useTheme";
|
|
2
|
+
export { withStyles } from "./withStyles";
|
|
3
|
+
export { ThemeProvider, ThemeConsumer } from "./context";
|
|
4
|
+
export { useColorTransition } from "./useColorTransition";
|
|
5
|
+
export { makeUseStyles } from "./makeUseStyles";
|
|
6
|
+
export type { DefaultTheme } from "./DefaultTheme";
|
|
7
|
+
export type { InferInjectedStyledProps } from "./withStyles";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { StyleSheet } from "react-native";
|
|
2
|
+
import { useTheme } from "../useTheme";
|
|
3
|
+
import { resetUsing } from "../dependencyUsage";
|
|
4
|
+
import { getDependenciesKeys, useForceUpdate, subscribe } from "./utils";
|
|
5
|
+
|
|
6
|
+
export function createUseStylesTheme(getStyles) {
|
|
7
|
+
const scope = {
|
|
8
|
+
styles: new WeakMap(),
|
|
9
|
+
forceUpdate: [],
|
|
10
|
+
unsubscribe: () => {},
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function onUpdate() {
|
|
14
|
+
// Clear all styles to recreate them after dependencies update
|
|
15
|
+
scope.styles = new WeakMap();
|
|
16
|
+
scope.forceUpdate.forEach((fn) => fn());
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function initStyle(theme) {
|
|
20
|
+
scope.unsubscribe();
|
|
21
|
+
resetUsing();
|
|
22
|
+
|
|
23
|
+
const style = StyleSheet.create(getStyles(theme));
|
|
24
|
+
const keys = getDependenciesKeys();
|
|
25
|
+
|
|
26
|
+
scope.unsubscribe = subscribe(keys, onUpdate);
|
|
27
|
+
|
|
28
|
+
return style;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return function useStyles() {
|
|
32
|
+
const theme = useTheme();
|
|
33
|
+
|
|
34
|
+
useForceUpdate(scope);
|
|
35
|
+
|
|
36
|
+
if (!scope.styles.has(theme)) {
|
|
37
|
+
scope.styles.set(theme, initStyle(theme));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return scope.styles.get(theme);
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { renderHook, act } from "@testing-library/react-hooks";
|
|
2
|
+
import { createUseStylesTheme } from "./createUseStylesTheme";
|
|
3
|
+
import { ThemeProvider } from "../context";
|
|
4
|
+
import { createEventEmitter } from "../createEventEmitter";
|
|
5
|
+
import { addDependency } from "../dependencyRegistry";
|
|
6
|
+
import { onUse } from "../dependencyUsage";
|
|
7
|
+
|
|
8
|
+
it("should create styles using a theme", () => {
|
|
9
|
+
const mockGetStyles = jest.fn(({ colors }) => ({
|
|
10
|
+
root: { color: colors.red },
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
const useStyles = createUseStylesTheme(mockGetStyles);
|
|
14
|
+
const { result } = renderHook(() => useStyles(), {
|
|
15
|
+
wrapper: ThemeProvider,
|
|
16
|
+
initialProps: { value: { colors: { red: "red" } } },
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
expect(result.current).toEqual({
|
|
20
|
+
root: { color: "red" },
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("should create styles once", () => {
|
|
25
|
+
const theme = { colors: { red: "red" } };
|
|
26
|
+
const mockGetStyles = jest.fn(({ colors }) => ({
|
|
27
|
+
root: { color: colors.red },
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
const useStyles = createUseStylesTheme(mockGetStyles);
|
|
31
|
+
|
|
32
|
+
renderHook(
|
|
33
|
+
() => {
|
|
34
|
+
useStyles();
|
|
35
|
+
useStyles();
|
|
36
|
+
useStyles();
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
wrapper: ThemeProvider,
|
|
40
|
+
initialProps: { value: theme },
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
expect(mockGetStyles).toHaveBeenCalledTimes(1);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should not update styles when component rerender", () => {
|
|
48
|
+
const theme = { colors: { red: "red" } };
|
|
49
|
+
const mockGetStyles = jest.fn(({ colors }) => ({
|
|
50
|
+
root: { color: colors.red },
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
const useStyles = createUseStylesTheme(mockGetStyles);
|
|
54
|
+
const { rerender } = renderHook(() => useStyles(), {
|
|
55
|
+
wrapper: ThemeProvider,
|
|
56
|
+
initialProps: { value: theme },
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
rerender({ value: theme, a: 1 });
|
|
60
|
+
rerender({ value: theme, a: 2 });
|
|
61
|
+
rerender({ value: theme, a: 3 });
|
|
62
|
+
|
|
63
|
+
expect(mockGetStyles).toHaveBeenCalledTimes(1);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("should use memoized styles when component rerender", () => {
|
|
67
|
+
const theme = { colors: { red: "red" } };
|
|
68
|
+
const mockGetStyles = jest.fn(({ colors }) => ({
|
|
69
|
+
root: { color: colors.red },
|
|
70
|
+
}));
|
|
71
|
+
|
|
72
|
+
const useStyles = createUseStylesTheme(mockGetStyles);
|
|
73
|
+
const { rerender, result } = renderHook(() => useStyles(), {
|
|
74
|
+
wrapper: ThemeProvider,
|
|
75
|
+
initialProps: { value: theme },
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const initialStyles = result.current;
|
|
79
|
+
|
|
80
|
+
rerender({ value: theme, a: 1 });
|
|
81
|
+
rerender({ value: theme, a: 2 });
|
|
82
|
+
rerender({ value: theme, a: 3 });
|
|
83
|
+
|
|
84
|
+
const afterUpdateStyles = result.current;
|
|
85
|
+
|
|
86
|
+
expect(afterUpdateStyles).toBe(initialStyles);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("should update styles when theme was changed", () => {
|
|
90
|
+
const initialTheme = { colors: { red: "red" } };
|
|
91
|
+
const newTheme = { colors: { red: "white" } };
|
|
92
|
+
|
|
93
|
+
const mockGetStyles = jest.fn(({ colors }) => ({
|
|
94
|
+
root: { color: colors.red },
|
|
95
|
+
}));
|
|
96
|
+
|
|
97
|
+
const useStyles = createUseStylesTheme(mockGetStyles);
|
|
98
|
+
const { rerender } = renderHook(() => useStyles(), {
|
|
99
|
+
wrapper: ThemeProvider,
|
|
100
|
+
initialProps: { value: initialTheme },
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
act(() => {
|
|
104
|
+
rerender({ value: newTheme });
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
expect(mockGetStyles).toHaveBeenCalledTimes(2);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("should update styles when dependency changed", () => {
|
|
111
|
+
const theme = { colors: { red: "red" } };
|
|
112
|
+
const TEST_DEPENDENCY_KEY = `test_${Math.random()}`;
|
|
113
|
+
const { on, emit } = createEventEmitter(TEST_DEPENDENCY_KEY);
|
|
114
|
+
const mockGetStyles = jest.fn(({ colors }) => {
|
|
115
|
+
// simulate using a dependency
|
|
116
|
+
onUse(TEST_DEPENDENCY_KEY);
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
root: { color: colors.red },
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
addDependency(TEST_DEPENDENCY_KEY, (handler) => on(handler));
|
|
124
|
+
|
|
125
|
+
const useStyles = createUseStylesTheme(mockGetStyles);
|
|
126
|
+
|
|
127
|
+
renderHook(() => useStyles(), {
|
|
128
|
+
wrapper: ThemeProvider,
|
|
129
|
+
initialProps: { value: theme },
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
act(() => {
|
|
133
|
+
emit();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
expect(mockGetStyles).toHaveBeenCalledTimes(2);
|
|
137
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { StyleSheet } from "react-native";
|
|
2
|
+
import { resetUsing } from "../dependencyUsage";
|
|
3
|
+
import { getDependenciesKeys, subscribe, useForceUpdate } from "./utils";
|
|
4
|
+
|
|
5
|
+
export function createUseStylesWithoutTheme(getStyles) {
|
|
6
|
+
const scope = {
|
|
7
|
+
style: null,
|
|
8
|
+
forceUpdate: [],
|
|
9
|
+
unsubscribe: () => {},
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function onUpdate() {
|
|
13
|
+
// Recreate style after dependencies update
|
|
14
|
+
initStyle();
|
|
15
|
+
scope.forceUpdate.forEach((fn) => fn());
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function initStyle() {
|
|
19
|
+
scope.unsubscribe();
|
|
20
|
+
resetUsing();
|
|
21
|
+
|
|
22
|
+
scope.style = StyleSheet.create(getStyles());
|
|
23
|
+
|
|
24
|
+
const keys = getDependenciesKeys();
|
|
25
|
+
|
|
26
|
+
scope.unsubscribe = subscribe(keys, onUpdate);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
initStyle();
|
|
30
|
+
|
|
31
|
+
function useStyles() {
|
|
32
|
+
useForceUpdate(scope);
|
|
33
|
+
|
|
34
|
+
return scope.style;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return useStyles;
|
|
38
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { renderHook, act } from "@testing-library/react-hooks";
|
|
2
|
+
import { createUseStylesWithoutTheme } from "./createUseStylesWithoutTheme";
|
|
3
|
+
|
|
4
|
+
import { addDependency } from "../dependencyRegistry";
|
|
5
|
+
import { onUse } from "../dependencyUsage";
|
|
6
|
+
import { createEventEmitter } from "../createEventEmitter";
|
|
7
|
+
|
|
8
|
+
const TEST_DEPENDENCY_KEY = `test_${Math.random()}`;
|
|
9
|
+
|
|
10
|
+
const { on, emit } = createEventEmitter(TEST_DEPENDENCY_KEY);
|
|
11
|
+
|
|
12
|
+
addDependency(TEST_DEPENDENCY_KEY, (handler) => on(handler));
|
|
13
|
+
|
|
14
|
+
const useStyles = createUseStylesWithoutTheme(() => {
|
|
15
|
+
// simulate using a dependency
|
|
16
|
+
onUse(TEST_DEPENDENCY_KEY);
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
root: { color: "red" },
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should use the same styles object to reduce memory usage", () => {
|
|
24
|
+
const { result } = renderHook(() => ({
|
|
25
|
+
stl1: useStyles(),
|
|
26
|
+
stl2: useStyles(),
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
expect(result.current.stl1).toBe(result.current.stl2);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("should update styles when dependency changed", () => {
|
|
33
|
+
const { result } = renderHook(() => ({
|
|
34
|
+
stl1: useStyles(),
|
|
35
|
+
stl2: useStyles(),
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
const initialResult = { ...result.current };
|
|
39
|
+
|
|
40
|
+
act(() => {
|
|
41
|
+
emit();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const updatedResult = { ...result.current };
|
|
45
|
+
|
|
46
|
+
expect(initialResult.stl1).not.toBe(updatedResult.stl1);
|
|
47
|
+
expect(initialResult.stl2).not.toBe(updatedResult.stl2);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("should return the same style when component rerender", () => {
|
|
51
|
+
const mockUseStyles = jest.fn(useStyles);
|
|
52
|
+
|
|
53
|
+
const { result, rerender } = renderHook(() => mockUseStyles());
|
|
54
|
+
|
|
55
|
+
const initialResult = result.current;
|
|
56
|
+
|
|
57
|
+
rerender({});
|
|
58
|
+
|
|
59
|
+
const updatedResult = result.current;
|
|
60
|
+
|
|
61
|
+
expect(mockUseStyles).toHaveBeenCalledTimes(2);
|
|
62
|
+
expect(initialResult).toBe(updatedResult);
|
|
63
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { StyleSheet } from "react-native";
|
|
2
|
+
import { DefaultTheme } from "../DefaultTheme";
|
|
3
|
+
|
|
4
|
+
export function makeUseStyles<
|
|
5
|
+
Theme extends DefaultTheme,
|
|
6
|
+
T extends StyleSheet.NamedStyles<T> | StyleSheet.NamedStyles<any>
|
|
7
|
+
>(getStyles: (theme: Theme) => T | StyleSheet.NamedStyles<T>): () => T;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createUseStylesWithoutTheme } from "./createUseStylesWithoutTheme";
|
|
2
|
+
import { createUseStylesTheme } from "./createUseStylesTheme";
|
|
3
|
+
|
|
4
|
+
export function makeUseStyles(getStyles) {
|
|
5
|
+
const hasThemeDependency = getStyles.length === 1;
|
|
6
|
+
|
|
7
|
+
if (!hasThemeDependency) {
|
|
8
|
+
return createUseStylesWithoutTheme(getStyles);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return createUseStylesTheme(getStyles);
|
|
12
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createUseStylesWithoutTheme } from "./createUseStylesWithoutTheme";
|
|
2
|
+
import { createUseStylesTheme } from "./createUseStylesTheme";
|
|
3
|
+
import { makeUseStyles } from "./index";
|
|
4
|
+
|
|
5
|
+
jest.mock("./createUseStylesWithoutTheme");
|
|
6
|
+
jest.mock("./createUseStylesTheme");
|
|
7
|
+
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
jest.clearAllMocks();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("should invoke 'createUseStylesWithoutTheme' when pass function without first argument", () => {
|
|
13
|
+
const withoutThemeDeps = () => ({});
|
|
14
|
+
|
|
15
|
+
makeUseStyles(withoutThemeDeps);
|
|
16
|
+
|
|
17
|
+
expect(createUseStylesWithoutTheme).toHaveBeenCalledTimes(1);
|
|
18
|
+
expect(createUseStylesTheme).toHaveBeenCalledTimes(0);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("should invoke 'createUseStylesTheme' when pass function with first argument", () => {
|
|
22
|
+
const withThemeDeps = (theme) => ({});
|
|
23
|
+
|
|
24
|
+
makeUseStyles(withThemeDeps);
|
|
25
|
+
|
|
26
|
+
expect(createUseStylesWithoutTheme).toHaveBeenCalledTimes(0);
|
|
27
|
+
expect(createUseStylesTheme).toHaveBeenCalledTimes(1);
|
|
28
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { makeUseStyles } from "./index";
|
|
2
|
+
{
|
|
3
|
+
const useStyles = makeUseStyles(() => ({
|
|
4
|
+
root: {
|
|
5
|
+
// @ts-expect-error: invalid color value
|
|
6
|
+
color: [],
|
|
7
|
+
},
|
|
8
|
+
}));
|
|
9
|
+
const {
|
|
10
|
+
root,
|
|
11
|
+
// @ts-expect-error: 'otherProps' not exist
|
|
12
|
+
otherProp,
|
|
13
|
+
} = useStyles();
|
|
14
|
+
}
|
|
15
|
+
{
|
|
16
|
+
const useStyles = makeUseStyles((theme) => ({
|
|
17
|
+
root: {
|
|
18
|
+
color: theme.color.red,
|
|
19
|
+
// @ts-expect-error: 'blue' not exist in passed theme
|
|
20
|
+
borderColor: theme.color.blue,
|
|
21
|
+
},
|
|
22
|
+
}));
|
|
23
|
+
const {
|
|
24
|
+
root,
|
|
25
|
+
// @ts-expect-error: 'otherProps' not exist
|
|
26
|
+
otherProp,
|
|
27
|
+
} = useStyles();
|
|
28
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { optimizeDependencies } from "../dimensions/utils";
|
|
2
|
+
import { getUsing } from "../dependencyUsage";
|
|
3
|
+
import { getDependency } from "../dependencyRegistry";
|
|
4
|
+
|
|
5
|
+
import { useEffect, useRef, useState } from "react";
|
|
6
|
+
|
|
7
|
+
/* istanbul ignore next */
|
|
8
|
+
const noop = () => {};
|
|
9
|
+
|
|
10
|
+
export const useForceUpdate = (scope) => {
|
|
11
|
+
const unsubscribeRef = useRef(noop);
|
|
12
|
+
const setState = useState(false)[1];
|
|
13
|
+
|
|
14
|
+
if (unsubscribeRef.current === noop) {
|
|
15
|
+
const forceRerender = () => setState((flag) => !flag);
|
|
16
|
+
|
|
17
|
+
scope.forceUpdate = scope.forceUpdate.concat(forceRerender);
|
|
18
|
+
|
|
19
|
+
unsubscribeRef.current = () => {
|
|
20
|
+
scope.forceUpdate = scope.forceUpdate.filter(
|
|
21
|
+
(fn) => fn !== forceRerender
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
useEffect(
|
|
27
|
+
() => () => {
|
|
28
|
+
unsubscribeRef.current();
|
|
29
|
+
unsubscribeRef.current = noop;
|
|
30
|
+
},
|
|
31
|
+
[]
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const getDependenciesKeys = () =>
|
|
36
|
+
Object.keys(optimizeDependencies(getUsing())).sort();
|
|
37
|
+
|
|
38
|
+
export const subscribe = (dependenciesKeys, handler) => {
|
|
39
|
+
if (dependenciesKeys.length === 0) {
|
|
40
|
+
/* istanbul ignore next */
|
|
41
|
+
return () => {};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const unsubscribeFns = dependenciesKeys
|
|
45
|
+
.map(getDependency)
|
|
46
|
+
.filter(
|
|
47
|
+
/* istanbul ignore next */
|
|
48
|
+
process.env.NODE_ENV !== "production"
|
|
49
|
+
? (onChange, index) => {
|
|
50
|
+
const dependencyName = dependenciesKeys[index];
|
|
51
|
+
|
|
52
|
+
if (!onChange) {
|
|
53
|
+
console.warn(
|
|
54
|
+
`[react-native-stylex] Could not find onChange handler for ${dependencyName}!`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return !!onChange;
|
|
59
|
+
}
|
|
60
|
+
: Boolean
|
|
61
|
+
)
|
|
62
|
+
.map((onChange) => onChange(handler));
|
|
63
|
+
|
|
64
|
+
return () => {
|
|
65
|
+
unsubscribeFns.forEach((unsubscribe) => unsubscribe());
|
|
66
|
+
};
|
|
67
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ScaledSize } from "react-native";
|
|
2
|
+
|
|
3
|
+
import { getWindowDimensions } from "../dimensions";
|
|
4
|
+
|
|
5
|
+
export const createDimensionQueryHelper = <Value>(
|
|
6
|
+
queryFunction: (options: { value: Value; dimensions: ScaledSize }) => boolean
|
|
7
|
+
) => <T>(value: Value, styles: T): null | T => {
|
|
8
|
+
const isMatched = queryFunction({ value, dimensions: getWindowDimensions() });
|
|
9
|
+
|
|
10
|
+
if (isMatched) {
|
|
11
|
+
return styles;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return null;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const maxHeight = createDimensionQueryHelper<number>(
|
|
18
|
+
({ value, dimensions }) => value >= dimensions.height
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
export const maxWidth = createDimensionQueryHelper<number>(
|
|
22
|
+
({ value, dimensions }) => value >= dimensions.width
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
export const minHeight = createDimensionQueryHelper<number>(
|
|
26
|
+
({ value, dimensions }) => value <= dimensions.height
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
export const minWidth = createDimensionQueryHelper<number>(
|
|
30
|
+
({ value, dimensions }) => value <= dimensions.width
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
export const minAspectRatio = createDimensionQueryHelper<number>(
|
|
34
|
+
({ value, dimensions: { width, height } }) => value <= width / height
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
export const maxAspectRatio = createDimensionQueryHelper<number>(
|
|
38
|
+
({ value, dimensions: { width, height } }) => value >= width / height
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
export const aspectRatio = createDimensionQueryHelper<number>(
|
|
42
|
+
({ value, dimensions: { width, height } }) => value === width / height
|
|
43
|
+
);
|