@applicaster/zapp-react-native-utils 12.0.0-alpha.9151207838 → 12.0.0-alpha.9233494544

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 (71) hide show
  1. package/actionsExecutor/ActionExecutorContext.tsx +11 -3
  2. package/analyticsUtils/__tests__/analyticsManager.test.js +75 -73
  3. package/analyticsUtils/helpers/hooks.ts +1 -1
  4. package/appUtils/HooksManager/index.js +3 -1
  5. package/appUtils/focusManager/index.ts +147 -25
  6. package/appUtils/focusManager/treeDataStructure/Utils/index.js +83 -33
  7. package/appUtils/focusManager/utils/index.ts +77 -0
  8. package/appUtils/keyCodes/__tests__/__snapshots__/keyCodes.test.js.snap +1 -1
  9. package/appUtils/keyCodes/__tests__/keyCodes.test.js +80 -85
  10. package/appUtils/keyCodes/keys.web.js +1 -0
  11. package/appUtils/orientationHelper.ts +1 -1
  12. package/appUtils/playerManager/conts.ts +5 -0
  13. package/appUtils/playerManager/player.ts +35 -0
  14. package/appUtils/playerManager/playerFactory.ts +15 -1
  15. package/appUtils/playerManager/playerNative.ts +9 -0
  16. package/cellUtils/__tests__/cellUtils.test.ts +59 -0
  17. package/cellUtils/__tests__/fixtures.ts +13 -0
  18. package/cellUtils/index.ts +46 -2
  19. package/focusManager/index.ts +1 -1
  20. package/functionUtils/__tests__/__snapshots__/functionUtils.test.js.snap +2 -2
  21. package/functionUtils/__tests__/functionUtils.test.js +244 -248
  22. package/headersUtils/index.ts +1 -0
  23. package/localizationUtils/__tests__/localizationUtils.test.ts +17 -13
  24. package/localizationUtils/index.ts +6 -3
  25. package/logger/index.ts +1 -1
  26. package/manifestUtils/defaultManifestConfigurations/generalContent.js +6 -0
  27. package/manifestUtils/defaultManifestConfigurations/player.js +2 -1
  28. package/manifestUtils/index.js +5 -1
  29. package/manifestUtils/secondaryImage/index.js +18 -15
  30. package/navigationUtils/index.ts +56 -7
  31. package/package.json +3 -2
  32. package/playerUtils/configurationGenerator.ts +3 -1
  33. package/pluginUtils/index.ts +11 -7
  34. package/reactHooks/autoscrolling/__tests__/useTrackCurrentAutoScrollingElement.test.ts +2 -0
  35. package/reactHooks/autoscrolling/__tests__/{useTrackedView.test.ts → useTrackedView.test.tsx} +18 -1
  36. package/reactHooks/cell-click/__tests__/index.test.js +6 -2
  37. package/reactHooks/cell-click/index.ts +2 -2
  38. package/reactHooks/device/index.ts +0 -1
  39. package/reactHooks/device/useIsTablet.tsx +28 -3
  40. package/reactHooks/device/useMemoizedIsTablet.ts +1 -1
  41. package/reactHooks/device/useOrientation.tsx +3 -1
  42. package/reactHooks/flatList/useLoadNextPageIfNeeded.ts +16 -9
  43. package/reactHooks/index.ts +2 -0
  44. package/reactHooks/layout/__tests__/{index.test.js → index.test.tsx} +66 -59
  45. package/reactHooks/layout/getDeviceInfo/index.ts +4 -2
  46. package/reactHooks/layout/index.ts +2 -1
  47. package/reactHooks/layout/types/index.ts +2 -0
  48. package/reactHooks/layout/useDimensions/helpers/__tests__/getInitialDimensions.native.test.js +11 -16
  49. package/reactHooks/layout/useDimensions/helpers/__tests__/getInitialDimensions.test.js +12 -14
  50. package/reactHooks/layout/useDimensions/useDimensions.ts +13 -7
  51. package/reactHooks/navigation/__mocks__/index.ts +15 -0
  52. package/reactHooks/navigation/__mocks__/useNavigation.ts +7 -0
  53. package/reactHooks/navigation/__tests__/index.test.tsx +234 -78
  54. package/reactHooks/navigation/index.ts +262 -81
  55. package/reactHooks/resolvers/__tests__/useCellResolver.test.tsx +3 -1
  56. package/reactHooks/screen/__mocks__/useScreenContext.ts +3 -1
  57. package/reactHooks/screen/index.ts +2 -0
  58. package/reactHooks/screen/useScreenContext.ts +5 -4
  59. package/reactHooks/screen/useScreenData.ts +1 -1
  60. package/reactHooks/screen/useTargetScreenData.ts +7 -0
  61. package/reactHooks/state/useRivers.ts +3 -1
  62. package/reactHooks/useListenEventBusEvent.ts +1 -4
  63. package/reactHooks/videoModal/hooks/useVideoModalScreenData.tsx +10 -9
  64. package/reactUtils/helpers.tsx +1 -1
  65. package/riverComponetsMeasurementProvider/index.tsx +2 -2
  66. package/stringUtils/index.ts +5 -0
  67. package/testUtils/getByTestId.ts +14 -0
  68. package/testUtils/index.tsx +38 -23
  69. package/reactHooks/device/useIsTablet.android.tsx +0 -14
  70. package/reactHooks/device/useIsTablet.ios.tsx +0 -7
  71. package/reactHooks/device/useIsTablet.web.tsx +0 -3
