@applicaster/zapp-react-native-utils 13.0.0-rc.8 → 13.0.0-rc.81

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.
Files changed (148) hide show
  1. package/README.md +0 -10
  2. package/actionsExecutor/ActionExecutor.ts +16 -9
  3. package/actionsExecutor/ActionExecutorContext.tsx +74 -9
  4. package/alertUtils/alert.ts +148 -0
  5. package/alertUtils/index.ts +1 -148
  6. package/analyticsUtils/AnalyticPlayerListener.ts +18 -1
  7. package/analyticsUtils/__tests__/__snapshots__/analyticsManagerNoModule.test.js.snap +9 -9
  8. package/analyticsUtils/currentScreenProperities.ts +6 -4
  9. package/analyticsUtils/helpers/hooks.ts +1 -1
  10. package/appUtils/HooksManager/{Hook.js → Hook.ts} +35 -23
  11. package/appUtils/HooksManager/__tests__/__snapshots__/hook.test.js.snap +10 -8
  12. package/appUtils/HooksManager/__tests__/__snapshots__/hooksManager.test.js.snap +693 -0
  13. package/appUtils/HooksManager/__tests__/hook.test.js +196 -184
  14. package/appUtils/HooksManager/__tests__/hooksManager.test.js +96 -99
  15. package/appUtils/HooksManager/{constants.js → constants.ts} +1 -1
  16. package/appUtils/HooksManager/{index.js → index.ts} +122 -113
  17. package/appUtils/HooksManager/types.ts +119 -0
  18. package/appUtils/RiverFocusManager/index.js +17 -12
  19. package/appUtils/accessibilityManager/index.ts +42 -1
  20. package/appUtils/contextKeysManager/contextResolver.ts +77 -0
  21. package/appUtils/contextKeysManager/index.ts +2 -2
  22. package/appUtils/contextKeysManager/utils/index.ts +2 -1
  23. package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +7 -6
  24. package/appUtils/focusManager/index.ts +100 -74
  25. package/appUtils/focusManager/treeDataStructure/Tree/index.js +25 -3
  26. package/appUtils/focusManager/treeDataStructure/Utils/index.js +1 -3
  27. package/appUtils/focusManager/utils/enums.ts +4 -0
  28. package/appUtils/focusManagerAux/utils/index.ts +4 -3
  29. package/appUtils/{index.js → index.ts} +1 -1
  30. package/appUtils/keyCodes/__tests__/__snapshots__/keyCodes.test.js.snap +2 -2
  31. package/appUtils/keyCodes/__tests__/keyCodes.test.js +4 -4
  32. package/appUtils/keyCodes/{index.js → index.ts} +13 -7
  33. package/appUtils/keyCodes/keys/index.ts +3 -0
  34. package/appUtils/keyCodes/{keys.android.js → keys/keyEventMapper.ts} +0 -19
  35. package/appUtils/keyCodes/keys/keys.android.ts +18 -0
  36. package/appUtils/keyCodes/keys/keys.ios.ts +25 -0
  37. package/appUtils/keyCodes/keys/keys.ts +15 -0
  38. package/appUtils/keyCodes/{keysByPlatform.js → keys/keysByPlatform.ts} +4 -0
  39. package/appUtils/keyCodes/keysByPlatform.ts +8 -0
  40. package/appUtils/keyInputHandler/KeyInputHandler.ts +68 -0
  41. package/appUtils/orientationHelper.ts +11 -13
  42. package/appUtils/platform/const.ts +14 -0
  43. package/appUtils/platform/platformUtils.ts +47 -18
  44. package/appUtils/playerManager/index.ts +1 -1
  45. package/appUtils/playerManager/player.ts +42 -18
  46. package/appUtils/playerManager/playerNative.ts +18 -2
  47. package/appUtils/playerManager/usePlayerControllerSetup.tsx +3 -3
  48. package/arrayUtils/__tests__/__snapshots__/arrayUtils.test.ts.snap +4 -4
  49. package/domUtils/{cookieManager.js → cookieManager.ts} +1 -2
  50. package/equalUtils/__tests__/isEqual/combination.test.ts +263 -0
  51. package/equalUtils/index.ts +49 -30
  52. package/focusManager/FocusManager.ts +11 -1
  53. package/focusManager/index.ts +49 -4
  54. package/functionUtils/__tests__/__snapshots__/functionUtils.test.js.snap +4 -4
  55. package/functionUtils/index.ts +36 -19
  56. package/idleUtils/__tests__/withTimeout$.test.ts +37 -0
  57. package/idleUtils/index.ts +1 -1
  58. package/idleUtils/withTimeout.ts +15 -2
  59. package/logger/const.ts +9 -67
  60. package/manifestUtils/__tests__/createConfig.test.js +16 -16
  61. package/manifestUtils/defaultManifestConfigurations/player.js +8 -0
  62. package/manifestUtils/fonts/index.js +5 -6
  63. package/manifestUtils/sharedConfiguration/screenPicker/__tests__/utils.test.js +304 -0
  64. package/manifestUtils/sharedConfiguration/screenPicker/stylesFields.js +875 -0
  65. package/manifestUtils/sharedConfiguration/screenPicker/utils.js +130 -0
  66. package/modalState/index.ts +1 -5
  67. package/navigationUtils/__tests__/__snapshots__/navigationUtils.test.js.snap +6 -6
  68. package/navigationUtils/__tests__/itemTypes.test.js +2 -2
  69. package/navigationUtils/__tests__/navigationUtils.test.js +2 -2
  70. package/navigationUtils/index.ts +17 -8
  71. package/numberUtils/index.ts +7 -5
  72. package/package.json +5 -10
  73. package/playerUtils/configurationGenerator.ts +8 -0
  74. package/reactHooks/autoscrolling/__tests__/useTrackCurrentAutoScrollingElement.test.ts +1 -1
  75. package/reactHooks/autoscrolling/__tests__/useTrackedView.test.tsx +6 -8
  76. package/reactHooks/autoscrolling/useTrackedView.ts +12 -1
  77. package/reactHooks/cell-click/__tests__/index.test.js +10 -2
  78. package/reactHooks/cell-click/index.ts +7 -1
  79. package/reactHooks/feed/__tests__/useBuildPipesUrl.test.tsx +4 -4
  80. package/reactHooks/feed/__tests__/useFeedLoader.test.tsx +10 -11
  81. package/reactHooks/feed/useFeedLoader.tsx +1 -1
  82. package/reactHooks/feed/usePipesCacheReset.ts +5 -5
  83. package/reactHooks/flatList/__tests__/index.test.tsx +30 -21
  84. package/reactHooks/layout/__tests__/index.test.tsx +10 -10
  85. package/reactHooks/layout/useDimensions/helpers/__tests__/getInitialDimensions.native.test.js +14 -28
  86. package/reactHooks/layout/useDimensions/helpers/__tests__/getInitialDimensions.test.js +1 -1
  87. package/reactHooks/layout/useDimensions/useDimensions.ts +8 -0
  88. package/reactHooks/navigation/__mocks__/useNavigation.ts +3 -0
  89. package/reactHooks/navigation/__tests__/index.test.tsx +6 -21
  90. package/reactHooks/navigation/index.ts +41 -240
  91. package/reactHooks/navigation/useGetBottomTabBarHeight.ts +12 -0
  92. package/reactHooks/navigation/useGetNavBarTopBorderWidth.ts +14 -0
  93. package/reactHooks/navigation/useGetTabBarHeight.ts +10 -0
  94. package/reactHooks/navigation/useIsScreenActive.ts +17 -0
  95. package/reactHooks/navigation/useNavigationPluginData.ts +15 -0
  96. package/reactHooks/navigation/useNavigationType.ts +15 -0
  97. package/reactHooks/navigation/usePathname.ts +4 -0
  98. package/reactHooks/navigation/useProfilerLogging.ts +15 -0
  99. package/reactHooks/navigation/useRoute.ts +100 -0
  100. package/reactHooks/navigation/useUniqueRouteSuffix.ts +41 -0
  101. package/reactHooks/player/TVSeekControlller/ISeekController.ts +32 -0
  102. package/reactHooks/player/TVSeekControlller/LongPressSeekController.ts +71 -0
  103. package/reactHooks/player/TVSeekControlller/TVSeekController.ts +258 -0
  104. package/reactHooks/player/TVSeekControlller/TapSeekController.ts +53 -0
  105. package/reactHooks/player/__tests__/{useAutoSeek.test.tsx → useAutoSeek._test.tsx} +2 -3
  106. package/reactHooks/player/const.ts +0 -4
  107. package/reactHooks/player/index.ts +0 -6
  108. package/reactHooks/state/useRefWithInitialValue.ts +14 -0
  109. package/reactHooks/state/useStateFromSubscribe.ts +23 -0
  110. package/reactHooks/usePluginConfiguration.ts +1 -1
  111. package/reactHooks/useSubscriberFor.ts +4 -3
  112. package/reactUtils/__tests__/__snapshots__/reactUtilsHelpers.test.js.snap +16 -4
  113. package/reactUtils/__tests__/reactUtils.test.js +1 -37
  114. package/reactUtils/__tests__/reactUtilsHelpers.test.js +72 -71
  115. package/reactUtils/createContext/__tests__/__snapshots__/helpers.test.js.snap +13 -79
  116. package/reactUtils/createContext/__tests__/__snapshots__/{index.test.js.snap → index.test.ts.snap} +2 -2
  117. package/reactUtils/createContext/__tests__/__snapshots__/provider.test.js.snap +3 -28
  118. package/reactUtils/createContext/__tests__/__snapshots__/withConsumer.test.js.snap +3 -4
  119. package/reactUtils/createContext/__tests__/__snapshots__/withProvider.test.js.snap +7 -3
  120. package/reactUtils/createContext/__tests__/helpers.test.js +107 -121
  121. package/reactUtils/createContext/__tests__/{index.test.js → index.test.ts} +2 -2
  122. package/reactUtils/createContext/__tests__/provider.test.js +4 -6
  123. package/reactUtils/createContext/__tests__/withConsumer.test.js +12 -12
  124. package/reactUtils/createContext/__tests__/withProvider.test.js +4 -4
  125. package/reactUtils/createContext/helpers.ts +6 -1
  126. package/reactUtils/createContext/index.ts +11 -25
  127. package/reactUtils/index.ts +0 -25
  128. package/screenState/index.ts +24 -10
  129. package/services/js2native.ts +673 -0
  130. package/testUtils/index.tsx +0 -10
  131. package/theme/__mocks__/index.ts +4 -0
  132. package/theme/index.ts +1 -1
  133. package/time/BackgroundTimer.ts +55 -0
  134. package/time/Timer.ts +9 -6
  135. package/types.ts +1 -0
  136. package/zappFrameworkUtils/presentPluginOneTime.ts +2 -3
  137. package/appUtils/keyCodes/keys.ios.js +0 -46
  138. package/reactHooks/player/useAutoSeek.tsx +0 -87
  139. package/reactHooks/player/useSeek.tsx +0 -66
  140. package/reactHooks/player/useTapSeek.tsx +0 -58
  141. package/reactUtils/createContext/__tests__/index-no-react.test.js +0 -27
  142. /package/appUtils/HooksManager/{logger.js → logger.ts} +0 -0
  143. /package/appUtils/{directionHelper.js → directionHelper.ts} +0 -0
  144. /package/appUtils/keyCodes/{androidTVKeys.js → androidTVKeys.ts} +0 -0
  145. /package/appUtils/keyCodes/{keys.web.js → keys/keys.web.ts} +0 -0
  146. /package/appUtils/{localizationsHelper.js → localizationsHelper.ts} +0 -0
  147. /package/domUtils/{logger.js → logger.ts} +0 -0
  148. /package/reactHooks/player/__tests__/{useTapSeek.test.ts → useTapSeek._test.ts} +0 -0
