@applicaster/zapp-react-native-ui-components 15.0.0-rc.144 → 15.0.0-rc.146
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/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +5 -0
- package/Components/MasterCell/CONFIG_BUILDER_TO_REACT_COMPONENT.md +144 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/ActionButtonController.tsx +165 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/__tests__/ActionButtonController.test.tsx +405 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/index.ts +1 -0
- package/Components/MasterCell/DefaultComponents/ButtonContainerView/components/HorizontalSeparator.tsx +8 -0
- package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.tsx +15 -0
- package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.tv.android.tsx +58 -0
- package/Components/MasterCell/DefaultComponents/{tv/ButtonContainerView/index.tsx → ButtonContainerView/index.tv.tsx} +3 -11
- package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.web.ts +1 -0
- package/Components/MasterCell/DefaultComponents/ButtonContainerView/types.ts +40 -0
- package/Components/MasterCell/DefaultComponents/DataProvider/index.tsx +163 -0
- package/Components/MasterCell/DefaultComponents/FocusableView/index.android.tsx +2 -23
- package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +4 -22
- package/Components/MasterCell/DefaultComponents/PressableView.tsx +7 -234
- package/Components/MasterCell/DefaultComponents/Text/hooks/useText.ts +11 -0
- package/Components/MasterCell/DefaultComponents/__tests__/DataProvider.test.tsx +141 -0
- package/Components/MasterCell/DefaultComponents/index.ts +7 -3
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ActionButton.tsx +135 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Asset.ts +20 -29
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/AssetComponent.tsx +22 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +67 -69
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +21 -16
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +207 -9
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/builders.test.ts +56 -55
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +137 -16
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +49 -31
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +165 -0
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/Asset.ts +4 -18
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/Button.ts +24 -73
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/TextLabelsContainer.ts +37 -18
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/TvActionButton.tsx +27 -0
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/index.test.ts +24 -21
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/renderedTree.test.tsx +231 -0
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +24 -12
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +62 -0
- package/Components/MasterCell/MappingFunctions/index.js +3 -2
- package/Components/MasterCell/README.md +4 -0
- package/Components/MasterCell/__tests__/__snapshots__/dataAdapter.test.js.snap +24 -0
- package/Components/MasterCell/__tests__/configInflater.test.js +1 -0
- package/Components/MasterCell/__tests__/elementMapper.test.js +46 -0
- package/Components/MasterCell/dataAdapter.ts +4 -1
- package/Components/MasterCell/elementMapper.tsx +51 -7
- package/Components/MasterCell/utils/__tests__/cloneChildrenWithIds.test.tsx +43 -0
- package/Components/MasterCell/utils/__tests__/useFilterChildren.test.tsx +80 -0
- package/Components/MasterCell/utils/index.ts +85 -15
- package/Components/Navigator/StackNavigator.tsx +6 -0
- package/Components/ScreenRevealManager/withScreenRevealManager.tsx +4 -1
- package/package.json +5 -5
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ButtonContainerView.ts +0 -23
- package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.android.tsx +0 -135
- package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/types.ts +0 -25
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { View } from "react-native";
|
|
3
|
+
|
|
4
|
+
type Props = Record<string, any> & {
|
|
5
|
+
children?: React.ReactNode;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Creates the provider payload from the resolved runtime props.
|
|
10
|
+
* `children` is structural and is therefore excluded. `_dataKey` is produced
|
|
11
|
+
* by `configInflater` from the data mapping propName and identifies the prop
|
|
12
|
+
* that carries the resolved entry value.
|
|
13
|
+
*/
|
|
14
|
+
const createDataProviderProps = ({ children: _children, ...props }: Props) => ({
|
|
15
|
+
_dataKey: props._dataKey,
|
|
16
|
+
[props._dataKey as string]: props[props._dataKey as string],
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Merges provider values into an element's existing `dataProviderProps`
|
|
21
|
+
* without overwriting keys that were supplied explicitly.
|
|
22
|
+
*/
|
|
23
|
+
const mergeDataProviderProps = (
|
|
24
|
+
targetProps: Record<string, unknown>,
|
|
25
|
+
forwardedProps: Record<string, unknown>
|
|
26
|
+
) => {
|
|
27
|
+
const dataProviderProps = Object.keys(forwardedProps).reduce(
|
|
28
|
+
(acc, key) => {
|
|
29
|
+
if (typeof acc[key] === "undefined") {
|
|
30
|
+
acc[key] = forwardedProps[key];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return acc;
|
|
34
|
+
},
|
|
35
|
+
{ ...(targetProps.dataProviderProps as Record<string, unknown>) } as Record<
|
|
36
|
+
string,
|
|
37
|
+
unknown
|
|
38
|
+
>
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
dataProviderProps: dataProviderProps,
|
|
43
|
+
...(targetProps[dataProviderProps._dataKey as string] === undefined
|
|
44
|
+
? {
|
|
45
|
+
[dataProviderProps._dataKey as string]:
|
|
46
|
+
forwardedProps[dataProviderProps._dataKey as string],
|
|
47
|
+
}
|
|
48
|
+
: {}),
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Applies the provider props to the wrapped child's direct children only.
|
|
54
|
+
* The shallow stop is intentional: descendants below this level must receive
|
|
55
|
+
* data explicitly from their parent component instead of implicit propagation.
|
|
56
|
+
*/
|
|
57
|
+
const cloneSecondLevel = (
|
|
58
|
+
nodes: React.ReactNode,
|
|
59
|
+
forwardedProps: Record<string, unknown>
|
|
60
|
+
) =>
|
|
61
|
+
React.Children.map(nodes, (child) => {
|
|
62
|
+
if (!React.isValidElement(child)) {
|
|
63
|
+
return child;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (child.type === View) {
|
|
67
|
+
return child;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return React.cloneElement(
|
|
71
|
+
child,
|
|
72
|
+
mergeDataProviderProps(
|
|
73
|
+
child.props as Record<string, unknown>,
|
|
74
|
+
forwardedProps
|
|
75
|
+
) as never
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Specification:
|
|
81
|
+
* `DataProvider` bridges MasterCell node-tree `data` mappings into runtime
|
|
82
|
+
* React props for a shallow wrapped subtree under `dataProviderProps`.
|
|
83
|
+
*
|
|
84
|
+
* Purpose:
|
|
85
|
+
* Use `DataProvider` when a subtree root and that root's direct children must
|
|
86
|
+
* receive the same resolved runtime prop without propagating that prop deeper
|
|
87
|
+
* into the tree.
|
|
88
|
+
*
|
|
89
|
+
* Input contract:
|
|
90
|
+
* - `DataProvider` receives runtime props from normal MasterCell inflation.
|
|
91
|
+
* - The values under `dataProviderProps` are derived from the resolved props
|
|
92
|
+
* it receives.
|
|
93
|
+
* - The inner keys are not hardcoded and may include `entry`, `item`, or any
|
|
94
|
+
* other prop name produced by the node-tree `data` mapping.
|
|
95
|
+
*
|
|
96
|
+
* Forwarding contract:
|
|
97
|
+
* - inject the mapped value as a direct prop on the wrapped child
|
|
98
|
+
* - inject `dataProviderProps` into the wrapped child
|
|
99
|
+
* - inject both the direct prop and `dataProviderProps` into that child's
|
|
100
|
+
* direct children
|
|
101
|
+
* - stop at that level
|
|
102
|
+
* - do not overwrite explicit keys that already exist inside
|
|
103
|
+
* `dataProviderProps` on wrapped elements
|
|
104
|
+
*
|
|
105
|
+
* Non-forwarded props:
|
|
106
|
+
* - `children`
|
|
107
|
+
*
|
|
108
|
+
* Example:
|
|
109
|
+
*
|
|
110
|
+
* ```ts
|
|
111
|
+
* {
|
|
112
|
+
* type: "DataProvider",
|
|
113
|
+
* data: [
|
|
114
|
+
* {
|
|
115
|
+
* func: "identity",
|
|
116
|
+
* args: [],
|
|
117
|
+
* propName: "entry",
|
|
118
|
+
* },
|
|
119
|
+
* ],
|
|
120
|
+
* elements: [
|
|
121
|
+
* {
|
|
122
|
+
* type: "ButtonContainerView",
|
|
123
|
+
* elements: [Button({ ... })],
|
|
124
|
+
* },
|
|
125
|
+
* ],
|
|
126
|
+
* }
|
|
127
|
+
* ```
|
|
128
|
+
*
|
|
129
|
+
* Expected runtime result:
|
|
130
|
+
* - `ButtonContainerView` receives `entry` and `dataProviderProps.entry`
|
|
131
|
+
* - each direct `Button` child receives `entry` and `dataProviderProps.entry`
|
|
132
|
+
* - deeper descendants such as `Asset` or `TextLabel` do not receive
|
|
133
|
+
* `entry` or `dataProviderProps.entry` unless their parent passes it
|
|
134
|
+
* explicitly
|
|
135
|
+
*/
|
|
136
|
+
export const DataProvider = ({ children, ...props }: Props) => {
|
|
137
|
+
const forwardedProps = createDataProviderProps(props);
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<>
|
|
141
|
+
{React.Children.map(children, (child) => {
|
|
142
|
+
if (!React.isValidElement(child)) {
|
|
143
|
+
return child;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const injectedChildren = cloneSecondLevel(
|
|
147
|
+
child.props.children,
|
|
148
|
+
forwardedProps
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
return React.cloneElement(child, {
|
|
152
|
+
...mergeDataProviderProps(
|
|
153
|
+
child.props as Record<string, unknown>,
|
|
154
|
+
forwardedProps
|
|
155
|
+
),
|
|
156
|
+
...(typeof injectedChildren !== "undefined"
|
|
157
|
+
? { children: injectedChildren }
|
|
158
|
+
: {}),
|
|
159
|
+
});
|
|
160
|
+
})}
|
|
161
|
+
</>
|
|
162
|
+
);
|
|
163
|
+
};
|
|
@@ -1,17 +1,11 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { View, ImageStyle } from "react-native";
|
|
3
3
|
import { Focusable } from "@applicaster/zapp-react-native-ui-components/Components/Focusable";
|
|
4
|
-
import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
|
|
5
|
-
import { getXray } from "@applicaster/zapp-react-native-utils/logger";
|
|
6
4
|
import { useFocusable } from "@applicaster/zapp-react-native-ui-components/Components/Focusable/index.android";
|
|
7
5
|
import { useIsRTL } from "@applicaster/zapp-react-native-utils/localizationUtils";
|
|
8
6
|
|
|
9
|
-
const { Logger } = getXray();
|
|
10
|
-
|
|
11
7
|
import { recursiveCloneElementsWithState } from "../../utils";
|
|
12
8
|
|
|
13
|
-
const logger = new Logger("plugin", "plugins/navigation-action");
|
|
14
|
-
|
|
15
9
|
type ButtonProps = Record<string, any> & {
|
|
16
10
|
style: ImageStyle;
|
|
17
11
|
};
|
|
@@ -33,7 +27,7 @@ const getNextFocusRight = ({ nextFocusRight, parentFocus, isRTL }) => {
|
|
|
33
27
|
};
|
|
34
28
|
|
|
35
29
|
export function FocusableViewComponent(
|
|
36
|
-
{ style, children,
|
|
30
|
+
{ style, children, ...otherProps }: ButtonProps,
|
|
37
31
|
ref
|
|
38
32
|
) {
|
|
39
33
|
const {
|
|
@@ -44,7 +38,6 @@ export function FocusableViewComponent(
|
|
|
44
38
|
focusedStyles,
|
|
45
39
|
nextFocusLeft,
|
|
46
40
|
nextFocusRight,
|
|
47
|
-
pluginIdentifier,
|
|
48
41
|
disableFocus,
|
|
49
42
|
onButtonFocus,
|
|
50
43
|
} = otherProps;
|
|
@@ -52,20 +45,6 @@ export function FocusableViewComponent(
|
|
|
52
45
|
const parentFocus = useFocusable();
|
|
53
46
|
const isRTL = useIsRTL();
|
|
54
47
|
|
|
55
|
-
const actionContext = useActions(pluginIdentifier);
|
|
56
|
-
|
|
57
|
-
const onPress = () => {
|
|
58
|
-
if (!actionContext) {
|
|
59
|
-
logger.warning(
|
|
60
|
-
`Cannot resolve action context for ${pluginIdentifier} - please make sure the plugin is installed and up to date`
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
actionContext?.invokeAction?.(item);
|
|
67
|
-
};
|
|
68
|
-
|
|
69
48
|
const onFocus = React.useCallback(
|
|
70
49
|
(ref) => {
|
|
71
50
|
onButtonFocus?.(ref);
|
|
@@ -82,7 +61,7 @@ export function FocusableViewComponent(
|
|
|
82
61
|
id={generateId(cellUUID, suffixId)}
|
|
83
62
|
disableFocus={disableFocus}
|
|
84
63
|
groupId={groupId}
|
|
85
|
-
onPress={onPress}
|
|
64
|
+
onPress={otherProps.onPress}
|
|
86
65
|
nextFocusUp={parentFocus?.nextFocusUp}
|
|
87
66
|
nextFocusDown={parentFocus?.nextFocusDown}
|
|
88
67
|
nextFocusLeft={getNextFocusLeft({ nextFocusLeft, parentFocus, isRTL })}
|
|
@@ -1,26 +1,20 @@
|
|
|
1
1
|
import React, { useMemo } from "react";
|
|
2
2
|
import { ImageStyle } from "react-native";
|
|
3
3
|
import { Focusable } from "@applicaster/zapp-react-native-ui-components/Components/Focusable";
|
|
4
|
-
import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
|
|
5
|
-
import { getXray } from "@applicaster/zapp-react-native-utils/logger";
|
|
6
4
|
import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
|
|
7
5
|
|
|
8
|
-
const { Logger } = getXray();
|
|
9
|
-
|
|
10
6
|
import {
|
|
11
7
|
recursiveCloneElementsWithState,
|
|
12
8
|
getFocusedButtonId,
|
|
13
9
|
} from "../../utils";
|
|
14
10
|
|
|
15
|
-
const logger = new Logger("plugin", "plugins/navigation-action");
|
|
16
|
-
|
|
17
11
|
type Props = Record<string, any> & {
|
|
18
12
|
style: ImageStyle;
|
|
19
13
|
};
|
|
20
14
|
|
|
21
15
|
const getFocusableId = (prefixId, suffixId) => `${prefixId}___${suffixId}`;
|
|
22
16
|
|
|
23
|
-
export function FocusableView({ style, children,
|
|
17
|
+
export function FocusableView({ style, children, ...otherProps }: Props) {
|
|
24
18
|
const {
|
|
25
19
|
groupId,
|
|
26
20
|
prefixId,
|
|
@@ -29,7 +23,6 @@ export function FocusableView({ style, children, item, ...otherProps }: Props) {
|
|
|
29
23
|
focusedButtonId,
|
|
30
24
|
normalStyles,
|
|
31
25
|
focusedStyles,
|
|
32
|
-
pluginIdentifier,
|
|
33
26
|
preferredFocus,
|
|
34
27
|
} = otherProps;
|
|
35
28
|
|
|
@@ -41,20 +34,9 @@ export function FocusableView({ style, children, item, ...otherProps }: Props) {
|
|
|
41
34
|
|
|
42
35
|
const additionalStyles = focused ? focusedStyles : normalStyles;
|
|
43
36
|
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
const onPress = (event) => {
|
|
37
|
+
const handlePress = (event) => {
|
|
47
38
|
event?.preventDefault?.();
|
|
48
|
-
|
|
49
|
-
if (!actionContext) {
|
|
50
|
-
logger.warning(
|
|
51
|
-
`Cannot resolve action context for ${pluginIdentifier} - please make sure the plugin is installed and up to date`
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
actionContext?.invokeAction?.(item);
|
|
39
|
+
otherProps?.onPress?.(event);
|
|
58
40
|
};
|
|
59
41
|
|
|
60
42
|
const handleFocus = (focusable) => {
|
|
@@ -83,7 +65,7 @@ export function FocusableView({ style, children, item, ...otherProps }: Props) {
|
|
|
83
65
|
style={styles}
|
|
84
66
|
onFocus={handleFocus}
|
|
85
67
|
onBlur={handleBlur}
|
|
86
|
-
onPress={
|
|
68
|
+
onPress={handlePress}
|
|
87
69
|
preferredFocus={preferredFocus}
|
|
88
70
|
skipFocusManagerRegistration={otherProps.skipFocusManagerRegistration}
|
|
89
71
|
isFocusable={otherProps.isFocusable}
|
|
@@ -1,261 +1,34 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from "react";
|
|
2
2
|
import { TouchableOpacity } from "react-native";
|
|
3
3
|
|
|
4
|
-
import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
|
|
5
|
-
|
|
6
|
-
import { masterCellLogger } from "../logger";
|
|
7
|
-
import {
|
|
8
|
-
buildLegacySelection,
|
|
9
|
-
resolveIsActive,
|
|
10
|
-
resolveLabelText,
|
|
11
|
-
} from "./mobile/MobileActionButtons/helpers";
|
|
12
|
-
|
|
13
|
-
type ChildElementProps = {
|
|
14
|
-
children?: React.ReactNode;
|
|
15
|
-
mobileActionRole?: string;
|
|
16
|
-
uri?: string;
|
|
17
|
-
state?: "default" | "focused";
|
|
18
|
-
};
|
|
19
|
-
|
|
20
4
|
type Props = {
|
|
21
5
|
children?: React.ReactNode;
|
|
22
|
-
item: ZappEntry | ZappFeed;
|
|
23
|
-
action?: {
|
|
24
|
-
identifier?: string;
|
|
25
|
-
flavour?: "flavour_1" | "flavour_2";
|
|
26
|
-
};
|
|
27
6
|
style?: Record<string, unknown>;
|
|
28
|
-
focusedStyles?: Record<string, unknown>;
|
|
29
7
|
testID?: string;
|
|
30
8
|
accessibilityLabel?: string;
|
|
31
9
|
accessibilityHint?: string;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const isValidElement = (
|
|
35
|
-
child: React.ReactNode
|
|
36
|
-
): child is React.ReactElement<ChildElementProps> =>
|
|
37
|
-
React.isValidElement(child);
|
|
38
|
-
|
|
39
|
-
/** retrieves asset uri for a given flavour,
|
|
40
|
-
* if flavour is not provided, returns the default asset from `asset` or selected state asset (if available)
|
|
41
|
-
* asset can be:
|
|
42
|
-
* provided as asset path,
|
|
43
|
-
* provided as [default || flavour_1, alternative || flavour_2] array.
|
|
44
|
-
* mobileButtonAssets asset can be:
|
|
45
|
-
* provided as asset path,
|
|
46
|
-
* provided as [default || flavour_1, alternative || flavour_2] array,
|
|
47
|
-
* provided as [[] as flavour_1, [] as flavour_2] array for multiple flavours, where each flavour can be either a path or [default, alternative] array.
|
|
48
|
-
*
|
|
49
|
-
* isActive reflect the state of the action and can be used to render different asset for active/inactive state if asset is provided as array
|
|
50
|
-
*
|
|
51
|
-
* */
|
|
52
|
-
const selectByAssetFlavour = (
|
|
53
|
-
actionState: {
|
|
54
|
-
asset?: string | [string, string];
|
|
55
|
-
mobileButtonAssets?: [string, string] | [string, string][];
|
|
56
|
-
},
|
|
57
|
-
flavour?: "flavour_1" | "flavour_2",
|
|
58
|
-
isActive?: boolean
|
|
59
|
-
) => {
|
|
60
|
-
if (actionState.mobileButtonAssets) {
|
|
61
|
-
if (flavour) {
|
|
62
|
-
if (flavour === "flavour_1") {
|
|
63
|
-
if (typeof actionState.mobileButtonAssets[0] === "string") {
|
|
64
|
-
return actionState.mobileButtonAssets[0];
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (Array.isArray(actionState.mobileButtonAssets[0])) {
|
|
68
|
-
return actionState.mobileButtonAssets[0][isActive ? 1 : 0];
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return actionState.mobileButtonAssets[0];
|
|
72
|
-
} else if (flavour === "flavour_2") {
|
|
73
|
-
if (typeof actionState.mobileButtonAssets[1] === "string") {
|
|
74
|
-
return actionState.mobileButtonAssets[1];
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (Array.isArray(actionState.mobileButtonAssets[1])) {
|
|
78
|
-
return actionState.mobileButtonAssets[1][isActive ? 1 : 0];
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return actionState.mobileButtonAssets[1];
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return Array.isArray(actionState.mobileButtonAssets[0])
|
|
86
|
-
? actionState.mobileButtonAssets[0][isActive ? 1 : 0]
|
|
87
|
-
: actionState.mobileButtonAssets[0];
|
|
88
|
-
} else {
|
|
89
|
-
return typeof actionState?.asset === "string"
|
|
90
|
-
? actionState.asset
|
|
91
|
-
: actionState.asset[isActive ? 1 : 0];
|
|
92
|
-
}
|
|
10
|
+
onPress?: () => void;
|
|
93
11
|
};
|
|
94
12
|
|
|
95
13
|
export function PressableView({
|
|
96
14
|
children,
|
|
97
|
-
item,
|
|
98
|
-
action,
|
|
99
15
|
style = {},
|
|
100
|
-
focusedStyles = {},
|
|
101
16
|
testID,
|
|
102
17
|
accessibilityLabel,
|
|
103
18
|
accessibilityHint,
|
|
19
|
+
onPress,
|
|
104
20
|
}: Props) {
|
|
105
|
-
const actionContext = useActions(action?.identifier);
|
|
106
|
-
|
|
107
|
-
const actionDisabled =
|
|
108
|
-
typeof actionContext?.isActionAvailable === "function" &&
|
|
109
|
-
!actionContext.isActionAvailable(item);
|
|
110
|
-
|
|
111
|
-
const supportsEntryState =
|
|
112
|
-
typeof actionContext?.initialEntryState === "function" && !actionDisabled;
|
|
113
|
-
|
|
114
|
-
const [actionState, setActionState] = useState(() =>
|
|
115
|
-
supportsEntryState ? actionContext.initialEntryState(item) : null
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
useEffect(() => {
|
|
119
|
-
if (supportsEntryState) {
|
|
120
|
-
setActionState(actionContext.initialEntryState(item));
|
|
121
|
-
}
|
|
122
|
-
}, [supportsEntryState, item, actionContext]);
|
|
123
|
-
|
|
124
|
-
useEffect(() => {
|
|
125
|
-
if (typeof actionContext?.addListener === "function") {
|
|
126
|
-
return actionContext.addListener(String(item?.id), (nextState) => {
|
|
127
|
-
setActionState(nextState);
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (typeof actionContext?.addListeners === "function") {
|
|
132
|
-
return actionContext.addListeners(({ entryState, entry }) => {
|
|
133
|
-
if (entry?.id === item?.id) {
|
|
134
|
-
setActionState(entryState);
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return undefined;
|
|
140
|
-
}, [actionContext, item?.id]);
|
|
141
|
-
|
|
142
|
-
const legacySelected = useMemo(() => {
|
|
143
|
-
if (!actionContext || supportsEntryState) {
|
|
144
|
-
return false;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return buildLegacySelection(item, actionContext);
|
|
148
|
-
}, [actionContext, supportsEntryState, item]);
|
|
149
|
-
|
|
150
|
-
const isActive = resolveIsActive(actionState, legacySelected);
|
|
151
|
-
|
|
152
|
-
const labelText = supportsEntryState
|
|
153
|
-
? resolveLabelText(actionState?.label)
|
|
154
|
-
: "";
|
|
155
|
-
|
|
156
|
-
const shouldRenderAsset = Boolean(
|
|
157
|
-
supportsEntryState
|
|
158
|
-
? actionState?.asset && actionState?.mobileButtonAssets
|
|
159
|
-
: false
|
|
160
|
-
);
|
|
161
|
-
|
|
162
|
-
const shouldRenderLabel = Boolean(labelText);
|
|
163
|
-
|
|
164
|
-
const cloneChildrenWithState = (nodes?: React.ReactNode): React.ReactNode => {
|
|
165
|
-
return React.Children.map(nodes, (child) => {
|
|
166
|
-
if (!isValidElement(child)) {
|
|
167
|
-
return child;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const role = child.props.mobileActionRole;
|
|
171
|
-
const nextChildren = cloneChildrenWithState(child.props.children);
|
|
172
|
-
|
|
173
|
-
if (role === "asset" && !shouldRenderAsset) {
|
|
174
|
-
return null;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (role === "label" && !shouldRenderLabel) {
|
|
178
|
-
return null;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (
|
|
182
|
-
role === "label_container" &&
|
|
183
|
-
React.Children.count(nextChildren) === 0
|
|
184
|
-
) {
|
|
185
|
-
return null;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const nextProps: Partial<ChildElementProps> = {};
|
|
189
|
-
|
|
190
|
-
if (role === "asset") {
|
|
191
|
-
if (action.flavour) {
|
|
192
|
-
nextProps.uri = selectByAssetFlavour(
|
|
193
|
-
actionState,
|
|
194
|
-
action.flavour,
|
|
195
|
-
isActive
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (role === "label") {
|
|
201
|
-
nextProps.state = isActive ? "focused" : "default";
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (nextChildren !== child.props.children) {
|
|
205
|
-
nextProps.children = nextChildren;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return React.cloneElement(child, nextProps);
|
|
209
|
-
});
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
const onPress = useCallback(async () => {
|
|
213
|
-
if (!actionContext) {
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
if (supportsEntryState) {
|
|
218
|
-
return actionContext.invokeAction(item, {
|
|
219
|
-
updateState: setActionState,
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const favouritesAction = legacySelected
|
|
224
|
-
? actionContext.removeFavourite
|
|
225
|
-
: actionContext.addFavourite;
|
|
226
|
-
|
|
227
|
-
const toggleAction = actionContext?.invokeAction ?? favouritesAction;
|
|
228
|
-
|
|
229
|
-
return toggleAction?.(item);
|
|
230
|
-
}, [actionContext, supportsEntryState, item, legacySelected]);
|
|
231
|
-
|
|
232
|
-
if (!actionContext) {
|
|
233
|
-
masterCellLogger.warning(
|
|
234
|
-
`You're missing an action plugin(${action?.identifier}) required by your mobile action button.`
|
|
235
|
-
);
|
|
236
|
-
|
|
237
|
-
return null;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (actionDisabled) {
|
|
241
|
-
return null;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if (!shouldRenderAsset && !shouldRenderLabel) {
|
|
245
|
-
return null;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
21
|
return (
|
|
249
22
|
<TouchableOpacity
|
|
250
23
|
activeOpacity={1}
|
|
251
24
|
onPress={onPress}
|
|
252
|
-
testID={testID
|
|
253
|
-
accessibilityLabel={accessibilityLabel
|
|
25
|
+
testID={testID}
|
|
26
|
+
accessibilityLabel={accessibilityLabel}
|
|
254
27
|
accessibilityHint={accessibilityHint}
|
|
255
28
|
accessible={!!(testID || accessibilityLabel)}
|
|
256
|
-
style={
|
|
29
|
+
style={style}
|
|
257
30
|
>
|
|
258
|
-
{
|
|
31
|
+
{children}
|
|
259
32
|
</TouchableOpacity>
|
|
260
33
|
);
|
|
261
34
|
}
|
|
@@ -45,6 +45,7 @@ export const useTextLabel = ({ label, entry }): string => {
|
|
|
45
45
|
const [entryStateLocal, setEntryStateLocal] =
|
|
46
46
|
React.useState(initialEntryState);
|
|
47
47
|
|
|
48
|
+
// For favourites
|
|
48
49
|
React.useEffect(() => {
|
|
49
50
|
return action?.addListeners?.(({ entryState, entry: actionEntry }) => {
|
|
50
51
|
if (entry.id === actionEntry.id) {
|
|
@@ -53,6 +54,16 @@ export const useTextLabel = ({ label, entry }): string => {
|
|
|
53
54
|
});
|
|
54
55
|
}, []);
|
|
55
56
|
|
|
57
|
+
// For rest actions
|
|
58
|
+
React.useEffect(() => {
|
|
59
|
+
// Update entryStateLocal when action state changes. Example: state change when pressing the download button.
|
|
60
|
+
if (typeof action?.addListener === "function") {
|
|
61
|
+
return action.addListener(String(entry?.id), (nextState) => {
|
|
62
|
+
setEntryStateLocal(nextState);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}, []);
|
|
66
|
+
|
|
56
67
|
if (context && name && action) {
|
|
57
68
|
return prepareHebrewText(extractLabel(entryStateLocal.label, name), isRTL);
|
|
58
69
|
}
|