@applicaster/zapp-react-native-utils 14.0.0-alpha.8419134002 → 14.0.0-alpha.8583696651

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.
@@ -176,18 +176,30 @@ class FocusManager {
176
176
  }
177
177
  }
178
178
 
179
- registerFocusable(
180
- component: FocusManager.TouchableReactRef,
181
- parentFocusable: FocusManager.TouchableReactRef,
182
- isFocusableCell: boolean
183
- ) {
184
- const focusableId = getFocusableId(component);
179
+ registerFocusable({
180
+ touchableRef,
181
+ parentFocusableRef,
182
+ isFocusableCell,
183
+ parentFocusableId,
184
+ }: {
185
+ touchableRef: FocusManager.TouchableReactRef;
186
+ parentFocusableRef: FocusManager.TouchableReactRef;
187
+ isFocusableCell: boolean;
188
+ parentFocusableId: string;
189
+ }) {
190
+ const focusableId = getFocusableId(touchableRef);
191
+
185
192
  const focusableComponent = FocusManager.findFocusable(focusableId);
186
193
 
187
- if (!focusableComponent && component) {
188
- this.focusableComponents.push(component);
194
+ if (!focusableComponent && touchableRef) {
195
+ this.focusableComponents.push(touchableRef);
189
196
 
190
- this.tree.add(component, parentFocusable, isFocusableCell);
197
+ this.tree.add(
198
+ touchableRef,
199
+ parentFocusableRef,
200
+ isFocusableCell,
201
+ parentFocusableId
202
+ );
191
203
  } else {
192
204
  logger.warning("Focusable component already registered", {
193
205
  id: focusableId,
@@ -267,7 +279,7 @@ class FocusManager {
267
279
 
268
280
  if (nextFocus) {
269
281
  // HACK: hack to fix the hack below
270
- // HACK: putting call to the end of the event loop so the next component has a chane to be registered
282
+ // HACK: putting call to the end of the event loop so the next component has a chance to be registered
271
283
  setTimeout(() => {
272
284
  FocusManager.instance.setFocus(nextFocus, {
273
285
  direction: "down",
@@ -8,37 +8,41 @@ export class Tree {
8
8
  this.tree = focusManagerTree;
9
9
  }
10
10
 
11
- add(component, parentFocusable, isFocusableCell) {
12
- const focusableId = getFocusableId(component);
13
- const parentId = getFocusableId(parentFocusable);
11
+ add(
12
+ touchableRef: FocusManager.TouchableReactRef,
13
+ parentFocusableRef: FocusManager.TouchableReactRef,
14
+ isFocusableCell: boolean,
15
+ parentFocusableId: string
16
+ ) {
17
+ const focusableId = getFocusableId(touchableRef);
18
+ const parentId = getFocusableId(parentFocusableRef) || parentFocusableId;
14
19
  const focusableComponentInTree = this.find(focusableId);
15
20
 
16
21
  // update node if it already exists
17
22
  if (focusableComponentInTree) {
18
- focusableComponentInTree.updateNode(component);
23
+ focusableComponentInTree.updateNode(touchableRef);
19
24
  }
20
25
 
21
- if (parentFocusable?.current) {
22
- if (!this.find(parentId)) {
23
- this.tree.push(new TreeNode(null, parentId, null, isFocusableCell));
24
- }
26
+ if (!this.find(parentId)) {
27
+ // create temporary node to the root of the tree
28
+ this.tree.push(new TreeNode(null, parentId, null, isFocusableCell));
29
+ }
25
30
 
26
- const parentNode = this.find(parentId);
31
+ const parentNode = this.find(parentId);
27
32
 
28
- if (parentNode) {
29
- if (focusableComponentInTree) {
30
- focusableComponentInTree.isFocusableCell = isFocusableCell;
31
- focusableComponentInTree.parentId = parentNode.id;
33
+ if (parentNode) {
34
+ if (focusableComponentInTree) {
35
+ focusableComponentInTree.isFocusableCell = isFocusableCell;
36
+ focusableComponentInTree.parentId = parentNode.id;
32
37
 
33
- parentNode.addChild(focusableComponentInTree);
38
+ parentNode.addChild(focusableComponentInTree);
34
39
 
35
- // remove root object from the list
36
- this.tree = this.tree.filter(
37
- (node) => node !== focusableComponentInTree
38
- );
39
- } else {
40
- parentNode.addChild(component, focusableId, isFocusableCell);
41
- }
40
+ // remove root object from the list
41
+ this.tree = this.tree.filter(
42
+ (node) => node !== focusableComponentInTree
43
+ );
44
+ } else {
45
+ parentNode.addChild(touchableRef, focusableId, isFocusableCell);
42
46
  }
43
47
  }
44
48
  }
@@ -1,5 +1,8 @@
1
1
  import { focusManager } from "../FocusManager";
2
2
 
3
+ const isFocusableCell = true;
4
+ const parentFocusableId = "parentFocusableId";
5
+
3
6
  const group = {
4
7
  current: {
5
8
  props: {
@@ -62,13 +65,47 @@ jest.useFakeTimers();
62
65
 
63
66
  describe("FocusManager", () => {
64
67
  beforeAll(() => {
65
- focusManager.registerFocusable(group, { current: null });
66
- focusManager.registerFocusable(child1, group);
67
- focusManager.registerFocusable(child2, group);
68
- focusManager.registerFocusable(child3, child2);
69
-
70
- focusManager.registerFocusable(child4, child2);
71
- focusManager.registerFocusable(child5, child2);
68
+ focusManager.registerFocusable({
69
+ touchableRef: group,
70
+ parentFocusableRef: { current: null },
71
+ isFocusableCell,
72
+ parentFocusableId,
73
+ });
74
+
75
+ focusManager.registerFocusable({
76
+ touchableRef: child1,
77
+ parentFocusableRef: group,
78
+ isFocusableCell,
79
+ parentFocusableId,
80
+ });
81
+
82
+ focusManager.registerFocusable({
83
+ touchableRef: child2,
84
+ parentFocusableRef: group,
85
+ isFocusableCell,
86
+ parentFocusableId,
87
+ });
88
+
89
+ focusManager.registerFocusable({
90
+ touchableRef: child3,
91
+ parentFocusableRef: child2,
92
+ isFocusableCell,
93
+ parentFocusableId,
94
+ });
95
+
96
+ focusManager.registerFocusable({
97
+ touchableRef: child4,
98
+ parentFocusableRef: child2,
99
+ isFocusableCell,
100
+ parentFocusableId,
101
+ });
102
+
103
+ focusManager.registerFocusable({
104
+ touchableRef: child5,
105
+ parentFocusableRef: child2,
106
+ isFocusableCell,
107
+ parentFocusableId,
108
+ });
72
109
  });
73
110
 
74
111
  it("focusManager should be defined", () => {
@@ -199,7 +236,12 @@ describe("FocusManager", () => {
199
236
  });
200
237
 
201
238
  it("focusManager registerFocusable should register", () => {
202
- focusManager.registerFocusable(child5, child2);
239
+ focusManager.registerFocusable({
240
+ touchableRef: child5,
241
+ parentFocusableRef: child2,
242
+ isFocusableCell,
243
+ parentFocusableId,
244
+ });
203
245
 
204
246
  expect(
205
247
  focusManager.isFocusableChildOf(child5.current.props.id, child2)
package/index.d.ts CHANGED
@@ -137,12 +137,3 @@ declare type AccessibilityState = {
137
137
  reduceMotionEnabled: boolean;
138
138
  boldTextEnabled: boolean;
139
139
  };
140
-
141
- declare type AccessibilityProps = {
142
- accessibilityLabel?: string;
143
- accessibilityHint?: string;
144
- "aria-label"?: string;
145
- "aria-description"?: string;
146
- accessibilityRole?: string;
147
- "aria-role"?: string;
148
- };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-utils",
3
- "version": "14.0.0-alpha.8419134002",
3
+ "version": "14.0.0-alpha.8583696651",
4
4
  "description": "Applicaster Zapp React Native utilities package",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "homepage": "https://github.com/applicaster/quickbrick#readme",
29
29
  "dependencies": {
30
- "@applicaster/applicaster-types": "14.0.0-alpha.8419134002",
30
+ "@applicaster/applicaster-types": "14.0.0-alpha.8583696651",
31
31
  "buffer": "^5.2.1",
32
32
  "camelize": "^1.0.0",
33
33
  "dayjs": "^1.11.10",
@@ -5,6 +5,7 @@ import { isFilledArray } from "@applicaster/zapp-react-native-utils/arrayUtils";
5
5
  import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
6
6
 
7
7
  import { getBoolFromConfigValue } from "../configurationUtils";
8
+ import { Dimensions } from "react-native";
8
9
 
9
10
  export { getPlayerActionButtons } from "./getPlayerActionButtons";
10
11
 
@@ -97,3 +98,53 @@ export const isAudioItem = (item: Option<ZappEntry>) => {
97
98
  export const isInlineTV = (screenData) => {
98
99
  return isTV() && isFilledArray(screenData?.ui_components);
99
100
  };
101
+
102
+ const isPercentage = (value: string | number): boolean => {
103
+ if (typeof value === "string") {
104
+ return value.includes("%");
105
+ }
106
+
107
+ return false;
108
+ };
109
+
110
+ const getPercentageOf = (percent: string, value: number) => {
111
+ const percentageValue = parseFloat(percent.replace("%", ""));
112
+
113
+ if (isNaN(percentageValue)) {
114
+ return value;
115
+ }
116
+
117
+ return (value * percentageValue) / 100;
118
+ };
119
+
120
+ type DimensionsT = {
121
+ width: number | string;
122
+ height: number | string | undefined;
123
+ aspectRatio?: number;
124
+ };
125
+
126
+ export const getTabletWidth = (
127
+ tablet_landscape_sidebar_width,
128
+ dimensions: DimensionsT
129
+ ) => {
130
+ const { width: SCREEN_WIDTH } = Dimensions.get("screen");
131
+
132
+ const { width } = dimensions;
133
+ let widthValue = Number(width);
134
+
135
+ if (isPercentage(width)) {
136
+ widthValue = getPercentageOf(width.toString(), SCREEN_WIDTH);
137
+ }
138
+
139
+ const sidebarWidth = Number(tablet_landscape_sidebar_width?.replace("%", ""));
140
+
141
+ if (tablet_landscape_sidebar_width?.includes("%")) {
142
+ return widthValue * (1 - sidebarWidth / 100);
143
+ }
144
+
145
+ if (Number.isNaN(sidebarWidth)) {
146
+ return widthValue * 0.65;
147
+ }
148
+
149
+ return widthValue - sidebarWidth;
150
+ };
@@ -1,25 +1,27 @@
1
1
  import React from "react";
2
2
 
3
- import { act, renderHook } from "@testing-library/react-hooks";
3
+ import { renderHook } from "@testing-library/react-hooks";
4
+ import { act, waitFor } from "@testing-library/react-native";
4
5
  import { Provider } from "react-redux";
5
6
  import configureStore from "redux-mock-store";
7
+ import { useTrackedView } from "../useTrackedView";
6
8
 
7
9
  const mockUpdateComponentsPositions = jest.fn();
8
10
 
9
11
  jest.mock(
10
12
  "@applicaster/zapp-react-native-ui-components/Contexts/ScreenTrackedViewPositionsContext",
11
13
  () => ({
12
- useScreenTrackedViewPositionsContext: jest.fn().mockReturnValue({
14
+ useScreenTrackedViewPositionsContext: jest.fn(() => ({
13
15
  updateComponentsPositions: mockUpdateComponentsPositions,
14
16
  value: {
15
17
  "123": { componentId: "123", centerX: 0.4, centerY: 0.5 },
16
18
  "124": { componentId: "124", centerX: 0.2, centerY: 0.3 },
17
19
  },
18
- }),
20
+ })),
19
21
  })
20
22
  );
21
23
 
22
- jest.useFakeTimers({ legacyFakeTimers: true });
24
+ jest.useFakeTimers();
23
25
 
24
26
  jest.mock("@applicaster/zapp-react-native-utils/reactHooks/navigation");
25
27
 
@@ -32,10 +34,8 @@ const Wrapper = ({ children }: { children: React.ReactChild }) => (
32
34
  <Provider store={store}>{children}</Provider>
33
35
  );
34
36
 
35
- const { useTrackedView } = require("../useTrackedView");
36
-
37
37
  describe("useTrackCurrentAutoScrollingElement", () => {
38
- it("should update position for selected component - onViewportEnter", () => {
38
+ it("should update position for selected component - onViewportEnter", async () => {
39
39
  const { result } = renderHook(() => useTrackedView("123"), {
40
40
  wrapper: Wrapper,
41
41
  });
@@ -46,14 +46,13 @@ describe("useTrackCurrentAutoScrollingElement", () => {
46
46
  rect: { left: 1, right: 1, top: 1, bottom: 1 },
47
47
  };
48
48
 
49
- act(async () => {
50
- await result.current.onPositionUpdated(mockRect);
49
+ act(() => {
50
+ result.current.onPositionUpdated(mockRect);
51
51
  });
52
52
 
53
- // Fast-forward until all timers have been executed
54
- jest.runAllTimers();
55
-
56
- expect(result.current.inViewPort).toBe(true);
53
+ await waitFor(() => {
54
+ expect(result.current.inViewPort).toBe(true);
55
+ });
57
56
 
58
57
  expect(mockUpdateComponentsPositions).toHaveBeenCalledWith(
59
58
  "123",
@@ -1,8 +1,7 @@
1
1
  import { renderHook } from "@testing-library/react-hooks";
2
- import * as reduxMockStore from "redux-mock-store";
3
- import thunk from "redux-thunk";
4
- import React from "react";
5
- import * as ReactRedux from "react-redux";
2
+ import { allFeedsIsReady, useBatchLoading } from "../useBatchLoading";
3
+ import { WrappedWithProviders } from "@applicaster/zapp-react-native-utils/testUtils";
4
+ import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
6
5
 
7
6
  jest.mock("../../navigation");
8
7
 
@@ -13,17 +12,18 @@ jest.mock(
13
12
  })
14
13
  );
15
14
 
16
- const useBatchLoading = require("../useBatchLoading").useBatchLoading;
17
- const allFeedsIsReady = require("../useBatchLoading").allFeedsIsReady;
18
-
19
- const mockStore = reduxMockStore.default([thunk]);
20
-
21
- const wrapper: React.FC<any> = ({ children, store }) => (
22
- <ReactRedux.Provider store={store}>{children}</ReactRedux.Provider>
23
- );
15
+ const wrapper = WrappedWithProviders;
24
16
 
25
17
  describe("useBatchLoading", () => {
26
- const useDispatchSpy = jest.spyOn(ReactRedux, "useDispatch");
18
+ const data = [
19
+ { data: { source: "url1" }, component_type: "any" },
20
+ { data: { source: "url2" }, component_type: "any" },
21
+ { data: { source: "url3" }, component_type: "any" },
22
+ { data: { source: "url4" }, component_type: "any" },
23
+ { data: { source: "url5" }, component_type: "any" },
24
+ { data: { source: "url6" }, component_type: "any" },
25
+ // ... more items
26
+ ];
27
27
 
28
28
  beforeAll(() => {
29
29
  jest.useFakeTimers();
@@ -34,7 +34,7 @@ describe("useBatchLoading", () => {
34
34
  });
35
35
 
36
36
  it("loadPipesData start loading not started requests", () => {
37
- const store = mockStore({
37
+ const store = {
38
38
  zappPipes: {
39
39
  url1: {
40
40
  loading: true,
@@ -53,29 +53,17 @@ describe("useBatchLoading", () => {
53
53
  },
54
54
  },
55
55
  test: "true",
56
- });
57
-
58
- useDispatchSpy.mockReturnValue(store.dispatch);
56
+ };
59
57
 
60
58
  const initialBatchSize = 3;
61
59
  const riverId = "123";
62
60
 
63
- const data = [
64
- { data: { source: "url1" } },
65
- { data: { source: "url2" } },
66
- { data: { source: "url3" } },
67
- { data: { source: "url4" } },
68
- { data: { source: "url5" } },
69
- { data: { source: "url6" } },
70
- // ... more items
71
- ];
72
-
73
61
  renderHook(() => useBatchLoading(data, { initialBatchSize, riverId }), {
74
62
  wrapper,
75
63
  initialProps: { store },
76
64
  });
77
65
 
78
- const actions = store.getActions();
66
+ const actions = (appStore.getStore() as any).getActions();
79
67
 
80
68
  expect(actions).toHaveLength(2);
81
69
 
@@ -91,7 +79,7 @@ describe("useBatchLoading", () => {
91
79
  });
92
80
 
93
81
  it("loadPipesData start loading new feed when 1 feed is done loading and 1 is in loading state", () => {
94
- const store = mockStore({
82
+ const store = {
95
83
  zappPipes: {
96
84
  url1: {
97
85
  loading: false,
@@ -110,31 +98,17 @@ describe("useBatchLoading", () => {
110
98
  },
111
99
  },
112
100
  test: "true",
113
- });
114
-
115
- useDispatchSpy.mockReturnValue(store.dispatch);
101
+ };
116
102
 
117
103
  const initialBatchSize = 3;
118
104
  const riverId = "123";
119
105
 
120
- const data = [
121
- { data: { source: "url1" } },
122
- { data: { source: "url2" } },
123
- { data: { source: "url3" } },
124
- { data: { source: "url4" } },
125
- { data: { source: "url5" } },
126
- { data: { source: "url6" } },
127
- // ... more items
128
- ];
129
-
130
- expect(useDispatchSpy).toBeCalledTimes(0);
131
-
132
106
  renderHook(() => useBatchLoading(data, { initialBatchSize, riverId }), {
133
107
  wrapper,
134
108
  initialProps: { store },
135
109
  });
136
110
 
137
- const actions = store.getActions();
111
+ const actions = (appStore.getStore() as any).getActions();
138
112
 
139
113
  expect(actions).toHaveLength(1);
140
114
 
@@ -145,38 +119,26 @@ describe("useBatchLoading", () => {
145
119
  });
146
120
 
147
121
  it("loadPipesData has been called when no data cached", () => {
148
- const store = mockStore({
122
+ const store = {
149
123
  zappPipes: {},
150
124
  test: "true",
151
- });
152
-
153
- useDispatchSpy.mockReturnValue(store.dispatch);
125
+ };
154
126
 
155
127
  const initialBatchSize = 3;
156
128
  const riverId = "123";
157
129
 
158
- const data = [
159
- { data: { source: "url1" } },
160
- { data: { source: "url2" } },
161
- { data: { source: "url3" } },
162
- { data: { source: "url4" } },
163
- { data: { source: "url5" } },
164
- { data: { source: "url6" } },
165
- // ... more items
166
- ];
167
-
168
130
  renderHook(() => useBatchLoading(data, { initialBatchSize, riverId }), {
169
131
  wrapper,
170
132
  initialProps: { store },
171
133
  });
172
134
 
173
- const actions = store.getActions();
135
+ const actions = (appStore.getStore() as any).getActions();
174
136
 
175
137
  expect(actions).toHaveLength(3);
176
138
  });
177
139
 
178
140
  it("initial batch ready when all initial items loaded", () => {
179
- const store = mockStore({
141
+ const store = {
180
142
  zappPipes: {
181
143
  url1: {
182
144
  loading: false,
@@ -194,19 +156,11 @@ describe("useBatchLoading", () => {
194
156
  data: {},
195
157
  },
196
158
  },
197
- });
198
-
199
- useDispatchSpy.mockReturnValue(store.dispatch);
159
+ };
200
160
 
201
161
  const initialBatchSize = 3;
202
162
  const riverId = "123";
203
163
 
204
- const data: Partial<ZappUIComponent>[] = [
205
- { data: { source: "url1" } },
206
- { data: { source: "url2" } },
207
- { data: { source: "url3" } },
208
- ];
209
-
210
164
  const { result } = renderHook(
211
165
  () => useBatchLoading(data, { initialBatchSize, riverId }),
212
166
  { wrapper, initialProps: { store } }
@@ -216,12 +170,10 @@ describe("useBatchLoading", () => {
216
170
  });
217
171
 
218
172
  it("gallery-qb: loadPipesData should be called only once for first component in the gallery", () => {
219
- const store = mockStore({
173
+ const store = {
220
174
  zappPipes: {},
221
175
  test: "true",
222
- });
223
-
224
- useDispatchSpy.mockReturnValue(store.dispatch);
176
+ };
225
177
 
226
178
  const initialBatchSize = 3;
227
179
  const riverId = "123";
@@ -231,11 +183,11 @@ describe("useBatchLoading", () => {
231
183
  component_type: "gallery-qb",
232
184
  ui_components: [{ data: { source: "url1" } }],
233
185
  },
234
- { data: { source: "url2" } },
235
- { data: { source: "url3" } },
236
- { data: { source: "url4" } },
237
- { data: { source: "url5" } },
238
- { data: { source: "url6" } },
186
+ { data: { source: "url2" }, component_type: "any" },
187
+ { data: { source: "url3" }, component_type: "any" },
188
+ { data: { source: "url4" }, component_type: "any" },
189
+ { data: { source: "url5" }, component_type: "any" },
190
+ { data: { source: "url6" }, component_type: "any" },
239
191
  // ... more items
240
192
  ];
241
193
 
@@ -244,13 +196,13 @@ describe("useBatchLoading", () => {
244
196
  initialProps: { store },
245
197
  });
246
198
 
247
- const actions = store.getActions();
199
+ const actions = (appStore.getStore() as any).getActions();
248
200
 
249
201
  expect(actions).toHaveLength(1);
250
202
  });
251
203
 
252
204
  it("gallery-qb: initial batch ready when all initial items loaded", () => {
253
- const store = mockStore({
205
+ const store = {
254
206
  zappPipes: {
255
207
  url1: {
256
208
  loading: false,
@@ -258,20 +210,19 @@ describe("useBatchLoading", () => {
258
210
  data: {},
259
211
  },
260
212
  },
261
- });
262
-
263
- useDispatchSpy.mockReturnValue(store.dispatch);
213
+ };
264
214
 
265
215
  const initialBatchSize = 3;
266
216
  const riverId = "123";
267
217
 
268
- const data: Partial<ZappUIComponent>[] = [
218
+ const data = [
269
219
  {
270
220
  component_type: "gallery-qb",
271
- ui_components: [{ data: { source: "url1" } }],
221
+ data: {},
222
+ ui_components: [{ data: { source: "url1" } }] as any,
272
223
  },
273
- { data: { source: "url2" } },
274
- { data: { source: "url3" } },
224
+ { data: { source: "url2" }, component_type: "any" },
225
+ { data: { source: "url3" }, component_type: "any" },
275
226
  ];
276
227
 
277
228
  const { result } = renderHook(
@@ -1,21 +1,28 @@
1
1
  import { NativeModules, Platform } from "react-native";
2
2
 
3
+ export const isAndroidTablet = () => {
4
+ const { initialProps } = NativeModules.QuickBrickCommunicationModule;
5
+
6
+ return initialProps?.is_tablet;
7
+ };
8
+
3
9
  /**
4
10
  * Determines wether the given device is a tablet based on dimensions, orientation, and platform
5
11
  * @param {Object} dimensions - Dimensions object passed to the function
6
12
  * @param {String} orientation - Orientation enum passed to the function
7
13
  * @returns {Boolean} isTablet - returns whether the given device is a tablet
8
14
  */
9
- export const isTablet = (dimensions, orientation) => {
15
+ export const isTablet = (
16
+ dimensions?: { width: number; height: number },
17
+ orientation?: string
18
+ ) => {
10
19
  if (Platform?.OS === "ios") {
11
20
  return Platform?.isPad;
12
21
  } else if (Platform?.OS === "android") {
13
- const { initialProps } = NativeModules.QuickBrickCommunicationModule;
14
-
15
- return initialProps?.is_tablet;
22
+ return isAndroidTablet();
16
23
  }
17
24
 
18
- const { width } = dimensions;
25
+ const { width } = dimensions || {};
19
26
 
20
27
  if (width < 600) return false;
21
28
  if (width >= 600 && width < 840) return orientation === "portrait";
@@ -14,7 +14,7 @@ import { HOOKS_EVENTS } from "../../appUtils/HooksManager/constants";
14
14
  import { getRiverFromRoute, getTargetRoute } from "../../navigationUtils";
15
15
  import { useConnectionInfo } from "../connection";
16
16
 
17
- import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
17
+ import { isTV, isWeb } from "@applicaster/zapp-react-native-utils/reactUtils";
18
18
  import { useNavbarState } from "../screen";
19
19
 
20
20
  export { useNavigation } from "./useNavigation";
@@ -127,11 +127,13 @@ export function isNavBarVisible(
127
127
 
128
128
  export const useBackHandler = (cb: () => boolean) => {
129
129
  useEffect(() => {
130
- BackHandler.addEventListener("hardwareBackPress", cb);
130
+ if (!isWeb()) {
131
+ const unsubscribe = BackHandler.addEventListener("hardwareBackPress", cb);
131
132
 
132
- return () => {
133
- BackHandler.removeEventListener("hardwareBackPress", cb);
134
- };
133
+ return () => {
134
+ unsubscribe.remove();
135
+ };
136
+ }
135
137
  }, [cb]);
136
138
  };
137
139