@@ -0,0 +1,119 @@
1
+ import { SubscriberObj } from "../../functionUtils";
2
+ import { HOOKS_EVENTS } from "./constants";
3
+
4
+ export interface HookConfiguration {}
5
+
6
+ /* existing Hook Plugn props: sample */
7
+
8
+ /*
9
+ interface _HookModulePlugin {
10
+ Component:
11
+ | React.FC<HookPluginProps>
12
+ | React.Component<HookPluginProps, any>
13
+ | React.ReactNode;
14
+ ModalContainer?: React.FC<ModalProps> | React.Component<ModalProps, any>;
15
+ skipHook?: (payload: ZappRiver | ZappEntry) => boolean;
16
+ presentFullScreen: boolean;
17
+ showNavBar?: boolean;
18
+ isFlowBlocker: (() => boolean) | boolean; // narrow this
19
+ hasPlayerHook?: boolean;
20
+ name: string;
21
+ runInBackground?: (
22
+ item: HookPluginProps["payload"],
23
+ callback: hookCallback,
24
+ hookPlugin: HookScreenData,
25
+ presentUI: (payload?: HookPluginProps["payload"]) => void
26
+ ) => any;
27
+ }
28
+ */
29
+
30
+ //* Derived from usage */
31
+ export interface HookModule {
32
+ ModalContainer?: React.ComponentType<any>;
33
+ Component: React.ComponentType<any>;
34
+ presentFullScreen: boolean;
35
+ run?: (
36
+ arg0: unknown,
37
+ callback: () => void,
38
+ configuration: HookConfiguration
39
+ ) => void; // not sure about optional parameter
40
+ hasPlayerHook?: boolean;
41
+ configuration?: HookConfiguration;
42
+ isFlowBlocker: () => boolean;
43
+ skipHook?: (payload?: ZappRiver | ZappEntry) => boolean;
44
+ runInBackground?: (
45
+ arg0: unknown,
46
+ callback: () => void,
47
+ hookPlugin: Hook,
48
+ presentUI: (payload?: HookPluginProps["payload"]) => void
49
+ ) => void;
50
+ name: string;
51
+ showNavBar?: boolean; // unsure
52
+ }
53
+
54
+ export type HookManagerArgs = {
55
+ rivers: Record<string, ZappRiver>;
56
+ plugins: ZappPlugin[];
57
+ targetScreen: ZappRiver;
58
+ startUpHooksData?: {
59
+ startUpHooks?: unknown[] | "in_process";
60
+ setStartUpHooks?: (arg0: string) => void;
61
+ };
62
+ };
63
+
64
+ export interface HookManager {
65
+ subscriber: SubscriberObj;
66
+ presentScreenHook: (
67
+ hook: Hook,
68
+ payload: unknown,
69
+ callback: (...any) => void
70
+ ) => void;
71
+ executeHook: (
72
+ hook: Hook,
73
+ payload: unknown,
74
+ callback: (...any) => void
75
+ ) => void;
76
+ handleHooks: (payload: unknown) => void;
77
+ runInBackground: (
78
+ hook: Hook,
79
+ payload: unknown,
80
+ callback: (...any) => void,
81
+ presentUI: (payload?: HookPluginProps["payload"]) => void
82
+ ) => void;
83
+ }
84
+
85
+ export type HookIniObj = {
86
+ hook: HookModule & {
87
+ module: HookModule;
88
+ general: unknown;
89
+ screen_id?: string;
90
+ identifier?: string;
91
+ weight?: number | string;
92
+ };
93
+ hooks: Hook<any>[];
94
+ manager: HookManager;
95
+ };
96
+
97
+ export interface Hook<T extends HookModule = HookModule> {
98
+ manager: HookManager;
99
+ lastHook: boolean;
100
+ state: number;
101
+ module: T;
102
+ screen_id: string;
103
+ identifier: string;
104
+ configuration: T["configuration"];
105
+ setStateAndNotify(
106
+ event: (typeof HOOKS_EVENTS)[keyof typeof HOOKS_EVENTS],
107
+ opts: {
108
+ error?: typeof Error;
109
+ route?: string;
110
+ hookPlugin: Hook<T>;
111
+ payload: unknown;
112
+ }
113
+ );
114
+ general?: {
115
+ transition_type: (typeof HOOKS_EVENTS)[keyof typeof HOOKS_EVENTS];
116
+ allow_screen_plugin_presentation: boolean;
117
+ is_flow_blocker: boolean;
118
+ };
119
+ }
@@ -26,7 +26,7 @@ export const riverFocusManager = (function () {
26
26
  /**
27
27
  * Retrieve screen focus data by id
28
28
  *
29
- * @param {String} screenFocusableGroupId Id that was used to save screen data.
29
+ * @param {{screenFocusableGroupId: string}} screenFocusableGroupId Unique Id of the screen from layout.json
30
30
  *
31
31
  */
32
32
  function screenFocusableData({ screenFocusableGroupId }) {
@@ -36,8 +36,9 @@ export const riverFocusManager = (function () {
36
36
  /**
37
37
  * Clear screen data for specific screen
38
38
  *
39
- * @param {String} screenId Unique Id of the screen from layout.json
40
- * @param {boolean} isInsideContainer If this screen a screen picker child
39
+ * @param {{ screenId: string, isInsideContainer: boolean }}
40
+ * screenId Unique Id of the screen from layout.json
41
+ * isInsideContainer If this screen a screen picker child
41
42
  *
42
43
  */
43
44
  function clearScreenData({ screenId, isInsideContainer }) {
@@ -60,9 +61,13 @@ export const riverFocusManager = (function () {
60
61
  /**
61
62
  * Focus on selected items
62
63
  *
63
- * @param {String} screenFocusableGroupId Top group id that was saved as screenn
64
- * @param {String} screenFocusedOnItem focusable item to focus
65
- * @param {object} history navigator history object
64
+ * @param {{
65
+ * screenFocusableGroupId: string,
66
+ * screenFocusedOnItem: () => void,
67
+ * previousAction: QuickBrickNavigationActionType,
68
+ * isDeepLink: boolean }} object contains data for focus
69
+ * - `screenFocusedOnItem` focusable item to focus
70
+ * - `previousActionhistory` navigator history object
66
71
  *
67
72
  */
68
73
  function focusOnSelectedItem({
@@ -115,15 +120,15 @@ export const riverFocusManager = (function () {
115
120
 
116
121
  /**
117
122
  * Create unique key that will be used for save focused group data inside specific screen
118
- *
119
- * @param {String} screenId Unique Id of the screen from layout.json
120
- * @param {boolean} isInsideContainer If this screen a screen picker child
123
+ * @param {{ screenId: string, isInsideContainer: boolean }}
124
+ * screenId Unique Id of the screen from layout.json
125
+ * isInsideContainer If this screen a screen picker child
121
126
  *
122
127
  */
123
128
  function screenFocusableGroupId({ screenId, isInsideContainer }) {
124
- return R.isNil(isInsideContainer)
125
- ? `RiverFocusableGroup-${screenId}`
126
- : `RiverFocusableGroup-${screenId}-isInsideContainer`;
129
+ return `RiverFocusableGroup-${screenId}${
130
+ R.isNil(isInsideContainer) ? "" : "-isInsideContainer"
131
+ }`;
127
132
  }
128
133
 
129
134
  return {
@@ -6,6 +6,9 @@ import { AccessibilityRole } from "react-native";
6
6
 
7
7
  export class AccessibilityManager {
8
8
  private static _instance: AccessibilityManager | null = null;
9
+ private headingTimeout: NodeJS.Timeout | null = null;
10
+ private WORDS_PER_MINUTE = 160;
11
+ private MINIMUM_PAUSE = 500;
9
12
  private state$ = new BehaviorSubject<AccessibilityState>({
10
13
  screenReaderEnabled: false,
11
14
  reduceMotionEnabled: false,
@@ -20,6 +23,7 @@ export class AccessibilityManager {
20
23
 
21
24
  private ttsManager = TTSManager.getInstance();
22
25
  private localizations: { [key: string]: string } = {};
26
+ private headingQueue: string[] = [];
23
27
 
24
28
  private constructor() {}
25
29
 
@@ -52,6 +56,28 @@ export class AccessibilityManager {
52
56
  return this.state$.asObservable();
53
57
  }
54
58
 
59
+ /** Calculates the reading time for a given text
60
+ * This method is a bit of a hack because we don't have a callback, or promise from VIZIO API
61
+ * @param text - The text to calculate the reading time for
62
+ * @returns The reading time in milliseconds
63
+ */
64
+ private calculateReadingTime(text: string): number {
65
+ const words = text.trim().split(/\s+/).length;
66
+
67
+ return Math.max(
68
+ this.MINIMUM_PAUSE,
69
+ (words / this.WORDS_PER_MINUTE) * 60 * 1000
70
+ );
71
+ }
72
+
73
+ /**
74
+ * Adds a heading to the queue, headings will be read before the next text
75
+ * Each heading will be read once and removed from the queue
76
+ */
77
+ public addHeading(heading: string) {
78
+ this.headingQueue.push(heading);
79
+ }
80
+
55
81
  /**
56
82
  * text you want to be read, if you want to use localized text pass keyOfLocalizedText instead
57
83
  * keyOfLocalizedText is the key to the localized text
@@ -85,7 +111,22 @@ export class AccessibilityManager {
85
111
  textToRead = localizedMessage;
86
112
  }
87
113
 
88
- this.ttsManager && this.ttsManager.readText(textToRead);
114
+ if (this.headingQueue.length > 0) {
115
+ const heading = this.headingQueue.shift()!;
116
+ this.ttsManager?.readText(heading);
117
+
118
+ if (this.headingTimeout) {
119
+ clearTimeout(this.headingTimeout);
120
+ }
121
+
122
+ const pauseTime = this.calculateReadingTime(heading);
123
+
124
+ this.headingTimeout = setTimeout(() => {
125
+ this.ttsManager?.readText(textToRead);
126
+ }, pauseTime);
127
+ } else {
128
+ this.ttsManager?.readText(textToRead);
129
+ }
89
130
 
90
131
  this.announcements$.next({
91
132
  message: textToRead,
@@ -0,0 +1,77 @@
1
+ import { createLogger } from "../../logger";
2
+ import { ContextKeysManager } from "./index";
3
+ import * as R from "ramda";
4
+
5
+ const { log_error } = createLogger({
6
+ subsystem: "contextResolver",
7
+ category: "General",
8
+ });
9
+
10
+ interface IResolver {
11
+ resolve: (string) => Promise<string | number | object>;
12
+ }
13
+
14
+ export class EntryResolver implements IResolver {
15
+ entry: ZappEntry;
16
+
17
+ constructor(entry: ZappEntry) {
18
+ this.entry = entry;
19
+ }
20
+
21
+ async resolve(key: string) {
22
+ if (!key || key.length === 0) {
23
+ return this.entry;
24
+ }
25
+
26
+ return R.view(R.lensPath(key.split(".")), this.entry);
27
+ }
28
+ }
29
+
30
+ export class ContextResolver implements IResolver {
31
+ resolve = async (compositeKey: string) =>
32
+ ContextKeysManager.instance.getKey(compositeKey);
33
+ }
34
+
35
+ export const resolveObjectValues = async (
36
+ data: Record<string, any>,
37
+ resolvers: Record<string, IResolver> = null
38
+ ) => {
39
+ const entries = Object.entries(data);
40
+
41
+ const exResolvers = { ctx: new ContextResolver(), ...resolvers };
42
+
43
+ const resolvedEntries = await Promise.all(
44
+ entries.map(async ([key, value]) => {
45
+ if (typeof value !== "string") {
46
+ return [key, value];
47
+ }
48
+
49
+ if (!value.startsWith("@")) {
50
+ return [key, value];
51
+ }
52
+
53
+ const regex = /@{([^/]+)\/([^}]*)}/;
54
+
55
+ const match = (value as string).match(regex);
56
+
57
+ if (match) {
58
+ const contextResolverName = match[1];
59
+ const compositeKey = match[2];
60
+
61
+ const resolver = exResolvers[contextResolverName] || exResolvers.ctx;
62
+ const resolvedValue = await resolver.resolve(compositeKey);
63
+
64
+ return [key, resolvedValue];
65
+ }
66
+
67
+ log_error("resolveObjectValues: invalid context string", {
68
+ key,
69
+ value,
70
+ });
71
+
72
+ return [key, null];
73
+ })
74
+ );
75
+
76
+ return Object.fromEntries(resolvedEntries);
77
+ };
@@ -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(".")
@@ -1,14 +1,14 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`focusManager should be defined 1`] = `
4
- Object {
4
+ {
5
5
  "blur": [Function],
6
6
  "disableFocus": [Function],
7
7
  "enableFocus": [Function],
8
8
  "focusableTree": Tree {
9
9
  "loadingCounter": 0,
10
- "root": Object {
11
- "children": Array [],
10
+ "root": {
11
+ "children": [],
12
12
  "component": null,
13
13
  "id": "root",
14
14
  "lastFocusedItem": null,
@@ -28,6 +28,7 @@ Object {
28
28
  "moveFocus": [Function],
29
29
  "on": [Function],
30
30
  "press": [Function],
31
+ "pressIn": [Function],
31
32
  "pressOut": [Function],
32
33
  "recoverFocus": [Function],
33
34
  "register": [Function],
@@ -39,12 +40,12 @@ Object {
39
40
  `;
40
41
 
41
42
  exports[`focusManagerIOS should be defined 1`] = `
42
- Object {
43
+ {
43
44
  "focus": [Function],
44
45
  "focusableTree": Tree {
45
46
  "loadingCounter": 0,
46
- "root": Object {
47
- "children": Array [],
47
+ "root": {
48
+ "children": [],
48
49
  "component": null,
49
50
  "id": "root",
50
51
  "lastFocusedItem": null,
@@ -11,8 +11,9 @@ import {
11
11
  haveSameParentBeforeRoot,
12
12
  } from "./treeDataStructure/Utils";
13
13
  import { subscriber } from "../../functionUtils";
14
- import * as FOCUS_EVENTS from "./events";
15
14
  import { coreLogger } from "../../logger";
15
+ import { ACTION } from "./utils/enums";
16
+
16
17
  const logger = coreLogger.addSubsystem("focusManager");
17
18
 
18
19
  const isFocusEnabled = (focusableItem): boolean => {
@@ -167,13 +168,82 @@ export const focusManager = (function () {
167
168
  }
168
169
 
169
170
  /**
170
- * returns the current focusable's group
171
- * @private
172
- * @returns {React.Component} the current focus
171
+ * Computes the difference in parent groups between two nodes.
172
+ * It returns the IDs of parents that are present in the current node but not in the previous node,
173
+ * as well as the IDs of parents that are present in the previous node but not in the current node.
174
+ *
175
+ * @param {string} currentNodeID - The ID of the current node.
176
+ * @param {string} previousNodeID - The ID of the previous node.
177
+ * @returns {Object} An object containing the differences in parent IDs:
178
+ * - {Array<string>} currentNodeParentsIDs: Parent IDs present in the current node but not in the previous node.
179
+ * - {Array<string>} previousNodeParentsIDs: Parent IDs present in the previous node but not in the current node.
173
180
  */
174
- function getCurrentGroup() {
175
- return getGroupById(R.path(["props", "groupId"], getCurrentFocus()));
176
- }
181
+ const getParentDiffs = (currentNodeID, previousNodeID) => {
182
+ const currentNodeParentsIDs =
183
+ focusableTree.getParentGroupIds(currentNodeID);
184
+
185
+ const previousNodeParentsIDs =
186
+ focusableTree.getParentGroupIds(previousNodeID);
187
+
188
+ return {
189
+ currentNodeParentsIDs: R.difference(
190
+ currentNodeParentsIDs,
191
+ previousNodeParentsIDs
192
+ ),
193
+ previousNodeParentsIDs: R.difference(
194
+ previousNodeParentsIDs,
195
+ currentNodeParentsIDs
196
+ ),
197
+ };
198
+ };
199
+
200
+ /**
201
+ * Updates the focus state of nodes identified by their IDs.
202
+ * For each ID, if `setFocus` is true, the node will receive focus;
203
+ * otherwise, it will lose focus.
204
+ *
205
+ * @param {Array<string>} ids - An array of node IDs to update.
206
+ * @param {boolean} setFocus - A flag indicating whether to set focus (true) or blur (false) on the nodes.
207
+ */
208
+ const updateNodeFocus = (ids, action) => {
209
+ if (!ids || ids.length === 0) {
210
+ return; // Nothing to do
211
+ }
212
+
213
+ const startNode = focusableTree.findInTree(ids[0]);
214
+
215
+ if (!startNode) {
216
+ return; // Start node not found
217
+ }
218
+
219
+ const endNodeId = ids[ids.length - 1]; // Get the ID of the end node
220
+
221
+ let currentNode = startNode;
222
+
223
+ // Function to apply the action (focus or blur)
224
+ const applyAction = (node) => {
225
+ if (node && node.component) {
226
+ if (action === "focus") {
227
+ node.component.setFocus();
228
+ } else if (action === "blur") {
229
+ node.component.setBlur();
230
+ }
231
+ }
232
+ };
233
+
234
+ // Apply action to the starting node
235
+ applyAction(currentNode);
236
+
237
+ // Traverse upwards
238
+ while (currentNode && currentNode.id !== endNodeId) {
239
+ currentNode = currentNode.parent;
240
+
241
+ if (currentNode) {
242
+ // check if current node exists, as parent might be root
243
+ applyAction(currentNode);
244
+ }
245
+ }
246
+ };
177
247
 
178
248
  /**
179
249
  * Sets the current focus to the provided id. If there is an existing focus, it will
@@ -181,34 +251,29 @@ export const focusManager = (function () {
181
251
  * @private
182
252
  * @param {String} id of the focusable to set as currentFocus
183
253
  * @param {Object} direction of the navigation, which led to this focus change
184
- * @param {Object} options
185
- * @param {Boolean} options.groupFocusedChanged tells wether the focus has moved
186
254
  * to another group or not. defaults to false
187
255
  */
188
- function setFocus(
189
- id: string,
190
- direction?: FocusManager.Web.Direction,
191
- options?: Partial<{ groupFocusedChanged: boolean }>
192
- ) {
256
+ function setFocus(id: string, direction?: FocusManager.Web.Direction) {
193
257
  if (focusDisabled) return false;
194
258
 
195
- const { groupFocusedChanged = false } = options || {};
196
-
197
259
  // due to optimisiation it's recommanded to set currentFocusNode before setFocus
198
260
  // is called, so there will be no delays on tree search.
199
261
  // If currentFocusNode is null or undefined we still want it to findInTree
200
262
  if (currentFocusNode?.id !== id) {
263
+ const { currentNodeParentsIDs, previousNodeParentsIDs } = getParentDiffs(
264
+ id,
265
+ currentFocusNode?.id
266
+ );
267
+
268
+ // Set focus on current node parents and blur on previous node parents
269
+ updateNodeFocus(currentNodeParentsIDs, ACTION.FOCUS);
270
+ updateNodeFocus(previousNodeParentsIDs, ACTION.BLUR);
271
+
201
272
  currentFocusNode = focusableTree.findInTree(id);
202
273
  }
203
274
 
204
275
  setLastFocusOnParentNode(currentFocusNode);
205
276
 
206
- const currentGroup = getCurrentGroup();
207
-
208
- if (groupFocusedChanged && currentGroup) {
209
- currentGroup.hasLostFocus(currentGroup, direction);
210
- }
211
-
212
277
  focus(direction);
213
278
  }
214
279
 
@@ -251,6 +316,7 @@ export const focusManager = (function () {
251
316
  !focusableItem.isGroup &&
252
317
  isFocusEnabled(focusableItem)
253
318
  ) {
319
+ // TODO: Move this to setFocus method
254
320
  blur();
255
321
 
256
322
  logger.log({
@@ -260,8 +326,6 @@ export const focusManager = (function () {
260
326
  },
261
327
  });
262
328
 
263
- currentFocusNode = focusCandidate;
264
- focusableItem.willReceiveFocus(focusableItem, null);
265
329
  focusableItem && setFocus(focusCandidate.id, null);
266
330
 
267
331
  return { success: true };
@@ -270,15 +334,14 @@ export const focusManager = (function () {
270
334
  return { success: false };
271
335
  }
272
336
 
273
- function treeLoaded(val, node) {
274
- logger.log({
275
- message: "treeLoaded",
276
- data: {
277
- val,
278
- node,
279
- currentFocusNode,
280
- },
281
- });
337
+ function treeLoaded(_val, _node) {}
338
+
339
+ function pressIn() {
340
+ const currentFocusable = getCurrentFocus();
341
+
342
+ if (currentFocusable) {
343
+ currentFocusable.pressIn(null);
344
+ }
282
345
  }
283
346
 
284
347
  /**
@@ -412,7 +475,7 @@ export const focusManager = (function () {
412
475
  function isCurrentFocusOnTheTopScreen() {
413
476
  const routes = R.pathOr([], ["root", "children"], focusableTree);
414
477
 
415
- if (routes.length <= 1) {
478
+ if (currentFocusNode && routes.length <= 1) {
416
479
  // there is only one or zero routes
417
480
  return true;
418
481
  }
@@ -448,20 +511,10 @@ export const focusManager = (function () {
448
511
  const nextRect = nextFocusable.component.getRect();
449
512
 
450
513
  if (nextRect && nextRect.width > 0) {
451
- nextFocusable.component.willReceiveFocus(
452
- nextFocusable.component,
453
- direction
454
- );
455
-
514
+ // TODO: Move this to setFocus method
456
515
  blur(direction);
457
516
 
458
- const groupChanged =
459
- nextFocusable.component.props.groupId !==
460
- currentFocusNode?.component?.props?.groupId;
461
-
462
- setFocus(nextFocusable.id, direction, {
463
- groupFocusedChanged: groupChanged,
464
- });
517
+ setFocus(nextFocusable.id, direction);
465
518
  }
466
519
  } else {
467
520
  currentFocusNode.component.failedLostFocus(currentFocusNode, direction);
@@ -481,34 +534,6 @@ export const focusManager = (function () {
481
534
 
482
535
  const { on, invokeHandler, removeHandler } = eventHandler;
483
536
 
484
- on(FOCUS_EVENTS.FOCUS, (focusable, direction) => {
485
- if (focusable.props.groupId) {
486
- const parent = focusableTree.findInTree(focusable.props.groupId);
487
- parent?.component?.onFocus(focusable.component, direction);
488
- }
489
- });
490
-
491
- on(FOCUS_EVENTS.BLUR, (focusable, direction) => {
492
- if (focusable.props.groupId) {
493
- const parent = focusableTree.findInTree(focusable.props.groupId);
494
- parent?.component?.onBlur(focusable.component, direction);
495
- }
496
- });
497
-
498
- on(FOCUS_EVENTS.HAS_LOST_FOCUS, (focusable, direction) => {
499
- if (focusable.props.groupId) {
500
- const parent = focusableTree.findInTree(focusable.props.groupId);
501
- parent?.component?.hasLostFocus(parent.component, direction);
502
- }
503
- });
504
-
505
- on(FOCUS_EVENTS.WILL_RECEIVE_FOCUS, (focusable, direction) => {
506
- if (focusable.props.groupId) {
507
- const parent = focusableTree.findInTree(focusable.props.groupId);
508
- parent?.component?.willReceiveFocus(focusable.component, direction);
509
- }
510
- });
511
-
512
537
  /**
513
538
  * this is the list of the functions available externally
514
539
  * when importing the focus manager
@@ -526,6 +551,7 @@ export const focusManager = (function () {
526
551
  invokeHandler,
527
552
  removeHandler,
528
553
  longPress,
554
+ pressIn,
529
555
  pressOut,
530
556
  enableFocus,
531
557
  setInitialFocus,