@@ -0,0 +1,77 @@
1
+ import { isNotNil } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
2
+ import { find, last, startsWith, pathOr } from "ramda";
3
+
4
+ // run check each 300 ms
5
+ // run this check too often could lead to performance penalty on low-end devices
6
+ const HOW_OFTEN_TO_CHECK_CONDITION = 300; //ms
7
+
8
+ type Props = {
9
+ maxTimeout: number;
10
+ conditionFn: () => boolean;
11
+ };
12
+
13
+ export const waitUntil = ({ maxTimeout, conditionFn }: Props) => {
14
+ let interval;
15
+ let timeout;
16
+ return new Promise((resolve, reject) => {
17
+ interval = setInterval(() => {
18
+ if (conditionFn()) {
19
+ clearInterval(interval);
20
+ clearTimeout(timeout);
21
+
22
+ resolve(undefined);
23
+ }
24
+ }, HOW_OFTEN_TO_CHECK_CONDITION);
25
+
26
+ timeout = setTimeout(() => {
27
+ clearInterval(interval);
28
+ clearTimeout(timeout);
29
+
30
+ reject();
31
+ }, maxTimeout);
32
+ });
33
+ };
34
+
35
+ export const waitForActiveScreen = (currentRoute: string, focusableTree) => {
36
+ const isActiveScreenAvailableToFocus = (): boolean => {
37
+ const routes = focusableTree.root.children;
38
+
39
+ const route = find((route) => route.id === currentRoute, routes);
40
+ return isNotNil(route);
41
+ };
42
+
43
+ return waitUntil({
44
+ maxTimeout: 2500, //ms
45
+ conditionFn: isActiveScreenAvailableToFocus,
46
+ });
47
+ };
48
+
49
+ export const getActiveScreen = (focusableTree) => {
50
+ return last(focusableTree.root.children);
51
+ };
52
+
53
+ export const getContentNode = (focusableTree) => {
54
+ const content = find(
55
+ (child) => startsWith("content", child.id),
56
+ getActiveScreen(focusableTree)?.children
57
+ );
58
+
59
+ return content;
60
+ };
61
+
62
+ export const waitForContent = (focusableTree) => {
63
+ const contentHasAnyChildren = (): boolean => {
64
+ const countOfChildren = pathOr(
65
+ 0,
66
+ ["children", "length"],
67
+ getContentNode(focusableTree)
68
+ );
69
+
70
+ return countOfChildren > 0;
71
+ };
72
+
73
+ return waitUntil({
74
+ maxTimeout: 2500, //ms
75
+ conditionFn: contentHasAnyChildren,
76
+ });
77
+ };
@@ -1,6 +1,6 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
- exports[`keyCode returns an object for a given key event 1`] = `
3
+ exports[`keyCodes keyCode returns an object for a given key event 1`] = `
4
4
  Object {
5
5
  "direction": [Function],
6
6
  "matches": [Function],
@@ -1,3 +1,7 @@
1
+ const { Platform } = require("react-native");
2
+
3
+ Platform.OS = "web";
4
+
1
5
  import { directionHelper } from "../../directionHelper";
2
6
 
3
7
  const globalTizen = {
@@ -6,105 +10,96 @@ const globalTizen = {
6
10
  },
7
11
  };
8
12
 
9
- jest.mock("react-native", () => {
10
- return { Platform: { OS: "web" } };
11
- });
12
-
13
- describe("keyCode", () => {
14
- const { keyCode } = require("../index");
15
- const { KEYS } = require("../keys.web");
16
-
17
- it("returns an object for a given key event", () => {
18
- expect(keyCode(KEYS.Enter)).toMatchSnapshot();
19
- });
20
-
21
- it("has a matcher method for a single key", () => {
22
- expect(keyCode(KEYS.Enter).matches("Enter")).toBeTruthy();
23
- expect(keyCode(KEYS.Enter).matches(KEYS.Enter)).toBeTruthy();
24
- expect(keyCode(KEYS.Back).matches("Enter")).toBeFalsy();
25
- });
26
-
27
- it("has matcher for multiple keys", () => {
28
- expect(
29
- keyCode(KEYS.Enter).matchesAny("Enter", "Back", "ArrowUp")
30
- ).toBeTruthy();
13
+ describe("keyCodes", () => {
14
+ describe("keyCode", () => {
15
+ const { keyCode } = require("..");
16
+ const { KEYS } = require("../keys.web");
31
17
 
32
- expect(
33
- keyCode(KEYS.Enter).matchesAny("Back", "ArrowUp", "ArrowDown")
34
- ).toBeFalsy();
35
- });
18
+ it("returns an object for a given key event", () => {
19
+ expect(keyCode(KEYS.Enter)).toMatchSnapshot();
20
+ });
36
21
 
37
- it("returns the direction if it matches an arrow key", () => {
38
- expect(keyCode(KEYS.Enter).direction()).toBe(null);
22
+ it("has a matcher method for a single key", () => {
23
+ expect(keyCode(KEYS.Enter).matches("Enter")).toBeTruthy();
24
+ expect(keyCode(KEYS.Enter).matches(KEYS.Enter)).toBeTruthy();
25
+ expect(keyCode(KEYS.Back).matches("Enter")).toBeFalsy();
26
+ });
39
27
 
40
- expect(keyCode(KEYS.ArrowDown).direction()).toEqual(
41
- directionHelper(KEYS.ArrowDown)
42
- );
43
- });
28
+ it("has matcher for multiple keys", () => {
29
+ expect(
30
+ keyCode(KEYS.Enter).matchesAny("Enter", "Back", "ArrowUp")
31
+ ).toBeTruthy();
44
32
 
45
- describe("in web env", () => {
46
- it("uses the proper keyCode for the environment", () => {
47
- expect(keyCode({ keyCode: 13 }).matches(KEYS.Enter)).toBeTruthy();
33
+ expect(
34
+ keyCode(KEYS.Enter).matchesAny("Back", "ArrowUp", "ArrowDown")
35
+ ).toBeFalsy();
48
36
  });
49
- });
50
- });
51
37
 
52
- describe("in Tizen Env", () => {
53
- jest.mock("react-native", () => {
54
- return { Platform: { OS: "samsung_tv" } };
55
- });
38
+ it("returns the direction if it matches an arrow key", () => {
39
+ expect(keyCode(KEYS.Enter).direction()).toBe(null);
56
40
 
57
- beforeEach(() => {
58
- global.tizen = globalTizen;
59
- jest.resetModules();
60
- });
41
+ expect(keyCode(KEYS.ArrowDown).direction()).toEqual(
42
+ directionHelper(KEYS.ArrowDown)
43
+ );
44
+ });
61
45
 
62
- afterEach(() => {
63
- global.tizen = undefined;
46
+ describe("in web env", () => {
47
+ it("uses the proper keyCode for the environment", () => {
48
+ expect(keyCode({ keyCode: 13 }).matches(KEYS.Enter)).toBeTruthy();
49
+ });
50
+ });
64
51
  });
65
52
 
66
- it("uses the proper keyCode for the environment", () => {
67
- const { keyCode } = require("../index");
68
- const { KEYS } = require("../keys.web");
53
+ describe("in Tizen Env", () => {
54
+ beforeEach(() => {
55
+ jest.resetModules();
56
+ global.tizen = globalTizen;
57
+ });
69
58
 
70
- expect(globalTizen.tvinputdevice.registerKeyBatch).toHaveBeenCalledWith([
71
- "Back",
72
- "MediaPlay",
73
- "MediaPlayPause",
74
- "MediaPause",
75
- "MediaFastForward",
76
- "MediaRewind",
77
- "MediaStop",
78
- "Exit",
79
- "0",
80
- "1",
81
- "2",
82
- "3",
83
- "4",
84
- "5",
85
- "6",
86
- "7",
87
- "8",
88
- "9",
89
- ]);
90
-
91
- expect(keyCode({ keyCode: 10009 }).matches(KEYS.Back)).toBeTruthy();
92
- });
93
- });
59
+ afterEach(() => {
60
+ global.tizen = undefined;
61
+ });
94
62
 
95
- describe("in WebOS Env", () => {
96
- jest.mock("react-native", () => {
97
- return { Platform: { OS: "lg_tv" } };
63
+ it("uses the proper keyCode for the environment", () => {
64
+ Platform.OS = "samsung_tv";
65
+ const { keyCode } = require("../");
66
+ const { KEYS } = require("../keys.web");
67
+
68
+ expect(globalTizen.tvinputdevice.registerKeyBatch).toHaveBeenCalledWith([
69
+ "MediaPlay",
70
+ "MediaPlayPause",
71
+ "MediaPause",
72
+ "MediaFastForward",
73
+ "MediaRewind",
74
+ "MediaStop",
75
+ "Exit",
76
+ "0",
77
+ "1",
78
+ "2",
79
+ "3",
80
+ "4",
81
+ "5",
82
+ "6",
83
+ "7",
84
+ "8",
85
+ "9",
86
+ ]);
87
+
88
+ expect(keyCode({ keyCode: 10009 }).matches(KEYS.Back)).toBeTruthy();
89
+ });
98
90
  });
99
91
 
100
- beforeEach(() => {
101
- jest.resetModules();
102
- });
92
+ describe("in WebOS Env", () => {
93
+ beforeEach(() => {
94
+ jest.resetModules();
95
+ });
103
96
 
104
- it("uses the proper keyCode for the environment", () => {
105
- const { keyCode } = require("../index");
106
- const { KEYS } = require("../keys.web");
107
- // Back is reserved on LG
108
- expect(keyCode({ keyCode: 417 }).matches(KEYS.Forward)).toBeTruthy();
97
+ it("uses the proper keyCode for the environment", () => {
98
+ Platform.OS = "lg_tv";
99
+ const { keyCode } = require("..");
100
+ const { KEYS } = require("../keys.web");
101
+ // Back is reserved on LG
102
+ expect(keyCode({ keyCode: 417 }).matches(KEYS.Forward)).toBeTruthy();
103
+ });
109
104
  });
110
105
  });
@@ -1,5 +1,6 @@
1
1
  /* global tizen */
2
2
  const isTizen = typeof tizen !== "undefined";
3
+
3
4
  import * as platformKeys from "./keysByPlatform";
4
5
  import { Platform } from "react-native";
5
6
 
@@ -2,6 +2,7 @@
2
2
  import * as ReactNative from "react-native";
3
3
  import { isTV, platformSelect } from "../reactUtils";
4
4
  import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
5
+ import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
5
6
 
6
7
  import {
7
8
  QUICK_BRICK_EVENTS,
@@ -9,7 +10,6 @@ import {
9
10
  sendQuickBrickEvent,
10
11
  } from "@applicaster/zapp-react-native-bridge/QuickBrick";
11
12
  import { utilsLogger } from "../logger";
12
- import { useIsTablet } from "../reactHooks/device/useIsTablet";
13
13
 
14
14
  const { DeviceEventEmitter, NativeModules, NativeEventEmitter } = ReactNative;
15
15
 
@@ -20,6 +20,11 @@ export enum SharedPlayerCallBacksKeys {
20
20
  OnPlayerMute = "onPlayerMute",
21
21
  OnPlayerUnmute = "onPlayerUnmute",
22
22
 
23
+ onVideoFullscreenPlayerDidDismiss = "onVideoFullscreenPlayerDidDismiss",
24
+ onVideoFullscreenPlayerDidPresent = "onVideoFullscreenPlayerDidPresent",
25
+ onVideoFullscreenPlayerWillDismiss = "onVideoFullscreenPlayerWillDismiss",
26
+ onVideoFullscreenPlayerWillPresent = "onVideoFullscreenPlayerWillPresent",
27
+
23
28
  OnAdBegin = "onAdBegin",
24
29
  OnAdBreakBegin = "onAdBreakBegin",
25
30
  OnAdBreakEnd = "onAdBreakEnd",
@@ -414,6 +414,32 @@ export class Player {
414
414
  });
415
415
  };
416
416
 
417
+ onVideoFullscreenPlayerWillDismiss = () => {
418
+ this.invokeListenersUpdate({
419
+ callbackName:
420
+ SharedPlayerCallBacksKeys.onVideoFullscreenPlayerWillDismiss,
421
+ });
422
+ };
423
+
424
+ onVideoFullscreenPlayerDidDismiss = () => {
425
+ this.invokeListenersUpdate({
426
+ callbackName: SharedPlayerCallBacksKeys.onVideoFullscreenPlayerDidDismiss,
427
+ });
428
+ };
429
+
430
+ onVideoFullscreenPlayerWillPresent = () => {
431
+ this.invokeListenersUpdate({
432
+ callbackName:
433
+ SharedPlayerCallBacksKeys.onVideoFullscreenPlayerWillPresent,
434
+ });
435
+ };
436
+
437
+ onVideoFullscreenPlayerDidPresent = () => {
438
+ this.invokeListenersUpdate({
439
+ callbackName: SharedPlayerCallBacksKeys.onVideoFullscreenPlayerDidPresent,
440
+ });
441
+ };
442
+
417
443
  public invokeListenersUpdate = ({
418
444
  callbackName,
419
445
  event = {},
@@ -634,6 +660,12 @@ export class Player {
634
660
  onBufferStart: this.onBufferStart,
635
661
  onBufferComplete: this.onBufferComplete,
636
662
 
663
+ onPlayerClose: this.onPlayerClose,
664
+ onVideoFullscreenPlayerWillPresent: this.onVideoFullscreenPlayerWillPresent,
665
+ onVideoFullscreenPlayerDidPresent: this.onVideoFullscreenPlayerDidPresent,
666
+ onVideoFullscreenPlayerWillDismiss: this.onVideoFullscreenPlayerWillDismiss,
667
+ onVideoFullscreenPlayerDidDismiss: this.onVideoFullscreenPlayerDidDismiss,
668
+
637
669
  // Ads Callbacks:
638
670
  onAdBegin: this.onAdBegin,
639
671
  onAdBreakBegin: this.onAdBreakBegin,
@@ -693,4 +725,7 @@ export class Player {
693
725
  isFullScreenSupported(): boolean {
694
726
  return true;
695
727
  }
728
+
729
+ public supportsNativeControls = (): boolean => false;
730
+ public supportNativeCast = (): boolean => false;
696
731
  }
@@ -1,17 +1,31 @@
1
1
  import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
2
2
  import { findPluginByIdentifier } from "@applicaster/zapp-react-native-utils/pluginUtils";
3
3
  import { Player } from "./player";
4
+ import { PlayerRole } from "./conts";
4
5
 
5
6
  type PlayerFactoryItem = {
6
7
  Component: any;
7
8
  controller: Player;
8
9
  };
9
10
 
11
+ type PlayerFactoryProps = {
12
+ player: any;
13
+ playerId: string;
14
+ autoplay: boolean;
15
+ entry: ZappEntry;
16
+ muted: boolean;
17
+ playerPluginId: string;
18
+ screenConfig: Record<string, any>;
19
+ playerRole: PlayerRole;
20
+ };
21
+
10
22
  interface PlayerConstructor {
11
23
  new (params: any): Player;
12
24
  }
13
25
 
14
- export const playerFactory = (config: any): PlayerFactoryItem | null => {
26
+ export const playerFactory = (
27
+ config: PlayerFactoryProps
28
+ ): PlayerFactoryItem | null => {
15
29
  const IDENTIFIER = config?.playerPluginId;
16
30
 
17
31
  if (!IDENTIFIER) {
@@ -5,6 +5,7 @@ import { Player } from "./player";
5
5
  import { findNodeHandle, NativeModules } from "react-native";
6
6
  import { isApplePlatform } from "../../reactUtils";
7
7
  import { PlayerRole } from "./conts";
8
+ import { toBooleanWithDefaultFalse } from "../../booleanUtils";
8
9
 
9
10
  const RNPlayerManager = {
10
11
  QuickBrickPlayerPlugin: isApplePlatform()
@@ -291,4 +292,12 @@ export class PlayerNative extends Player {
291
292
 
292
293
  return !(isFullScreenAudioPlayer && this.isAudioItem());
293
294
  };
295
+
296
+ supportsNativeControls = (): boolean =>
297
+ toBooleanWithDefaultFalse(this.getConfig()?.["supports_native_controls"]);
298
+
299
+ supportNativeCast = (): boolean =>
300
+ toBooleanWithDefaultFalse(
301
+ this.playerPluginId === "quick-brick-player-brightcove"
302
+ );
294
303
  }
@@ -14,6 +14,7 @@ import {
14
14
  castAndFallbackColor,
15
15
  compact,
16
16
  getValue,
17
+ getColorFromData,
17
18
  } from "../";
18
19
 
19
20
  import {
@@ -23,6 +24,7 @@ import {
23
24
  EMPTY,
24
25
  FALLBACK,
25
26
  TRANSFORM,
27
+ defaultColor,
26
28
  } from "./fixtures";
27
29
 
28
30
  describe("getKeyForLabel", () => {
@@ -388,3 +390,60 @@ describe("getValue", () => {
388
390
  expect(getValue({ value })(value)).toBeNull();
389
391
  });
390
392
  });
393
+
394
+ describe("getColorFromData", () => {
395
+ it("should return value directly if valid color", () => {
396
+ const result = getColorFromData({
397
+ data: entry,
398
+ key: "background_color",
399
+ getValue: getValue(componentStyle),
400
+ defaultColor,
401
+ });
402
+
403
+ expect(result).toBe("#123456");
404
+ });
405
+
406
+ it("should return valid rgba color name", () => {
407
+ const result = getColorFromData({
408
+ data: entry,
409
+ key: "component_background_color",
410
+ getValue: getValue(componentStyle),
411
+ defaultColor,
412
+ });
413
+
414
+ expect(result).toBe("rgba(255, 255, 255, 0)");
415
+ });
416
+
417
+ it("should return path value if path value is set and is valid color", () => {
418
+ const result = getColorFromData({
419
+ data: entry,
420
+ key: VAL.colorKeyPath,
421
+ getValue: getValue(componentStyle),
422
+ defaultColor,
423
+ });
424
+
425
+ expect(result).toBe("#123456");
426
+ });
427
+
428
+ it("should return defaultColor if key value and path value are invalid", () => {
429
+ const result = getColorFromData({
430
+ data: entry,
431
+ key: VAL.invalidColorKey,
432
+ getValue: getValue(componentStyle),
433
+ defaultColor,
434
+ });
435
+
436
+ expect(result).toBe(defaultColor);
437
+ });
438
+
439
+ it("should return defaultColor if path does not exist", () => {
440
+ const result = getColorFromData({
441
+ data: entry,
442
+ key: VAL.invalidColorKeyPath,
443
+ getValue: getValue(componentStyle),
444
+ defaultColor,
445
+ });
446
+
447
+ expect(result).toBe(defaultColor);
448
+ });
449
+ });
@@ -1,5 +1,7 @@
1
1
  /* eslint-disable max-len */
2
2
 
3
+ export const defaultColor = "#ffffff";
4
+
3
5
  export const entry = {
4
6
  type: {
5
7
  value: "video",
@@ -26,7 +28,9 @@ export const entry = {
26
28
  type: "video/mp4",
27
29
  src: "https://wino-feed.s3.amazonaws.com/movieFeed/Ford+v+Ferrari.mp4",
28
30
  },
31
+ background_color: "#123456",
29
32
  extensions: {
33
+ background_color: "#123456",
30
34
  free: false,
31
35
  duration: 9000,
32
36
  section: "New Releases",
@@ -46,7 +50,11 @@ export const componentStyle = {
46
50
  horizontal_list_cell_width: "400",
47
51
  cell_plugin_configuration_id: "267bd05a-08bd-4675-bdd3-9b73899773f0",
48
52
  horizontal_list_scaled_switch: false,
53
+ background_color: "#123456",
49
54
  component_background_color: "rgba(255, 255, 255, 0)",
55
+ background_color_1: "extensions.background_color",
56
+ background_color_2: "invalidColorKey",
57
+ background_color_3: "extensions.invalidColorKeyPath",
50
58
  component_padding_left: "",
51
59
  horizontal_list_next_offset: 20,
52
60
  component_margin_bottom: "",
@@ -93,6 +101,11 @@ export const VAL = {
93
101
  upper: "STRING",
94
102
  width: 400,
95
103
  height: 225,
104
+ colorKey: "background_color",
105
+ colorKeyRgba: "component_background_color",
106
+ colorKeyPath: "background_color_1",
107
+ invalidColorKey: "background_color_2",
108
+ invalidColorKeyPath: "background_color_3",
96
109
  };
97
110
 
98
111
  export const TRANSFORM = {
@@ -1,5 +1,6 @@
1
1
  import * as R from "ramda";
2
2
  import dayjs from "dayjs";
3
+ import validateColor from "validate-color";
3
4
 
4
5
  import { toNumberWithDefault, toNumberWithDefaultZero } from "../numberUtils";
5
6
  import { transformColorCode as fixColorHexCode } from "../transform";
@@ -181,8 +182,8 @@ export const getHeight = (width, aspectRatio) => {
181
182
  * @returns {string} duration
182
183
  */
183
184
 
184
- export const getDurationInMinutes = (seconds: number): string => {
185
- if (seconds === (null || undefined)) return "00:00";
185
+ export const getDurationInMinutes = (seconds?: number): string => {
186
+ if (!seconds) return "00:00";
186
187
  const minutes = Math.floor(Number(seconds) / 60);
187
188
  const secondsReminder = Math.floor(Number(seconds)) % 60;
188
189
  const pad = (num: number): string => `${num < 10 ? "0" : ""}${num}`;
@@ -343,6 +344,19 @@ export const ifElseFocused = R.curry(
343
344
  state === "focused" ? focusedValue : defaultValue
344
345
  );
345
346
 
347
+ export const ifElseSelected = R.curry(
348
+ (state: string, selectedValue, defaultValue) => {
349
+ switch (state) {
350
+ case "selected":
351
+ return selectedValue;
352
+ case "focused_selected":
353
+ return selectedValue;
354
+ default:
355
+ return defaultValue;
356
+ }
357
+ }
358
+ );
359
+
346
360
  /**
347
361
  * Map textAlign values to their corresponding alignSelf values
348
362
  * This is so that we could use the same field for both properties
@@ -430,3 +444,33 @@ export const toResizeMode = (
430
444
  resizeMode: "contain",
431
445
  };
432
446
  };
447
+
448
+ type GetColorFromData = {
449
+ data: Record<string, any>;
450
+ key: string;
451
+ getValue: Function;
452
+ defaultColor: string;
453
+ };
454
+
455
+ export const getColorFromData = ({
456
+ data,
457
+ key,
458
+ getValue,
459
+ defaultColor,
460
+ }: GetColorFromData): string => {
461
+ const value = getValue(key);
462
+
463
+ // Temporary hack to fix color validation when alpha is floating point number
464
+ // https://github.com/dreamyguy/validate-color/issues/44
465
+ if (validateColor(value.replace(".00", ""))) {
466
+ return value;
467
+ }
468
+
469
+ const pathValue = R.path(value.split("."), data);
470
+
471
+ if (pathValue && validateColor(pathValue)) {
472
+ return pathValue;
473
+ }
474
+
475
+ return defaultColor;
476
+ };
@@ -86,7 +86,7 @@ export const isFocusable = (component: ZappEntry | ZappUIComponent | any) =>
86
86
 
87
87
  type Props = {
88
88
  component: ZappUIComponent;
89
- zappPipesData: ZappPipesData;
89
+ zappPipesData?: ZappPipesData;
90
90
  } & Record<string, any>;
91
91
 
92
92
  export const useIsFocusable = (props: Props): void => {
@@ -1,6 +1,6 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
- exports[`subscriber can be invoked without args 1`] = `
3
+ exports[`functionUtils subscriber can be invoked without args 1`] = `
4
4
  Object {
5
5
  "handlers": Object {},
6
6
  "invokeHandler": [Function],
@@ -9,7 +9,7 @@ Object {
9
9
  }
10
10
  `;
11
11
 
12
- exports[`subscriber returns the original object with additional methods 1`] = `
12
+ exports[`functionUtils subscriber returns the original object with additional methods 1`] = `
13
13
  Object {
14
14
  "foo": "bar",
15
15
  "handlers": Object {},