@applicaster/zapp-react-native-utils 15.0.0-rc.66 → 15.0.0-rc.68

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.
@@ -1,11 +1,31 @@
1
- import * as R from "ramda";
2
-
3
- import { focusManager } from "../focusManager";
1
+ import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager/index.ios";
2
+ import { QUICK_BRICK_CONTENT } from "@applicaster/quick-brick-core/const";
3
+ import { isNil, isEmpty } from "@applicaster/zapp-react-native-utils/utils";
4
+ import { isNotNil } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
4
5
 
5
6
  let riverFocusData = {};
6
7
  let initialyPresentedScreenFocused = false;
7
8
 
8
9
  export const riverFocusManager = (function () {
10
+ /**
11
+ * Create unique key that will be used for save focused group data inside specific screen
12
+ * @param {{ screenId: string, isInsideContainer: boolean }}
13
+ * screenId Unique Id of the screen from layout.json
14
+ * isInsideContainer If this screen a screen picker child
15
+ *
16
+ */
17
+ function screenFocusableGroupId({
18
+ screenId,
19
+ isInsideContainer,
20
+ }: {
21
+ screenId: string;
22
+ isInsideContainer: Option<boolean>;
23
+ }) {
24
+ return `${QUICK_BRICK_CONTENT}-${screenId}${
25
+ isNil(isInsideContainer) ? "" : "-isInsideContainer"
26
+ }`;
27
+ }
28
+
9
29
  function setScreenFocusableData({
10
30
  screenFocusableGroupId,
11
31
  groupId,
@@ -78,8 +98,8 @@ export const riverFocusManager = (function () {
78
98
  }) {
79
99
  // Check if screen should be focused
80
100
  const shouldFocus =
81
- (initialyPresentedScreenFocused === false && R.isEmpty(riverFocusData)) ||
82
- R.compose(R.not, R.isNil)(riverFocusData[screenFocusableGroupId]) ||
101
+ (initialyPresentedScreenFocused === false && isEmpty(riverFocusData)) ||
102
+ isNotNil(riverFocusData[screenFocusableGroupId]) ||
83
103
  isDeepLink;
84
104
 
85
105
  // TODO: Uncommit it to start fixing bug where selection wrong item
@@ -118,19 +138,6 @@ export const riverFocusManager = (function () {
118
138
  }
119
139
  }
120
140
 
121
- /**
122
- * Create unique key that will be used for save focused group data inside specific screen
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
126
- *
127
- */
128
- function screenFocusableGroupId({ screenId, isInsideContainer }) {
129
- return `RiverFocusableGroup-${screenId}${
130
- R.isNil(isInsideContainer) ? "" : "-isInsideContainer"
131
- }`;
132
- }
133
-
134
141
  return {
135
142
  setScreenFocusableData,
136
143
  clearAllScreensData,
@@ -71,6 +71,9 @@ exports[`focusManagerIOS should be defined 1`] = `
71
71
  "invokeHandler": [Function],
72
72
  "isChildOf": [Function],
73
73
  "isFocusOn": [Function],
74
+ "isFocusOnContent": [Function],
75
+ "isFocusOnMenu": [Function],
76
+ "isFocusOnTabsScreenContent": [Function],
74
77
  "isGroupItemFocused": [Function],
75
78
  "moveFocus": [Function],
76
79
  "on": [Function],
@@ -4,7 +4,11 @@ import * as R from "ramda";
4
4
  import {
5
5
  isCurrentFocusOn,
6
6
  isChildOf as isChildOfUtils,
7
- } from "../focusManagerAux/utils";
7
+ isPartOfMenu,
8
+ isPartOfContent,
9
+ isPartOfTabsScreenContent,
10
+ } from "../focusManagerAux/utils/index.ios";
11
+
8
12
  import { Tree } from "./treeDataStructure/Tree";
9
13
  import { findFocusableNode } from "./treeDataStructure/Utils";
10
14
  import { subscriber } from "../../functionUtils";
@@ -279,7 +283,9 @@ export const focusManager = (function () {
279
283
  function setFocus(
280
284
  id: string,
281
285
  direction?: FocusManager.IOS.Direction,
282
- options?: Partial<{ groupFocusedChanged: boolean }>,
286
+ options?: Partial<{
287
+ groupFocusedChanged: boolean;
288
+ }>,
283
289
  callback?: any
284
290
  ) {
285
291
  blur(direction);
@@ -418,6 +424,30 @@ export const focusManager = (function () {
418
424
  return id && isCurrentFocusOn(id, currentFocusNode);
419
425
  }
420
426
 
427
+ function isFocusOnMenu(): boolean {
428
+ const currentFocusable = getCurrentFocus();
429
+
430
+ return isPartOfMenu(focusableTree, currentFocusable?.props?.id);
431
+ }
432
+
433
+ function isFocusOnContent(): boolean {
434
+ const currentFocusable = getCurrentFocus();
435
+
436
+ return isPartOfContent(focusableTree, currentFocusable?.props?.id);
437
+ }
438
+
439
+ function isFocusOnTabsScreenContent(
440
+ screenPickerContentContainerId: string
441
+ ): boolean {
442
+ const currentFocusable = getCurrentFocus();
443
+
444
+ return isPartOfTabsScreenContent(
445
+ focusableTree,
446
+ screenPickerContentContainerId,
447
+ currentFocusable?.props?.id
448
+ );
449
+ }
450
+
421
451
  function isChildOf(childId, parentId): boolean {
422
452
  return isChildOfUtils(focusableTree, childId, parentId);
423
453
  }
@@ -444,6 +474,9 @@ export const focusManager = (function () {
444
474
  isGroupItemFocused,
445
475
  getPreferredFocusChild,
446
476
  isFocusOn,
477
+ isFocusOnMenu,
478
+ isFocusOnContent,
479
+ isFocusOnTabsScreenContent,
447
480
  isChildOf,
448
481
  };
449
482
  })();
@@ -0,0 +1,122 @@
1
+ import { isNil, startsWith } from "@applicaster/zapp-react-native-utils/utils";
2
+
3
+ import {
4
+ QUICK_BRICK_CONTENT,
5
+ QUICK_BRICK_NAVBAR,
6
+ } from "@applicaster/quick-brick-core/const";
7
+
8
+ const isNavBar = (node) => startsWith(QUICK_BRICK_NAVBAR, node?.id);
9
+ const isContent = (node) => startsWith(QUICK_BRICK_CONTENT, node?.id);
10
+ const isRoot = (node) => node?.id === "root";
11
+
12
+ export const isPartOfTabsScreenContent = (
13
+ focusableTree,
14
+ screenPickerContentContainerId,
15
+ id
16
+ ) => {
17
+ const node = focusableTree.findInTree(id);
18
+
19
+ if (isNil(node)) {
20
+ return false;
21
+ }
22
+
23
+ if (isRoot(node)) {
24
+ return false;
25
+ }
26
+
27
+ if (isNavBar(node)) {
28
+ return false;
29
+ }
30
+
31
+ if (isContent(node)) {
32
+ return false;
33
+ }
34
+
35
+ if (node?.id === screenPickerContentContainerId) {
36
+ return true;
37
+ }
38
+
39
+ return isPartOfTabsScreenContent(
40
+ focusableTree,
41
+ screenPickerContentContainerId,
42
+ node.parent?.id
43
+ );
44
+ };
45
+
46
+ export const isPartOfMenu = (focusableTree, id): boolean => {
47
+ const node = focusableTree.findInTree(id);
48
+
49
+ if (isNil(node)) {
50
+ return false;
51
+ }
52
+
53
+ if (isRoot(node)) {
54
+ return false;
55
+ }
56
+
57
+ if (isNavBar(node)) {
58
+ return true;
59
+ }
60
+
61
+ if (isContent(node)) {
62
+ return false;
63
+ }
64
+
65
+ return isPartOfMenu(focusableTree, node.parent?.id);
66
+ };
67
+
68
+ export const isPartOfContent = (focusableTree, id) => {
69
+ const node = focusableTree.findInTree(id);
70
+
71
+ if (isNil(node)) {
72
+ return false;
73
+ }
74
+
75
+ if (isRoot(node)) {
76
+ return false;
77
+ }
78
+
79
+ if (isNavBar(node)) {
80
+ return false;
81
+ }
82
+
83
+ if (isContent(node)) {
84
+ return true;
85
+ }
86
+
87
+ return isPartOfContent(focusableTree, node.parent?.id);
88
+ };
89
+
90
+ export const isCurrentFocusOn = (id, node) => {
91
+ if (!node) {
92
+ return false;
93
+ }
94
+
95
+ if (isRoot(node)) {
96
+ return false;
97
+ }
98
+
99
+ if (node?.id === id) {
100
+ return true;
101
+ }
102
+
103
+ return isCurrentFocusOn(id, node.parent);
104
+ };
105
+
106
+ export const isChildOf = (focusableTree, childId, parentId) => {
107
+ if (isNil(childId) || isNil(parentId)) {
108
+ return false;
109
+ }
110
+
111
+ const childNode = focusableTree.findInTree(childId);
112
+
113
+ if (isNil(childNode)) {
114
+ return false;
115
+ }
116
+
117
+ if (childNode.parent?.id === parentId) {
118
+ return true;
119
+ }
120
+
121
+ return isChildOf(focusableTree, childNode.parent?.id, parentId);
122
+ };
@@ -1,7 +1,9 @@
1
1
  import { ReplaySubject, Subject } from "rxjs";
2
- import { filter, switchMap, take, map } from "rxjs/operators";
2
+ import { filter, switchMap, take, withLatestFrom, map } from "rxjs/operators";
3
+
3
4
  import { BUTTON_PREFIX } from "@applicaster/zapp-react-native-ui-components/Components/MasterCell/DefaultComponents/tv/TvActionButtons/const";
4
5
  import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager/index.ios";
6
+ import { isPartOfMenu, isPartOfContent } from "./index.ios";
5
7
 
6
8
  type FocusableID = string;
7
9
  type RegistrationEvent = {
@@ -175,3 +177,55 @@ export const emitNativeRegistered = ({
175
177
  focusableNativeGroupRegistrationSubject$.next({ id, groupId });
176
178
  }
177
179
  };
180
+
181
+ // /////
182
+
183
+ const focusedSubject$ = new Subject<FocusableID>();
184
+
185
+ const focused$ = focusedSubject$.asObservable();
186
+
187
+ export const emitFocused = (id: FocusableID): void => {
188
+ focusedSubject$.next(id);
189
+ };
190
+
191
+ export const topMenuItemFocused$ = focused$.pipe(
192
+ filter((id) => id && isPartOfMenu(focusManager.focusableTree, id))
193
+ );
194
+
195
+ export const contentFocused$ = focused$.pipe(
196
+ filter((id) => {
197
+ const isContent = isPartOfContent(focusManager.focusableTree, id);
198
+
199
+ return id && isContent;
200
+ })
201
+ );
202
+
203
+ const createFocusableRegistry = () => {
204
+ const subject$ = new ReplaySubject<FocusableID | undefined>(1);
205
+
206
+ return {
207
+ observable$: subject$.asObservable(),
208
+ register: (id: FocusableID) => {
209
+ // save focusable_id on registration
210
+ subject$.next(id);
211
+ },
212
+ unregister: () => {
213
+ // reset focusable_id on unregistration
214
+ subject$.next(undefined);
215
+ },
216
+ };
217
+ };
218
+
219
+ /// HOME_TOP_MENU_ITEM
220
+ export const HomeTopMenuItemRegistry = createFocusableRegistry();
221
+
222
+ export const homeTopMenuItemFocused$ = topMenuItemFocused$.pipe(
223
+ withLatestFrom(HomeTopMenuItemRegistry.observable$),
224
+ filter(([id, homeId]) => id === homeId)
225
+ );
226
+
227
+ /// SCREEN_PICKER
228
+ export const ScreenPickerContentContainerRegistry = createFocusableRegistry();
229
+
230
+ /// SEARCH_INPUT
231
+ export const SearchInputRegistry = createFocusableRegistry();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-utils",
3
- "version": "15.0.0-rc.66",
3
+ "version": "15.0.0-rc.68",
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": "15.0.0-rc.66",
30
+ "@applicaster/applicaster-types": "15.0.0-rc.68",
31
31
  "buffer": "^5.2.1",
32
32
  "camelize": "^1.0.0",
33
33
  "dayjs": "^1.11.10",
@@ -0,0 +1,48 @@
1
+ import { mergeRight } from "../mergeRight";
2
+
3
+ describe("mergeRight", () => {
4
+ test("merges two objects with no overlapping keys", () => {
5
+ const a = { x: 1, y: 2 };
6
+ const b = { z: 3 };
7
+
8
+ const result = mergeRight(a, b);
9
+
10
+ expect(result).toEqual({ x: 1, y: 2, z: 3 });
11
+ });
12
+
13
+ test("overwrites keys from the second object", () => {
14
+ const a = { x: 1, y: 2 };
15
+ const b = { y: 10, z: 3 };
16
+
17
+ const result = mergeRight(a, b);
18
+
19
+ expect(result).toEqual({ x: 1, y: 10, z: 3 });
20
+ });
21
+
22
+ test("does not mutate the original objects", () => {
23
+ const a = { x: 1 };
24
+ const b = { y: 2 };
25
+
26
+ const result = mergeRight(a, b);
27
+
28
+ expect(result).not.toBe(a);
29
+ expect(result).not.toBe(b);
30
+ expect(a).toEqual({ x: 1 });
31
+ expect(b).toEqual({ y: 2 });
32
+ });
33
+
34
+ test("works with empty objects", () => {
35
+ expect(mergeRight({}, { a: 1 })).toEqual({ a: 1 });
36
+ expect(mergeRight({ a: 1 }, {})).toEqual({ a: 1 });
37
+ expect(mergeRight({}, {})).toEqual({});
38
+ });
39
+
40
+ test("works with nested objects (shallow merge only)", () => {
41
+ const a = { x: { nested: 1 }, y: 2 };
42
+ const b = { x: { nested: 10 }, z: 3 };
43
+
44
+ const result = mergeRight(a, b);
45
+
46
+ expect(result).toEqual({ x: { nested: 10 }, y: 2, z: 3 });
47
+ });
48
+ });
package/utils/index.ts CHANGED
@@ -18,6 +18,8 @@ export { take } from "./take";
18
18
 
19
19
  export { mapAccum } from "./mapAccum";
20
20
 
21
+ export { mergeRight } from "./mergeRight";
22
+
21
23
  export {
22
24
  cloneDeep as clone,
23
25
  flatten,
@@ -0,0 +1,5 @@
1
+ import { assign } from "lodash";
2
+
3
+ export const mergeRight = (a, b) => {
4
+ return assign({}, a, b);
5
+ };