@applicaster/zapp-react-native-ui-components 16.0.0-rc.72 → 16.0.0-rc.73

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,22 +2,10 @@ import React, { useCallback, useEffect } from "react";
2
2
  import { TouchableOpacity, ViewStyle } from "react-native";
3
3
 
4
4
  import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
5
- import {
6
- observeEntryState,
7
- RegisteredActionValue,
8
- } from "@applicaster/zapp-react-native-utils/uiActionsRegistrator";
9
-
10
- import { masterCellLogger } from "../logger";
11
5
 
12
6
  import Image from "./Image";
13
7
  type Props = {
14
8
  item: ZappEntry | ZappFeed;
15
- /**
16
- * Already resolved action value. Pass it when the caller took the action out
17
- * of `uiActionsRegistry` (entry actions have no plugin context to look up),
18
- * and this skips the `useActions` lookup entirely.
19
- */
20
- actionContext?: RegisteredActionValue;
21
9
  flavour?: "flavour_1" | "flavour_2";
22
10
  asset?: {
23
11
  props: {};
@@ -44,19 +32,6 @@ function isStringAsset(asset) {
44
32
  return typeof asset === "string" || Array.isArray(asset);
45
33
  }
46
34
 
47
- /**
48
- * `CellActionEntryState["asset"]` is either something `Image` can take (a URI,
49
- * or a per-flavour array) or a component to render. A plain object is neither -
50
- * a locale/state map reaches us that way - and handing one to JSX throws
51
- * "Element type is invalid", so it has to be recognised rather than assumed.
52
- */
53
- function isComponentAsset(asset): boolean {
54
- return (
55
- typeof asset === "function" ||
56
- (typeof asset === "object" && asset !== null && "$$typeof" in asset)
57
- );
58
- }
59
-
60
35
  function getAssetValue(asset, flavour, fallbackAsset = null) {
61
36
  if (!asset) {
62
37
  return null;
@@ -80,52 +55,26 @@ function getAssetValue(asset, flavour, fallbackAsset = null) {
80
55
  export const ActionButton = React.memo(function ActionButtonComponent(
81
56
  props: Props
82
57
  ) {
83
- const {
84
- item,
85
- action,
86
- asset,
87
- flavour = "flavour_1",
88
- cellUUID,
89
- actionContext: providedActionContext,
90
- } = props;
91
-
92
- // Passing `undefined` makes `useActions` a no-op lookup instead of warning
93
- // about a plugin that was never meant to be there. Callers are expected to
94
- // either always or never provide the context for a given mounted button -
95
- // `useActions` already branches on plugin presence internally, so a value
96
- // that appears mid-life would change the hook count either way.
97
- const lookedUpActionContext = useActions(
98
- providedActionContext ? undefined : action?.identifier
99
- );
100
-
101
- const actionContext = providedActionContext ?? lookedUpActionContext;
58
+ const { item, action, asset, flavour = "flavour_1", cellUUID } = props;
59
+ const actionContext = useActions(action?.identifier);
102
60
 
103
61
  // TODO: add subscription API for action availability
104
62
  const actionDisabled =
105
63
  typeof actionContext?.isActionAvailable === "function" &&
106
64
  !actionContext.isActionAvailable(item);
107
65
 
108
- const [actionState, setActionState] = React.useState(() =>
109
- actionDisabled ? null : actionContext?.initialEntryState?.(item)
66
+ // Note: in theory initialization is not needed anymore, we are using useEffect
67
+ const [actionState, setActionState] = React.useState(
68
+ actionDisabled || !actionContext
69
+ ? null
70
+ : actionContext.initialEntryState(item)
110
71
  );
111
72
 
112
- // `observeEntryState` is the one way to follow an action's state: it uses the
113
- // action's own stream when it has one, and otherwise builds an equivalent one
114
- // from `initialEntryState` + `addListener`. Reading only the latter pair
115
- // leaves stream-backed actions - playback speed, sleep timer - frozen at the
116
- // value they had when the button mounted.
117
73
  useEffect(() => {
118
- if (actionDisabled || !actionContext) {
119
- return undefined;
74
+ if (!((actionDisabled || !actionContext) && actionState !== null)) {
75
+ setActionState(actionContext.initialEntryState(item));
120
76
  }
121
-
122
- const subscription = observeEntryState(actionContext, item).subscribe(
123
- setActionState
124
- );
125
-
126
- return () => subscription.unsubscribe();
127
- // eslint-disable-next-line @wogns3623/better-exhaustive-deps/exhaustive-deps
128
- }, [actionDisabled, item?.id, actionContext]);
77
+ }, [actionDisabled, item?.id, actionContext, action, setActionState]);
129
78
 
130
79
  const onPress = useCallback(() => {
131
80
  actionContext.invokeAction(item, {
@@ -133,30 +82,15 @@ export const ActionButton = React.memo(function ActionButtonComponent(
133
82
  setActionState(state);
134
83
  },
135
84
  });
136
- }, [actionContext, item]);
137
-
138
- // An asset that is present but of a shape nothing can render (a locale or
139
- // state map, say) drops the button for good - unlike a missing asset, which
140
- // is usually just state that has not loaded yet. Reported from an effect so
141
- // it is said once per action/entry rather than on every render.
142
- const unrenderableAsset =
143
- Boolean(actionState?.asset) &&
144
- !isStringAsset(actionState?.asset) &&
145
- !isComponentAsset(actionState?.asset);
85
+ }, [actionState, actionContext?.state, item?.id]);
146
86
 
147
87
  useEffect(() => {
148
- if (unrenderableAsset) {
149
- masterCellLogger.warning({
150
- message: `ActionButton: the asset of action "${action?.identifier}" is neither an image source nor a component - the button is not rendered`,
151
- data: {
152
- identifier: action?.identifier,
153
- entryId: item?.id,
154
- assetType: typeof actionState?.asset,
155
- },
88
+ if (typeof actionContext?.addListener === "function") {
89
+ return actionContext?.addListener(String(item.id), (state) => {
90
+ setActionState(state);
156
91
  });
157
92
  }
158
- // eslint-disable-next-line @wogns3623/better-exhaustive-deps/exhaustive-deps
159
- }, [unrenderableAsset, action?.identifier, item?.id]);
93
+ }, [item?.id, setActionState, actionContext]);
160
94
 
161
95
  if (actionDisabled || !actionContext) return null;
162
96
  const AssetComponent = actionState?.asset;
@@ -164,22 +98,12 @@ export const ActionButton = React.memo(function ActionButtonComponent(
164
98
  // Default state is not yet ready
165
99
  if (!AssetComponent) return null;
166
100
 
167
- // Neither an image source nor a component - nothing renderable.
168
- if (!isStringAsset(AssetComponent) && !isComponentAsset(AssetComponent)) {
169
- return null;
170
- }
171
-
172
101
  return (
173
102
  <TouchableOpacity
174
103
  activeOpacity={1}
175
104
  onPress={onPress}
176
105
  testID={props?.testID || `${item?.id}`}
177
- accessibilityRole="button"
178
- accessibilityLabel={
179
- props?.accessibilityLabel ||
180
- (typeof actionState?.label === "string" ? actionState.label : null) ||
181
- `${item?.id}`
182
- }
106
+ accessibilityLabel={props?.accessibilityLabel || `${item?.id}`}
183
107
  accessibilityHint={props?.accessibilityHint}
184
108
  accessible={!!(props?.testID || props?.accessibilityLabel)}
185
109
  style={props?.style}
@@ -13,8 +13,6 @@ import {
13
13
  } from "@applicaster/zapp-react-native-utils/reactHooks/screen";
14
14
  import { RegisteredAction } from "@applicaster/zapp-react-native-utils/uiActionsRegistrator";
15
15
 
16
- import { ZappPipesEntryContext } from "../Contexts";
17
-
18
16
  /**
19
17
  * Expands `entry.extensions.entry_action` into invokable actions, against the
20
18
  * screen this hook is called from.
@@ -33,12 +31,9 @@ export function useEntryActionsExpander(): (
33
31
  ) => RegisteredAction[] {
34
32
  const { pathname } = useRoute();
35
33
 
36
- const { context: screenEntryContext } = React.useContext(
37
- ZappPipesEntryContext.Context
38
- );
39
-
34
+ const screenContext = useScreenContext();
40
35
  const screenData = useCurrentScreenData();
41
- const screenState = useScreenContext()?.options;
36
+ const screenState = screenContext?.options;
42
37
  const screenStateStore = useScreenStateStore();
43
38
  const actionExecutor = React.useContext(ActionExecutorContext);
44
39
 
@@ -54,7 +49,10 @@ export function useEntryActionsExpander(): (
54
49
  screenState,
55
50
  screenRoute: pathname,
56
51
  screenStateStore,
57
- screenEntry: screenEntryContext?.data,
52
+ // From the screen context, the way `useCellClick` builds the same bag.
53
+ // The ZappPipes entry context is a route-keyed map, so reading `.data`
54
+ // off it was always undefined.
55
+ screenEntry: screenContext?.entry,
58
56
  },
59
57
  };
60
58
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-ui-components",
3
- "version": "16.0.0-rc.72",
3
+ "version": "16.0.0-rc.73",
4
4
  "description": "Applicaster Zapp React Native ui components for the Quick Brick App",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -28,10 +28,10 @@
28
28
  },
29
29
  "homepage": "https://github.com/applicaster/quickbrick#readme",
30
30
  "dependencies": {
31
- "@applicaster/applicaster-types": "16.0.0-rc.72",
32
- "@applicaster/zapp-react-native-bridge": "16.0.0-rc.72",
33
- "@applicaster/zapp-react-native-redux": "16.0.0-rc.72",
34
- "@applicaster/zapp-react-native-utils": "16.0.0-rc.72",
31
+ "@applicaster/applicaster-types": "16.0.0-rc.73",
32
+ "@applicaster/zapp-react-native-bridge": "16.0.0-rc.73",
33
+ "@applicaster/zapp-react-native-redux": "16.0.0-rc.73",
34
+ "@applicaster/zapp-react-native-utils": "16.0.0-rc.73",
35
35
  "fast-json-stable-stringify": "^2.1.0",
36
36
  "promise": "^8.3.0",
37
37
  "react-native-sortables": "1.7.1",