@applicaster/zapp-react-native-utils 13.0.0-alpha.4528460103 → 13.0.0-alpha.4574923605

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.
@@ -16,12 +16,12 @@ import {
16
16
 
17
17
  export { StorageLevel };
18
18
 
19
- type KeyNameObj = {
19
+ export type KeyNameObj = {
20
20
  namespace: string;
21
21
  key: string;
22
22
  };
23
23
 
24
- type KeyName = string | KeyNameObj;
24
+ export type KeyName = string | KeyNameObj;
25
25
 
26
26
  type ParsedKeyAndNamespace = {
27
27
  namespace?: string;
@@ -1,10 +1,11 @@
1
1
  import * as R from "ramda";
2
2
  import { DEFAULT_NAMESPACE } from "../consts";
3
+ import { KeyName, KeyNameObj } from "../index";
3
4
 
4
5
  const lastDotRegex = /\.([^.]+)$/;
5
6
  const splitByLastDot = R.compose(R.init, R.split(lastDotRegex));
6
7
 
7
- export function getNamespaceAndKey(namespacedKey) {
8
+ export function getNamespaceAndKey(namespacedKey: KeyName): KeyNameObj {
8
9
  if (typeof namespacedKey !== "string") return namespacedKey;
9
10
 
10
11
  const namespacedKeyProps = namespacedKey.includes(".")
@@ -33,6 +33,7 @@ exports[`focusManager should be defined 1`] = `
33
33
  "recoverFocus": [Function],
34
34
  "register": [Function],
35
35
  "removeHandler": [Function],
36
+ "resetFocus": [Function],
36
37
  "setFocus": [Function],
37
38
  "setInitialFocus": [Function],
38
39
  "unregister": [Function],
@@ -1,5 +1,6 @@
1
1
  import { focusManager } from "../index.ts";
2
2
  import { focusManager as focusManagerIOS } from "../index.ios.ts";
3
+ import { findFirstFocusableItem } from "../utils";
3
4
 
4
5
  describe("focusManagerIOS", () => {
5
6
  it("should be defined", () => {
@@ -8,6 +9,67 @@ describe("focusManagerIOS", () => {
8
9
  });
9
10
  });
10
11
 
12
+ const focusableTree = {
13
+ root: {
14
+ children: [
15
+ {
16
+ children: [
17
+ {
18
+ children: [
19
+ {
20
+ children: [],
21
+ component: {
22
+ isGroup: true,
23
+ },
24
+ id: "route/1/1",
25
+ },
26
+ {
27
+ children: [],
28
+ component: {
29
+ isGroup: true,
30
+ },
31
+ id: "route/1/2",
32
+ },
33
+ ],
34
+ component: {
35
+ isGroup: true,
36
+ },
37
+ id: "route/1",
38
+ },
39
+ {
40
+ children: [
41
+ {
42
+ children: [],
43
+ component: {
44
+ isGroup: true,
45
+ },
46
+ id: "route/2/1",
47
+ },
48
+ {
49
+ children: [],
50
+ component: {
51
+ isGroup: false,
52
+ },
53
+ id: "route/2/2",
54
+ },
55
+ ],
56
+ component: {
57
+ isGroup: true,
58
+ },
59
+ id: "route/2",
60
+ },
61
+ ],
62
+ component: {
63
+ isGroup: true,
64
+ },
65
+ id: "/river/1234",
66
+ },
67
+ ],
68
+ component: null,
69
+ id: "root",
70
+ },
71
+ };
72
+
11
73
  describe("focusManager", () => {
12
74
  it("should be defined", () => {
13
75
  expect(focusManager).toBeDefined();
@@ -45,4 +107,24 @@ describe("focusManager", () => {
45
107
  it("placeholder test", () => {
46
108
  expect(true).toBe(true);
47
109
  });
110
+
111
+ it("findFirstFocusableItem returns focusableItem", () => {
112
+ const expectedItemId =
113
+ focusableTree.root.children[0].children[1].children[1].id;
114
+
115
+ const focusableItem = findFirstFocusableItem(focusableTree.root.children);
116
+
117
+ expect(
118
+ focusableItem !== undefined && expectedItemId === focusableItem.id
119
+ ).toBe(true);
120
+ });
121
+
122
+ it("findFirstFocusableItem does not return focusableItem", () => {
123
+ focusableTree.root.children[0].children[1].children[1].component.isGroup =
124
+ true;
125
+
126
+ const focusableItem = findFirstFocusableItem(focusableTree.root.children);
127
+
128
+ expect(focusableItem === undefined).toBe(true);
129
+ });
48
130
  });
@@ -30,6 +30,7 @@ describe("focusManager", () => {
30
30
  "isGroupItemFocused",
31
31
  "recoverFocus",
32
32
  "isCurrentFocusOnTheTopScreen",
33
+ "resetFocus",
33
34
  ];
34
35
 
35
36
  expectedMethods.forEach((method) => {
@@ -13,6 +13,7 @@ import {
13
13
  import { subscriber } from "../../functionUtils";
14
14
  import { coreLogger } from "../../logger";
15
15
  import { ACTION } from "./utils/enums";
16
+ import { findFirstFocusableItem } from "./utils";
16
17
 
17
18
  const logger = coreLogger.addSubsystem("focusManager");
18
19
 
@@ -492,20 +493,29 @@ export const focusManager = (function () {
492
493
  return haveSameParentBeforeRoot(currentFocusNode, R.last(routes));
493
494
  }
494
495
 
495
- function recoverFocus() {
496
+ function resetFocus() {
496
497
  if (!isCurrentFocusOnTheTopScreen()) {
497
- // We've failed to set focused node on the new screen => run focus recovery
498
+ // We've failed to set focused node on the new screen => run focus reset
498
499
  setInitialFocus();
499
500
  }
500
501
  }
501
502
 
503
+ /**
504
+ * sets the focus on first focusable item
505
+ * @public
506
+ */
507
+ function recoverFocus() {
508
+ const focusableItem = findFirstFocusableItem(focusableTree.root.children);
509
+ focusableItem && setFocus(focusableItem.id);
510
+ }
511
+
502
512
  /**
503
513
  * moves the focus in a provided direction
504
514
  * @public
505
515
  * @param {Object} direction of the scroll
506
516
  */
507
517
  function moveFocus(direction) {
508
- if (!isCurrentFocusOnTheTopScreen()) {
518
+ if (!isCurrentFocusOnTheTopScreen() && currentFocusNode) {
509
519
  // We are opening new screen. We need to ignore movement of focus.
510
520
  return false;
511
521
  }
@@ -528,6 +538,8 @@ export const focusManager = (function () {
528
538
  } else {
529
539
  currentFocusNode.component.failedLostFocus(currentFocusNode, direction);
530
540
  }
541
+ } else {
542
+ recoverFocus();
531
543
  }
532
544
  }
533
545
 
@@ -572,5 +584,6 @@ export const focusManager = (function () {
572
584
  isGroupItemFocused,
573
585
  recoverFocus,
574
586
  isCurrentFocusOnTheTopScreen,
587
+ resetFocus,
575
588
  };
576
589
  })();
@@ -98,3 +98,17 @@ export const waitForContent = (focusableTree) => {
98
98
  conditionFn: contentHasAnyChildren,
99
99
  });
100
100
  };
101
+
102
+ export const findFirstFocusableItem = (items) => {
103
+ for (const item of items) {
104
+ if (!item.component.isGroup) {
105
+ return item;
106
+ } else {
107
+ if (item.children) {
108
+ const focusableItem = findFirstFocusableItem(item.children);
109
+
110
+ if (focusableItem) return focusableItem;
111
+ }
112
+ }
113
+ }
114
+ };
@@ -54,7 +54,7 @@ export async function setFocusOnContent(currentRoute: string) {
54
54
  // For doing it we have to wait registration of first focusable node into focusableTree
55
55
  // we wait until content node starts having children
56
56
 
57
- const { setInitialFocus, focusableTree } = focusManager;
57
+ const { setInitialFocus, focusableTree, getCurrentFocus } = focusManager;
58
58
 
59
59
  try {
60
60
  await waitForActiveScreen(currentRoute, focusableTree);
@@ -71,6 +71,6 @@ export async function setFocusOnContent(currentRoute: string) {
71
71
  );
72
72
  }
73
73
  } catch (error) {
74
- setFocusOnContent(currentRoute);
74
+ !getCurrentFocus() && setFocusOnContent(currentRoute);
75
75
  }
76
76
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-utils",
3
- "version": "13.0.0-alpha.4528460103",
3
+ "version": "13.0.0-alpha.4574923605",
4
4
  "description": "Applicaster Zapp React Native utilities package",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -28,7 +28,7 @@
28
28
  },
29
29
  "homepage": "https://github.com/applicaster/quickbrick#readme",
30
30
  "dependencies": {
31
- "@applicaster/applicaster-types": "13.0.0-alpha.4528460103",
31
+ "@applicaster/applicaster-types": "13.0.0-alpha.4574923605",
32
32
  "buffer": "^5.2.1",
33
33
  "camelize": "^1.0.0",
34
34
  "create-react-context": "^0.2.3",
@@ -1,5 +1,5 @@
1
- import React, { useEffect, useMemo } from "react";
2
- import { BackHandler, InteractionManager, NativeModules } from "react-native";
1
+ import { useContext, useEffect, useMemo, useRef } from "react";
2
+ import { BackHandler } from "react-native";
3
3
 
4
4
  import {
5
5
  useContentTypes,
@@ -31,6 +31,8 @@ export { useGetBottomTabBarHeight } from "./useGetBottomTabBarHeight";
31
31
 
32
32
  export { useIsScreenActive } from "./useIsScreenActive";
33
33
 
34
+ export { useProfilerLogging } from "./useProfilerLogging";
35
+
34
36
  export {
35
37
  useUniqueRouteSuffix,
36
38
  useNavbarId,
@@ -121,10 +123,8 @@ export function isNavBarVisible(
121
123
  return true;
122
124
  }
123
125
 
124
- const { ProfilerBridge } = NativeModules;
125
-
126
126
  export const useBackHandler = (cb: () => boolean) => {
127
- React.useEffect(() => {
127
+ useEffect(() => {
128
128
  BackHandler.addEventListener("hardwareBackPress", cb);
129
129
 
130
130
  return () => {
@@ -158,7 +158,7 @@ export const useZappHooksForEntry = (
158
158
  setIsHooksExecutionInProgress,
159
159
  setIsPresentingFullScreen,
160
160
  setIsRunningInBackground,
161
- }: HookModalContextT = React.useContext(ZappHookModalContext.ReactContext);
161
+ }: HookModalContextT = useContext(ZappHookModalContext.ReactContext);
162
162
 
163
163
  const {
164
164
  appData: { layoutVersion },
@@ -256,22 +256,10 @@ export const useZappHooksForEntry = (
256
256
  }, [entry.id]);
257
257
  };
258
258
 
259
- export const useProfilerLogging = () => {
260
- const logTimestamp = (data: string) =>
261
- InteractionManager.runAfterInteractions(() => {
262
- if (ProfilerBridge && data) {
263
- ProfilerBridge.updateTimer("navigation", data, Date.now());
264
- }
265
- });
266
-
267
- return logTimestamp;
268
- };
269
-
270
259
  export const useRunIfLongPress = (
271
260
  options = { timeout: LONG_KEY_PRESS_TIMEOUT }
272
261
  ) => {
273
- const pressTimeout =
274
- React.useRef<Nullable<ReturnType<typeof setTimeout>>>(null);
262
+ const pressTimeout = useRef<Nullable<ReturnType<typeof setTimeout>>>(null);
275
263
 
276
264
  const resetTimeout = () => {
277
265
  clearTimeout(pressTimeout.current as ReturnType<typeof setTimeout>);
@@ -0,0 +1,15 @@
1
+ import { useCallback } from "react";
2
+ import { InteractionManager, NativeModules } from "react-native";
3
+
4
+ const { ProfilerBridge } = NativeModules;
5
+
6
+ export const useProfilerLogging = () =>
7
+ useCallback(
8
+ (data: string) =>
9
+ InteractionManager.runAfterInteractions(() => {
10
+ if (ProfilerBridge && data) {
11
+ ProfilerBridge.updateTimer("navigation", data, Date.now());
12
+ }
13
+ }),
14
+ []
15
+ );