Browse Source

[BREAKING] Event registry rework (#3497)

[BREAKING] Call Navigation.events().bindComponent(this) to listen to lifecycle events

This commit introduces breaking changes to the way components listen to RNN events.


Background

Up until now, components could handle navigation events by implemented a set of callbacks:
* componentDidAppear
* componentDidDisappear
* onNavigationButtonPressed
* onSearchBarUpdated
* onSearchBarCancelPressed

While this worked fine for the most part, this was completely broken for HOCs as RNN invoked these methods only on the registered component (top most HOC), leaving it to the user to propagate these events down the HOC chain. See the discussion in #1642 for more details.


Solution

In order to support HOC use case, we're introducing a new api which will let any Component bind itself to receive navigation events:

```js
class LifecycleScreen extends Component {
  constructor(props) {
    super(props);
    this.subscription = Navigation.events().bindComponent(this);
  }

  componentWillUnmount() {
    // The subscription is removed automatically when components unmount, but they can be explicitly removed as well by calling `this.subscription.remove(); `
  }
}
```

It's still the users responsibility to propagate the `componentId` down the HOC chain, but by binding a component to RNN, it will be able to handle events as expected. Multiple components can be bound for the same `componentId`.


Consolidate event names

* onNavigationButtonPressed -> navigationButtonPressed
* onSearchBarUpdated -> searchBarUpdated
* onSearchBarCancelPressed -> searchBarCancelPressed
Daniel Zlotin 6 years ago
parent
commit
b5a3b54fa6
47 changed files with 944 additions and 888 deletions
  1. 21
    13
      docs/api/Commands.md
  2. 51
    3
      docs/api/ComponentEventsObserver.md
  3. 2
    2
      docs/api/ComponentRegistry.md
  4. 32
    0
      docs/api/Constants.md
  5. 0
    146
      docs/api/Element.md
  6. 44
    12
      docs/api/EventsRegistry.md
  7. 15
    7
      docs/api/LayoutTreeParser.md
  8. 1
    0
      docs/api/LayoutType.md
  9. 19
    11
      docs/api/NativeCommandsSender.md
  10. 31
    7
      docs/api/NativeEventsReceiver.md
  11. 27
    2
      docs/api/Navigation.md
  12. 1
    0
      docs/api/README.md
  13. 5
    21
      docs/api/Store.md
  14. 1
    0
      docs/api/_sidebar.md
  15. 2
    2
      docs/docs/events.md
  16. 30
    36
      lib/android/app/src/main/java/com/reactnativenavigation/react/EventEmitter.java
  17. 0
    3
      lib/ios/RNNCommandsHandler.m
  18. 1
    3
      lib/ios/RNNEventEmitter.h
  19. 53
    31
      lib/ios/RNNEventEmitter.m
  20. 11
    11
      lib/ios/RNNRootViewController.m
  21. 1
    2
      lib/ios/RNNTabBarController.m
  22. 5
    6
      lib/src/Navigation.ts
  23. 29
    9
      lib/src/adapters/NativeEventsReceiver.ts
  24. 1
    1
      lib/src/components/ComponentRegistry.test.tsx
  25. 4
    6
      lib/src/components/ComponentRegistry.ts
  26. 51
    147
      lib/src/components/ComponentWrapper.test.tsx
  27. 6
    43
      lib/src/components/ComponentWrapper.tsx
  28. 1
    10
      lib/src/components/Store.test.ts
  29. 0
    10
      lib/src/components/Store.ts
  30. 1
    1
      lib/src/events/CommandsObserver.ts
  31. 0
    139
      lib/src/events/ComponentEventsObserver.test.ts
  32. 181
    0
      lib/src/events/ComponentEventsObserver.test.tsx
  33. 66
    41
      lib/src/events/ComponentEventsObserver.ts
  34. 0
    99
      lib/src/events/EventsRegistry.test.ts
  35. 100
    0
      lib/src/events/EventsRegistry.test.tsx
  36. 36
    11
      lib/src/events/EventsRegistry.ts
  37. 24
    0
      lib/src/interfaces/ComponentEvents.ts
  38. 10
    0
      lib/src/interfaces/Events.ts
  39. 3
    0
      playground/src/screens/CustomRoundedButton.js
  40. 3
    0
      playground/src/screens/CustomTopBar.js
  41. 4
    2
      playground/src/screens/LifecycleScreen.js
  42. 9
    4
      playground/src/screens/OptionsScreen.js
  43. 19
    22
      playground/src/screens/PushedScreen.js
  44. 3
    2
      playground/src/screens/SearchScreen.js
  45. 15
    13
      playground/src/screens/StaticLifecycleOverlay.js
  46. 3
    0
      playground/src/screens/TopBarBackground.js
  47. 22
    10
      scripts/gen-docs/ClassParser.ts

+ 21
- 13
docs/api/Commands.md View File

@@ -4,7 +4,7 @@
4 4
 
5 5
 `setRoot(simpleApi: any): any`
6 6
 
7
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/Commands.ts#L13)
7
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/Commands.ts#L15)
8 8
 
9 9
 ---
10 10
 
@@ -12,7 +12,7 @@
12 12
 
13 13
 `setDefaultOptions(options: any): void`
14 14
 
15
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/Commands.ts#L23)
15
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/Commands.ts#L38)
16 16
 
17 17
 ---
18 18
 
@@ -20,7 +20,7 @@
20 20
 
21 21
 `mergeOptions(componentId: any, options: any): void`
22 22
 
23
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/Commands.ts#L31)
23
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/Commands.ts#L46)
24 24
 
25 25
 ---
26 26
 
@@ -28,7 +28,7 @@
28 28
 
29 29
 `showModal(simpleApi: any): any`
30 30
 
31
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/Commands.ts#L39)
31
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/Commands.ts#L54)
32 32
 
33 33
 ---
34 34
 
@@ -36,7 +36,7 @@
36 36
 
37 37
 `dismissModal(componentId: any): any`
38 38
 
39
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/Commands.ts#L49)
39
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/Commands.ts#L65)
40 40
 
41 41
 ---
42 42
 
@@ -44,7 +44,7 @@
44 44
 
45 45
 `dismissAllModals(): any`
46 46
 
47
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/Commands.ts#L55)
47
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/Commands.ts#L72)
48 48
 
49 49
 ---
50 50
 
@@ -52,7 +52,7 @@
52 52
 
53 53
 `push(componentId: any, simpleApi: any): any`
54 54
 
55
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/Commands.ts#L61)
55
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/Commands.ts#L79)
56 56
 
57 57
 ---
58 58
 
@@ -60,7 +60,7 @@
60 60
 
61 61
 `pop(componentId: any, options: any): any`
62 62
 
63
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/Commands.ts#L72)
63
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/Commands.ts#L91)
64 64
 
65 65
 ---
66 66
 
@@ -68,7 +68,7 @@
68 68
 
69 69
 `popTo(componentId: any): any`
70 70
 
71
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/Commands.ts#L78)
71
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/Commands.ts#L98)
72 72
 
73 73
 ---
74 74
 
@@ -76,7 +76,7 @@
76 76
 
77 77
 `popToRoot(componentId: any): any`
78 78
 
79
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/Commands.ts#L84)
79
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/Commands.ts#L105)
80 80
 
81 81
 ---
82 82
 
@@ -84,7 +84,7 @@
84 84
 
85 85
 `setStackRoot(componentId: any, simpleApi: any): any`
86 86
 
87
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/Commands.ts#L90)
87
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/Commands.ts#L112)
88 88
 
89 89
 ---
90 90
 
@@ -92,7 +92,7 @@
92 92
 
93 93
 `showOverlay(simpleApi: any): any`
94 94
 
95
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/Commands.ts#L101)
95
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/Commands.ts#L124)
96 96
 
97 97
 ---
98 98
 
@@ -100,7 +100,15 @@
100 100
 
101 101
 `dismissOverlay(componentId: any): any`
102 102
 
103
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/Commands.ts#L112)
103
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/Commands.ts#L136)
104
+
105
+---
106
+
107
+## getLaunchArgs
108
+
109
+`getLaunchArgs(): any`
110
+
111
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/Commands.ts#L143)
104 112
 
105 113
 ---
106 114
 

+ 51
- 3
docs/api/ComponentEventsObserver.md View File

@@ -1,10 +1,58 @@
1 1
 # ComponentEventsObserver
2 2
 
3
-## registerForAllComponents
3
+## registerOnceForAllComponentEvents
4 4
 
5
-`registerForAllComponents(): void`
5
+`registerOnceForAllComponentEvents(): void`
6 6
 
7
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/events/ComponentEventsObserver.ts#L13)
7
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/events/ComponentEventsObserver.ts#L25)
8
+
9
+---
10
+
11
+## bindComponent
12
+
13
+`bindComponent(component: Component<any>): EventSubscription`
14
+
15
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/events/ComponentEventsObserver.ts#L35)
16
+
17
+---
18
+
19
+## notifyComponentDidAppear
20
+
21
+`notifyComponentDidAppear(event: ComponentDidAppearEvent): void`
22
+
23
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/events/ComponentEventsObserver.ts#L46)
24
+
25
+---
26
+
27
+## notifyComponentDidDisappear
28
+
29
+`notifyComponentDidDisappear(event: ComponentDidDisappearEvent): void`
30
+
31
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/events/ComponentEventsObserver.ts#L50)
32
+
33
+---
34
+
35
+## notifyNavigationButtonPressed
36
+
37
+`notifyNavigationButtonPressed(event: NavigationButtonPressedEvent): void`
38
+
39
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/events/ComponentEventsObserver.ts#L54)
40
+
41
+---
42
+
43
+## notifySearchBarUpdated
44
+
45
+`notifySearchBarUpdated(event: SearchBarUpdatedEvent): void`
46
+
47
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/events/ComponentEventsObserver.ts#L58)
48
+
49
+---
50
+
51
+## notifySearchBarCancelPressed
52
+
53
+`notifySearchBarCancelPressed(event: SearchBarCancelPressedEvent): void`
54
+
55
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/events/ComponentEventsObserver.ts#L62)
8 56
 
9 57
 ---
10 58
 

+ 2
- 2
docs/api/ComponentRegistry.md View File

@@ -2,9 +2,9 @@
2 2
 
3 3
 ## registerComponent
4 4
 
5
-`registerComponent(componentName: string, getComponentClassFunc: ComponentProvider): void`
5
+`registerComponent(componentName: string, getComponentClassFunc: ComponentProvider): ComponentType<any>`
6 6
 
7
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/components/ComponentRegistry.ts#L11)
7
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/components/ComponentRegistry.ts#L12)
8 8
 
9 9
 ---
10 10
 

+ 32
- 0
docs/api/Constants.md View File

@@ -0,0 +1,32 @@
1
+# Constants
2
+
3
+## backButtonId
4
+
5
+`backButtonId (string)`
6
+
7
+---
8
+## bottomTabsHeight
9
+
10
+`bottomTabsHeight (number)`
11
+
12
+---
13
+## statusBarHeight
14
+
15
+`statusBarHeight (number)`
16
+
17
+---
18
+## topBarHeight
19
+
20
+`topBarHeight (number)`
21
+
22
+---
23
+
24
+## get
25
+
26
+`get(): Promise<any>`
27
+
28
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/adapters/Constants.ts#L4)
29
+
30
+---
31
+
32
+

+ 0
- 146
docs/api/Element.md View File

@@ -29,150 +29,4 @@
29 29
 
30 30
 ---
31 31
 
32
-## setState
33
-
34
-`setState(state: function | S | object, callback: function): void`
35
-
36
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src//Users/danielzlotin/dev/react-native-navigation/node_modules/@types/react/index.d.ts#L287)
37
-
38
----
39
-
40
-## forceUpdate
41
-
42
-`forceUpdate(callBack: function): void`
43
-
44
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src//Users/danielzlotin/dev/react-native-navigation/node_modules/@types/react/index.d.ts#L292)
45
-
46
----
47
-
48
-## componentDidMount
49
-
50
-`componentDidMount(): void`
51
-
52
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src//Users/danielzlotin/dev/react-native-navigation/node_modules/@types/react/index.d.ts#L376)
53
-
54
-Called immediately after a compoment is mounted. Setting state here will trigger re-rendering.
55
-
56
----
57
-
58
-## shouldComponentUpdate
59
-
60
-`shouldComponentUpdate(nextProps: Readonly<object>, nextState: Readonly<any>, nextContext: any): boolean`
61
-
62
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src//Users/danielzlotin/dev/react-native-navigation/node_modules/@types/react/index.d.ts#L387)
63
-
64
-Called to determine whether the change in props and state should trigger a re-render.
65
-
66
----
67
-
68
-## componentWillUnmount
69
-
70
-`componentWillUnmount(): void`
71
-
72
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src//Users/danielzlotin/dev/react-native-navigation/node_modules/@types/react/index.d.ts#L392)
73
-
74
-Called immediately before a component is destroyed. Perform any necessary cleanup in this method, such as
75
-cancelled network requests, or cleaning up any DOM elements created in `componentDidMount`.
76
-
77
----
78
-
79
-## componentDidCatch
80
-
81
-`componentDidCatch(error: Error, errorInfo: ErrorInfo): void`
82
-
83
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src//Users/danielzlotin/dev/react-native-navigation/node_modules/@types/react/index.d.ts#L397)
84
-
85
-Catches exceptions generated in descendant components. Unhandled exceptions will cause
86
-the entire component tree to unmount.
87
-
88
----
89
-
90
-## getSnapshotBeforeUpdate
91
-
92
-`getSnapshotBeforeUpdate(prevProps: Readonly<object>, prevState: Readonly<any>): SS | null`
93
-
94
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src//Users/danielzlotin/dev/react-native-navigation/node_modules/@types/react/index.d.ts#L423)
95
-
96
-Runs before React applies the result of `render` to the document, and
97
-returns an object to be given to componentDidUpdate. Useful for saving
98
-things such as scroll position before `render` causes changes to it.
99
-
100
----
101
-
102
-## componentDidUpdate
103
-
104
-`componentDidUpdate(prevProps: Readonly<object>, prevState: Readonly<any>, snapshot: SS): void`
105
-
106
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src//Users/danielzlotin/dev/react-native-navigation/node_modules/@types/react/index.d.ts#L429)
107
-
108
-Called immediately after updating occurs. Not called for the initial render.
109
-
110
----
111
-
112
-## componentWillMount
113
-
114
-`componentWillMount(): void`
115
-
116
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src//Users/danielzlotin/dev/react-native-navigation/node_modules/@types/react/index.d.ts#L444)
117
-
118
-Called immediately before mounting occurs, and before `Component#render`.
119
-Avoid introducing any side-effects or subscriptions in this method.
120
-
121
----
122
-
123
-## UNSAFE_componentWillMount
124
-
125
-`UNSAFE_componentWillMount(): void`
126
-
127
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src//Users/danielzlotin/dev/react-native-navigation/node_modules/@types/react/index.d.ts#L458)
128
-
129
-Called immediately before mounting occurs, and before `Component#render`.
130
-Avoid introducing any side-effects or subscriptions in this method.
131
-
132
----
133
-
134
-## componentWillReceiveProps
135
-
136
-`componentWillReceiveProps(nextProps: Readonly<object>, nextContext: any): void`
137
-
138
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src//Users/danielzlotin/dev/react-native-navigation/node_modules/@types/react/index.d.ts#L473)
139
-
140
-Called when the component may be receiving new props.
141
-React may call this even if props have not changed, so be sure to compare new and existing
142
-props if you only want to handle changes.
143
-
144
----
145
-
146
-## UNSAFE_componentWillReceiveProps
147
-
148
-`UNSAFE_componentWillReceiveProps(nextProps: Readonly<object>, nextContext: any): void`
149
-
150
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src//Users/danielzlotin/dev/react-native-navigation/node_modules/@types/react/index.d.ts#L490)
151
-
152
-Called when the component may be receiving new props.
153
-React may call this even if props have not changed, so be sure to compare new and existing
154
-props if you only want to handle changes.
155
-
156
----
157
-
158
-## componentWillUpdate
159
-
160
-`componentWillUpdate(nextProps: Readonly<object>, nextState: Readonly<any>, nextContext: any): void`
161
-
162
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src//Users/danielzlotin/dev/react-native-navigation/node_modules/@types/react/index.d.ts#L503)
163
-
164
-Called immediately before rendering when new props or state is received. Not called for the initial render.
165
-
166
----
167
-
168
-## UNSAFE_componentWillUpdate
169
-
170
-`UNSAFE_componentWillUpdate(nextProps: Readonly<object>, nextState: Readonly<any>, nextContext: any): void`
171
-
172
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src//Users/danielzlotin/dev/react-native-navigation/node_modules/@types/react/index.d.ts#L518)
173
-
174
-Called immediately before rendering when new props or state is received. Not called for the initial render.
175
-
176
----
177
-
178 32
 

+ 44
- 12
docs/api/EventsRegistry.md View File

@@ -4,7 +4,7 @@
4 4
 
5 5
 `registerAppLaunchedListener(callback: function): EventSubscription`
6 6
 
7
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/events/EventsRegistry.ts#L8)
7
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/events/EventsRegistry.ts#L17)
8 8
 
9 9
 ---
10 10
 
@@ -12,7 +12,7 @@
12 12
 
13 13
 `registerComponentDidAppearListener(callback: function): EventSubscription`
14 14
 
15
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/events/EventsRegistry.ts#L12)
15
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/events/EventsRegistry.ts#L21)
16 16
 
17 17
 ---
18 18
 
@@ -20,31 +20,63 @@
20 20
 
21 21
 `registerComponentDidDisappearListener(callback: function): EventSubscription`
22 22
 
23
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/events/EventsRegistry.ts#L16)
23
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/events/EventsRegistry.ts#L25)
24 24
 
25 25
 ---
26 26
 
27
-## registerCommandListener
27
+## registerCommandCompletedListener
28 28
 
29
-`registerCommandListener(callback: function): EventSubscription`
29
+`registerCommandCompletedListener(callback: function): EventSubscription`
30 30
 
31
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/events/EventsRegistry.ts#L20)
31
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/events/EventsRegistry.ts#L29)
32 32
 
33 33
 ---
34 34
 
35
-## registerCommandCompletedListener
35
+## registerBottomTabSelectedListener
36 36
 
37
-`registerCommandCompletedListener(callback: function): EventSubscription`
37
+`registerBottomTabSelectedListener(callback: function): EventSubscription`
38
+
39
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/events/EventsRegistry.ts#L33)
40
+
41
+---
42
+
43
+## registerNavigationButtonPressedListener
44
+
45
+`registerNavigationButtonPressedListener(callback: function): EventSubscription`
46
+
47
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/events/EventsRegistry.ts#L37)
48
+
49
+---
50
+
51
+## registerSearchBarUpdatedListener
52
+
53
+`registerSearchBarUpdatedListener(callback: function): EventSubscription`
54
+
55
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/events/EventsRegistry.ts#L41)
56
+
57
+---
58
+
59
+## registerSearchBarCancelPressedListener
60
+
61
+`registerSearchBarCancelPressedListener(callback: function): EventSubscription`
62
+
63
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/events/EventsRegistry.ts#L45)
64
+
65
+---
66
+
67
+## registerCommandListener
68
+
69
+`registerCommandListener(callback: function): EventSubscription`
38 70
 
39
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/events/EventsRegistry.ts#L24)
71
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/events/EventsRegistry.ts#L49)
40 72
 
41 73
 ---
42 74
 
43
-## registerNativeEventListener
75
+## bindComponent
44 76
 
45
-`registerNativeEventListener(callback: function): EventSubscription`
77
+`bindComponent(component: Component<any>): EventSubscription`
46 78
 
47
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/events/EventsRegistry.ts#L28)
79
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/events/EventsRegistry.ts#L53)
48 80
 
49 81
 ---
50 82
 

+ 15
- 7
docs/api/LayoutTreeParser.md View File

@@ -12,7 +12,7 @@
12 12
 
13 13
 `_topTabs(api: any): LayoutNode`
14 14
 
15
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/LayoutTreeParser.ts#L27)
15
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/LayoutTreeParser.ts#L29)
16 16
 
17 17
 ---
18 18
 
@@ -20,7 +20,7 @@
20 20
 
21 21
 `_sideMenu(api: any): LayoutNode`
22 22
 
23
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/LayoutTreeParser.ts#L36)
23
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/LayoutTreeParser.ts#L38)
24 24
 
25 25
 ---
26 26
 
@@ -28,7 +28,7 @@
28 28
 
29 29
 `_sideMenuChildren(api: any): LayoutNode[]`
30 30
 
31
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/LayoutTreeParser.ts#L45)
31
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/LayoutTreeParser.ts#L47)
32 32
 
33 33
 ---
34 34
 
@@ -36,7 +36,7 @@
36 36
 
37 37
 `_bottomTabs(api: any): LayoutNode`
38 38
 
39
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/LayoutTreeParser.ts#L75)
39
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/LayoutTreeParser.ts#L77)
40 40
 
41 41
 ---
42 42
 
@@ -44,7 +44,7 @@
44 44
 
45 45
 `_stack(api: any): LayoutNode`
46 46
 
47
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/LayoutTreeParser.ts#L84)
47
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/LayoutTreeParser.ts#L86)
48 48
 
49 49
 ---
50 50
 
@@ -52,7 +52,7 @@
52 52
 
53 53
 `_component(api: any): LayoutNode`
54 54
 
55
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/LayoutTreeParser.ts#L93)
55
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/LayoutTreeParser.ts#L95)
56 56
 
57 57
 ---
58 58
 
@@ -60,7 +60,15 @@
60 60
 
61 61
 `_externalComponent(api: any): LayoutNode`
62 62
 
63
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/LayoutTreeParser.ts#L102)
63
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/LayoutTreeParser.ts#L104)
64
+
65
+---
66
+
67
+## _splitView
68
+
69
+`_splitView(api: any): LayoutNode`
70
+
71
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/commands/LayoutTreeParser.ts#L113)
64 72
 
65 73
 ---
66 74
 

+ 1
- 0
docs/api/LayoutType.md View File

@@ -7,5 +7,6 @@
7 7
 - SideMenuLeft
8 8
 - SideMenuRight
9 9
 - SideMenuRoot
10
+- SplitView
10 11
 - Stack
11 12
 - TopTabs

+ 19
- 11
docs/api/NativeCommandsSender.md View File

@@ -2,7 +2,7 @@
2 2
 
3 3
 ## setRoot
4 4
 
5
-`setRoot(layoutTree: object): any`
5
+`setRoot(commandId: string, layout: object): any`
6 6
 
7 7
 [source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/adapters/NativeCommandsSender.ts#L9)
8 8
 
@@ -26,7 +26,7 @@
26 26
 
27 27
 ## push
28 28
 
29
-`push(onComponentId: string, layout: object): any`
29
+`push(commandId: string, onComponentId: string, layout: object): any`
30 30
 
31 31
 [source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/adapters/NativeCommandsSender.ts#L21)
32 32
 
@@ -34,7 +34,7 @@
34 34
 
35 35
 ## pop
36 36
 
37
-`pop(componentId: string, options: object): any`
37
+`pop(commandId: string, componentId: string, options: object): any`
38 38
 
39 39
 [source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/adapters/NativeCommandsSender.ts#L25)
40 40
 
@@ -42,7 +42,7 @@
42 42
 
43 43
 ## popTo
44 44
 
45
-`popTo(componentId: string): any`
45
+`popTo(commandId: string, componentId: string): any`
46 46
 
47 47
 [source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/adapters/NativeCommandsSender.ts#L29)
48 48
 
@@ -50,7 +50,7 @@
50 50
 
51 51
 ## popToRoot
52 52
 
53
-`popToRoot(componentId: string): any`
53
+`popToRoot(commandId: string, componentId: string): any`
54 54
 
55 55
 [source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/adapters/NativeCommandsSender.ts#L33)
56 56
 
@@ -58,7 +58,7 @@
58 58
 
59 59
 ## setStackRoot
60 60
 
61
-`setStackRoot(onComponentId: string, layout: object): any`
61
+`setStackRoot(commandId: string, onComponentId: string, layout: object): any`
62 62
 
63 63
 [source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/adapters/NativeCommandsSender.ts#L37)
64 64
 
@@ -66,7 +66,7 @@
66 66
 
67 67
 ## showModal
68 68
 
69
-`showModal(layout: object): any`
69
+`showModal(commandId: string, layout: object): any`
70 70
 
71 71
 [source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/adapters/NativeCommandsSender.ts#L41)
72 72
 
@@ -74,7 +74,7 @@
74 74
 
75 75
 ## dismissModal
76 76
 
77
-`dismissModal(componentId: string): any`
77
+`dismissModal(commandId: string, componentId: string): any`
78 78
 
79 79
 [source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/adapters/NativeCommandsSender.ts#L45)
80 80
 
@@ -82,7 +82,7 @@
82 82
 
83 83
 ## dismissAllModals
84 84
 
85
-`dismissAllModals(): any`
85
+`dismissAllModals(commandId: string): any`
86 86
 
87 87
 [source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/adapters/NativeCommandsSender.ts#L49)
88 88
 
@@ -90,7 +90,7 @@
90 90
 
91 91
 ## showOverlay
92 92
 
93
-`showOverlay(layout: object): any`
93
+`showOverlay(commandId: string, layout: object): any`
94 94
 
95 95
 [source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/adapters/NativeCommandsSender.ts#L53)
96 96
 
@@ -98,10 +98,18 @@
98 98
 
99 99
 ## dismissOverlay
100 100
 
101
-`dismissOverlay(componentId: string): any`
101
+`dismissOverlay(commandId: string, componentId: string): any`
102 102
 
103 103
 [source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/adapters/NativeCommandsSender.ts#L57)
104 104
 
105 105
 ---
106 106
 
107
+## getLaunchArgs
108
+
109
+`getLaunchArgs(commandId: string): any`
110
+
111
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/adapters/NativeCommandsSender.ts#L61)
112
+
113
+---
114
+
107 115
 

+ 31
- 7
docs/api/NativeEventsReceiver.md View File

@@ -4,7 +4,7 @@
4 4
 
5 5
 `registerAppLaunchedListener(callback: function): EventSubscription`
6 6
 
7
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/adapters/NativeEventsReceiver.ts#L10)
7
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/adapters/NativeEventsReceiver.ts#L28)
8 8
 
9 9
 ---
10 10
 
@@ -12,7 +12,7 @@
12 12
 
13 13
 `registerComponentDidAppearListener(callback: function): EventSubscription`
14 14
 
15
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/adapters/NativeEventsReceiver.ts#L14)
15
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/adapters/NativeEventsReceiver.ts#L32)
16 16
 
17 17
 ---
18 18
 
@@ -20,7 +20,31 @@
20 20
 
21 21
 `registerComponentDidDisappearListener(callback: function): EventSubscription`
22 22
 
23
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/adapters/NativeEventsReceiver.ts#L18)
23
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/adapters/NativeEventsReceiver.ts#L36)
24
+
25
+---
26
+
27
+## registerNavigationButtonPressedListener
28
+
29
+`registerNavigationButtonPressedListener(callback: function): EventSubscription`
30
+
31
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/adapters/NativeEventsReceiver.ts#L40)
32
+
33
+---
34
+
35
+## registerSearchBarUpdatedListener
36
+
37
+`registerSearchBarUpdatedListener(callback: function): EventSubscription`
38
+
39
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/adapters/NativeEventsReceiver.ts#L44)
40
+
41
+---
42
+
43
+## registerSearchBarCancelPressedListener
44
+
45
+`registerSearchBarCancelPressedListener(callback: function): EventSubscription`
46
+
47
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/adapters/NativeEventsReceiver.ts#L48)
24 48
 
25 49
 ---
26 50
 
@@ -28,15 +52,15 @@
28 52
 
29 53
 `registerCommandCompletedListener(callback: function): EventSubscription`
30 54
 
31
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/adapters/NativeEventsReceiver.ts#L22)
55
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/adapters/NativeEventsReceiver.ts#L52)
32 56
 
33 57
 ---
34 58
 
35
-## registerNativeEventListener
59
+## registerBottomTabSelectedListener
36 60
 
37
-`registerNativeEventListener(callback: function): EventSubscription`
61
+`registerBottomTabSelectedListener(callback: function): EventSubscription`
38 62
 
39
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/adapters/NativeEventsReceiver.ts#L26)
63
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/adapters/NativeEventsReceiver.ts#L56)
40 64
 
41 65
 ---
42 66
 

+ 27
- 2
docs/api/Navigation.md View File

@@ -4,11 +4,16 @@
4 4
 
5 5
 `Element (React.ComponentType<object>)`
6 6
 
7
+---
8
+## store
9
+
10
+`store (Store)`
11
+
7 12
 ---
8 13
 
9 14
 ## registerComponent
10 15
 
11
-`registerComponent(componentName: string, getComponentClassFunc: ComponentProvider): React.ComponentType<any>`
16
+`registerComponent(componentName: string, getComponentClassFunc: ComponentProvider): ComponentType<any>`
12 17
 
13 18
 [source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/Navigation.ts#L52)
14 19
 
@@ -147,14 +152,34 @@ dismiss overlay by componentId
147 152
 
148 153
 ---
149 154
 
155
+## getLaunchArgs
156
+
157
+`getLaunchArgs(): Promise<any>`
158
+
159
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/Navigation.ts#L150)
160
+
161
+Resolves arguments passed on launch
162
+
163
+---
164
+
150 165
 ## events
151 166
 
152 167
 `events(): EventsRegistry`
153 168
 
154
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/Navigation.ts#L150)
169
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/Navigation.ts#L157)
155 170
 
156 171
 Obtain the events registry instance
157 172
 
158 173
 ---
159 174
 
175
+## constants
176
+
177
+`constants(): Promise<any>`
178
+
179
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/Navigation.ts#L164)
180
+
181
+Constants coming from native
182
+
183
+---
184
+
160 185
 

+ 1
- 0
docs/api/README.md View File

@@ -1,5 +1,6 @@
1 1
 - [Home](/)
2 2
 - [Navigation](/api/Navigation)
3
+- [Constants](/api/Constants)
3 4
 - [Element](/api/Element)
4 5
 - [NativeCommandsSender](/api/NativeCommandsSender)
5 6
 - [NativeEventsReceiver](/api/NativeEventsReceiver)

+ 5
- 21
docs/api/Store.md View File

@@ -4,7 +4,7 @@
4 4
 
5 5
 `setPropsForId(componentId: string, props: any): void`
6 6
 
7
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/components/Store.ts#L8)
7
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/components/Store.ts#L7)
8 8
 
9 9
 ---
10 10
 
@@ -12,7 +12,7 @@
12 12
 
13 13
 `getPropsForId(componentId: string): any`
14 14
 
15
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/components/Store.ts#L12)
15
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/components/Store.ts#L11)
16 16
 
17 17
 ---
18 18
 
@@ -20,7 +20,7 @@
20 20
 
21 21
 `setOriginalComponentClassForName(componentName: string, ComponentClass: any): void`
22 22
 
23
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/components/Store.ts#L16)
23
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/components/Store.ts#L15)
24 24
 
25 25
 ---
26 26
 
@@ -28,23 +28,7 @@
28 28
 
29 29
 `getOriginalComponentClassForName(componentName: string): any`
30 30
 
31
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/components/Store.ts#L20)
32
-
33
----
34
-
35
-## setRefForId
36
-
37
-`setRefForId(id: string, ref: any): void`
38
-
39
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/components/Store.ts#L24)
40
-
41
----
42
-
43
-## getRefForId
44
-
45
-`getRefForId(id: string): any`
46
-
47
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/components/Store.ts#L28)
31
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/components/Store.ts#L19)
48 32
 
49 33
 ---
50 34
 
@@ -52,7 +36,7 @@
52 36
 
53 37
 `cleanId(id: string): void`
54 38
 
55
-[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/components/Store.ts#L32)
39
+[source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/components/Store.ts#L23)
56 40
 
57 41
 ---
58 42
 

+ 1
- 0
docs/api/_sidebar.md View File

@@ -1,5 +1,6 @@
1 1
 - [Home](/)
2 2
 - [Navigation](/api/Navigation)
3
+- [Constants](/api/Constants)
3 4
 - [Element](/api/Element)
4 5
 - [NativeCommandsSender](/api/NativeCommandsSender)
5 6
 - [NativeEventsReceiver](/api/NativeEventsReceiver)

+ 2
- 2
docs/docs/events.md View File

@@ -103,12 +103,12 @@ This event is emitted whenever a TopBar button is pressed by the user
103 103
 |**name**|`buttonPressed`|
104 104
 |**params**|`componentId`: `componentId` of the layout element the pressed button is bound to<br>`buttonId`: `id` of the pressed button|
105 105
 
106
-## onNavigationButtonPressed
106
+## navigationButtonPressed
107 107
 Called when a TopBar button is pressed.
108 108
 
109 109
 ```js
110 110
 class MyComponent extends Component {
111
-  onNavigationButtonPressed(buttonId) {
111
+  navigationButtonPressed(buttonId) {
112 112
 
113 113
   }
114 114
 }

+ 30
- 36
lib/android/app/src/main/java/com/reactnativenavigation/react/EventEmitter.java View File

@@ -8,65 +8,59 @@ import com.facebook.react.modules.core.DeviceEventManagerModule;
8 8
 import static com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter;
9 9
 
10 10
 public class EventEmitter {
11
-	private static final String onAppLaunched = "RNN.appLaunched";
12
-	private static final String componentDidAppear = "RNN.componentDidAppear";
13
-	private static final String componentDidDisappear = "RNN.componentDidDisappear";
14
-	private static final String nativeEvent = "RNN.nativeEvent";
15
-    private static final String commandCompleted = "RNN.commandCompleted";
16
-    private static final String buttonPressedEvent = "buttonPressed";
11
+	private static final String AppLaunched             = "RNN.AppLaunched";
12
+	private static final String CommandCompleted        = "RNN.CommandCompleted";
13
+	private static final String BottomTabSelected       = "RNN.BottomTabSelected";
14
+	private static final String ComponentDidAppear      = "RNN.ComponentDidAppear";
15
+	private static final String ComponentDidDisappear   = "RNN.ComponentDidDisappear";
16
+	private static final String NavigationButtonPressed = "RNN.NavigationButtonPressed";
17
+	private static final String SearchBarUpdated        = "RNN.SearchBarUpdated";
18
+	private static final String SearchBarCancelPressed  = "RNN.SearchBarCancelPressed";
17 19
 
18
-    private final RCTDeviceEventEmitter emitter;
20
+	private final RCTDeviceEventEmitter emitter;
19 21
 
20
-    EventEmitter(ReactContext reactContext) {
22
+	EventEmitter(ReactContext reactContext) {
21 23
 		this.emitter = reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
22 24
 	}
23 25
 
24 26
 	public void appLaunched() {
25
-		emit(onAppLaunched);
27
+		emit(AppLaunched);
26 28
 	}
27 29
 
28 30
 	public void componentDidDisappear(String id, String componentName) {
29 31
 		WritableMap event = Arguments.createMap();
30 32
 		event.putString("componentId", id);
31 33
 		event.putString("componentName", componentName);
32
-		emit(componentDidDisappear, event);
34
+		emit(ComponentDidDisappear, event);
33 35
 	}
34 36
 
35 37
 	public void componentDidAppear(String id, String componentName) {
36 38
 		WritableMap event = Arguments.createMap();
37 39
 		event.putString("componentId", id);
38 40
 		event.putString("componentName", componentName);
39
-		emit(componentDidAppear, event);
41
+		emit(ComponentDidAppear, event);
40 42
 	}
41 43
 
42
-    public void emitOnNavigationButtonPressed(String id, String buttonId) {
43
-		WritableMap params = Arguments.createMap();
44
-		params.putString("componentId", id);
45
-		params.putString("buttonId", buttonId);
46
-
47
-        WritableMap event = Arguments.createMap();
48
-        event.putString("name", buttonPressedEvent);
49
-        event.putMap("params", params);
50
-		emit(nativeEvent, event);
44
+	public void emitOnNavigationButtonPressed(String id, String buttonId) {
45
+		WritableMap event = Arguments.createMap();
46
+		event.putString("componentId", id);
47
+		event.putString("buttonId", buttonId);
48
+		emit(NavigationButtonPressed, event);
51 49
 	}
52 50
 
53
-    public void emitBottomTabSelected(int unselectedTabIndex, int selectedTabIndex) {
54
-        WritableMap params = Arguments.createMap();
55
-        params.putInt("unselectedTabIndex", unselectedTabIndex);
56
-        params.putInt("selectedTabIndex", selectedTabIndex);
57
-
58
-        WritableMap event = Arguments.createMap();
59
-        event.putString("name", "bottomTabSelected");
60
-        event.putMap("params", params);
61
-        emitter.emit(nativeEvent, event);
62
-    }
51
+	public void emitBottomTabSelected(int unselectedTabIndex, int selectedTabIndex) {
52
+		WritableMap event = Arguments.createMap();
53
+		event.putInt("unselectedTabIndex", unselectedTabIndex);
54
+		event.putInt("selectedTabIndex", selectedTabIndex);
55
+		emit(BottomTabSelected, event);
56
+	}
63 57
 
64
-    public void emitCommandCompletedEvent(String commandId, long completionTime) {
65
-        WritableMap event = Arguments.createMap();
66
-        event.putString("commandId", commandId);
67
-        event.putDouble("completionTime", completionTime);
68
-        emitter.emit(commandCompleted, event);
69
-    }
58
+	public void emitCommandCompletedEvent(String commandId, long completionTime) {
59
+		WritableMap event = Arguments.createMap();
60
+		event.putString("commandId", commandId);
61
+		event.putDouble("completionTime", completionTime);
62
+		emit(CommandCompleted, event);
63
+	}
70 64
 
71 65
 	private void emit(String eventName) {
72 66
 		emit(eventName, Arguments.createMap());

+ 0
- 3
lib/ios/RNNCommandsHandler.m View File

@@ -50,7 +50,6 @@ static NSString* const setDefaultOptions	= @"setDefaultOptions";
50 50
 	[self assertReady];
51 51
 	
52 52
 	[_modalManager dismissAllModals];
53
-	[_eventEmitter sendOnNavigationCommand:setRoot params:@{@"layout": layout}];
54 53
 	
55 54
 	UIViewController *vc = [_controllerFactory createLayoutAndSaveToStore:layout[@"root"]];
56 55
 	
@@ -62,7 +61,6 @@ static NSString* const setDefaultOptions	= @"setDefaultOptions";
62 61
 
63 62
 -(void) mergeOptions:(NSString*)componentId options:(NSDictionary*)options completion:(RNNTransitionCompletionBlock)completion {
64 63
 	[self assertReady];
65
-	[_eventEmitter sendOnNavigationCommand:mergeOptions params:@{@"componentId": componentId, @"options": options}];
66 64
 	
67 65
 	UIViewController* vc = [_store findComponentForId:componentId];
68 66
 	if([vc isKindOfClass:[RNNRootViewController class]]) {
@@ -91,7 +89,6 @@ static NSString* const setDefaultOptions	= @"setDefaultOptions";
91 89
 
92 90
 -(void) setDefaultOptions:(NSDictionary*)optionsDict completion:(RNNTransitionCompletionBlock)completion {
93 91
 	[self assertReady];
94
-	[_eventEmitter sendOnNavigationCommand:setDefaultOptions params:@{@"options": optionsDict}];
95 92
 	[_controllerFactory setDefaultOptionsDict:optionsDict];
96 93
 }
97 94
 

+ 1
- 3
lib/ios/RNNEventEmitter.h View File

@@ -14,9 +14,7 @@
14 14
 
15 15
 -(void)sendOnNavigationButtonPressed:(NSString*)componentId buttonId:(NSString*)buttonId;
16 16
 
17
--(void)sendOnNavigationEvent:(NSString *)commandName params:(NSDictionary*)params;
18
-
19
--(void)sendOnNavigationCommand:(NSString *)commandName params:(NSDictionary*)params;
17
+-(void)sendBottomTabSelected:(NSNumber *)selectedTabIndex unselected:(NSNumber*)unselectedTabIndex;
20 18
 
21 19
 -(void)sendOnNavigationCommandCompletion:(NSString *)commandName params:(NSDictionary*)params;
22 20
 

+ 53
- 31
lib/ios/RNNEventEmitter.m View File

@@ -2,75 +2,97 @@
2 2
 #import "RNNUtils.h"
3 3
 
4 4
 @implementation RNNEventEmitter {
5
-  NSInteger _appLaunchedListenerCount;
6
-  BOOL _appLaunchedEventDeferred;
5
+	NSInteger _appLaunchedListenerCount;
6
+	BOOL _appLaunchedEventDeferred;
7 7
 }
8 8
 
9 9
 RCT_EXPORT_MODULE();
10 10
 
11
-static NSString* const onAppLaunched	= @"RNN.appLaunched";
12
-static NSString* const componentDidAppear	= @"RNN.componentDidAppear";
13
-static NSString* const componentDidDisappear	= @"RNN.componentDidDisappear";
14
-static NSString* const commandComplete	= @"RNN.commandCompleted";
15
-static NSString* const navigationEvent	= @"RNN.nativeEvent";
11
+static NSString* const AppLaunched				= @"RNN.AppLaunched";
12
+static NSString* const CommandCompleted			= @"RNN.CommandCompleted";
13
+static NSString* const BottomTabSelected		= @"RNN.BottomTabSelected";
14
+static NSString* const ComponentDidAppear		= @"RNN.ComponentDidAppear";
15
+static NSString* const ComponentDidDisappear	= @"RNN.ComponentDidDisappear";
16
+static NSString* const NavigationButtonPressed	= @"RNN.NavigationButtonPressed";
17
+static NSString* const SearchBarUpdated 		= @"RNN.SearchBarUpdated";
18
+static NSString* const SearchBarCancelPressed 	= @"RNN.SearchBarCancelPressed";
16 19
 
17 20
 -(NSArray<NSString *> *)supportedEvents {
18
-	return @[onAppLaunched, componentDidAppear, componentDidDisappear, commandComplete, navigationEvent];
21
+	return @[AppLaunched,
22
+			 CommandCompleted,
23
+			 BottomTabSelected,
24
+			 ComponentDidAppear,
25
+			 ComponentDidDisappear,
26
+			 NavigationButtonPressed,
27
+			 SearchBarUpdated,
28
+			 SearchBarCancelPressed];
19 29
 }
20 30
 
21 31
 # pragma mark public
22 32
 
23 33
 -(void)sendOnAppLaunched {
24 34
 	if (_appLaunchedListenerCount > 0) {
25
-		[self send:onAppLaunched body:nil];
35
+		[self send:AppLaunched body:nil];
26 36
 	} else {
27 37
 		_appLaunchedEventDeferred = TRUE;
28 38
 	}
29 39
 }
30 40
 
31 41
 -(void)sendComponentDidAppear:(NSString *)componentId componentName:(NSString *)componentName {
32
-	[self send:componentDidAppear body:@{@"componentId":componentId, @"componentName": componentName}];
42
+	[self send:ComponentDidAppear body:@{
43
+										 @"componentId":componentId,
44
+										 @"componentName": componentName
45
+										 }];
33 46
 }
34 47
 
35 48
 -(void)sendComponentDidDisappear:(NSString *)componentId componentName:(NSString *)componentName{
36
-	[self send:componentDidDisappear body:@{@"componentId":componentId, @"componentName": componentName}];
49
+	[self send:ComponentDidDisappear body:@{
50
+											@"componentId":componentId,
51
+											@"componentName": componentName
52
+											}];
37 53
 }
38 54
 
39 55
 -(void)sendOnNavigationButtonPressed:(NSString *)componentId buttonId:(NSString*)buttonId {
40
-	[self send:navigationEvent body:@{@"name": @"buttonPressed", @"params": @{@"componentId": componentId , @"buttonId": buttonId}}];
56
+	[self send:NavigationButtonPressed body:@{
57
+											  @"componentId": componentId,
58
+											  @"buttonId": buttonId
59
+											  }];
41 60
 }
42 61
 
43
--(void)sendOnNavigationCommand:(NSString *)commandName params:(NSDictionary*)params {
44
-	[self send:navigationEvent body:@{@"name":commandName , @"params": params}];
62
+-(void)sendBottomTabSelected:(NSNumber *)selectedTabIndex unselected:(NSNumber*)unselectedTabIndex {
63
+	[self send:BottomTabSelected body:@{
64
+									  @"selectedTabIndex": selectedTabIndex,
65
+									  @"unselectedTabIndex": unselectedTabIndex
66
+									  }];
45 67
 }
46 68
 
47 69
 -(void)sendOnNavigationCommandCompletion:(NSString *)commandName params:(NSDictionary*)params {
48
-	[self send:commandComplete body:@{@"commandId":commandName , @"params": params, @"completionTime": [RNNUtils getCurrentTimestamp] }];
49
-}
50
-
51
--(void)sendOnNavigationEvent:(NSString *)commandName params:(NSDictionary*)params {
52
-	[self send:navigationEvent body:@{@"name":commandName , @"params": params}];
70
+	[self send:CommandCompleted body:@{
71
+									   @"commandId":commandName,
72
+									   @"params": params,
73
+									   @"completionTime": [RNNUtils getCurrentTimestamp]
74
+									   }];
53 75
 }
54 76
 
55 77
 -(void)sendOnSearchBarUpdated:(NSString *)componentId
56 78
 						 text:(NSString*)text
57 79
 					isFocused:(BOOL)isFocused {
58
-	[self send:navigationEvent body:@{@"name": @"searchBarUpdated",
59
-									  @"params": @{
60
-												  @"componentId": componentId,
61
-												  @"text": text,
62
-												  @"isFocused": @(isFocused)}}];
80
+	[self send:SearchBarUpdated body:@{
81
+									   @"componentId": componentId,
82
+									   @"text": text,
83
+									   @"isFocused": @(isFocused)
84
+									   }];
63 85
 }
64 86
 
65 87
 - (void)sendOnSearchBarCancelPressed:(NSString *)componentId {
66
-	[self send:navigationEvent body:@{@"name": @"searchBarCancelPressed",
67
-									  @"params": @{
68
-											  @"componentId": componentId}}];
88
+	[self send:SearchBarCancelPressed body:@{
89
+											@"componentId": componentId
90
+											}];
69 91
 }
70 92
 
71 93
 - (void)addListener:(NSString *)eventName {
72 94
 	[super addListener:eventName];
73
-	if ([eventName isEqualToString:onAppLaunched]) {
95
+	if ([eventName isEqualToString:AppLaunched]) {
74 96
 		_appLaunchedListenerCount++;
75 97
 		if (_appLaunchedEventDeferred) {
76 98
 			_appLaunchedEventDeferred = FALSE;
@@ -82,9 +104,9 @@ static NSString* const navigationEvent	= @"RNN.nativeEvent";
82 104
 # pragma mark private
83 105
 
84 106
 -(void)send:(NSString *)eventName body:(id)body {
85
-    if ([eventName isEqualToString:componentDidDisappear] && self.bridge == nil) {
86
-        return;
87
-    }
107
+	if (self.bridge == nil) {
108
+		return;
109
+	}
88 110
 	[self sendEventWithName:eventName body:body];
89 111
 }
90 112
 

+ 11
- 11
lib/ios/RNNRootViewController.m View File

@@ -292,11 +292,11 @@
292 292
 
293 293
 - (UIViewController *)previewingContext:(id<UIViewControllerPreviewing>)previewingContext viewControllerForLocation:(CGPoint)location{
294 294
 	if (self.previewController) {
295
-		RNNRootViewController * vc = (RNNRootViewController*) self.previewController;
296
-		[_eventEmitter sendOnNavigationEvent:@"previewContext" params:@{
297
-																		@"previewComponentId": vc.componentId,
298
-																		@"componentId": self.componentId
299
-																		}];
295
+//		RNNRootViewController * vc = (RNNRootViewController*) self.previewController;
296
+//		[_eventEmitter sendOnNavigationEvent:@"previewContext" params:@{
297
+//																		@"previewComponentId": vc.componentId,
298
+//																		@"componentId": self.componentId
299
+//																		}];
300 300
 	}
301 301
 	return self.previewController;
302 302
 }
@@ -304,15 +304,15 @@
304 304
 
305 305
 - (void)previewingContext:(id<UIViewControllerPreviewing>)previewingContext commitViewController:(UIViewController *)viewControllerToCommit {
306 306
 	RNNRootViewController * vc = (RNNRootViewController*) self.previewController;
307
-	NSDictionary * params = @{
308
-							  @"previewComponentId": vc.componentId,
309
-							  @"componentId": self.componentId
310
-							  };
307
+//	NSDictionary * params = @{
308
+//							  @"previewComponentId": vc.componentId,
309
+//							  @"componentId": self.componentId
310
+//							  };
311 311
 	if (vc.options.preview.commit) {
312
-		[_eventEmitter sendOnNavigationEvent:@"previewCommit" params:params];
312
+//		[_eventEmitter sendOnNavigationEvent:@"previewCommit" params:params];
313 313
 		[self.navigationController pushViewController:vc animated:false];
314 314
 	} else {
315
-		[_eventEmitter sendOnNavigationEvent:@"previewDismissed" params:params];
315
+//		[_eventEmitter sendOnNavigationEvent:@"previewDismissed" params:params];
316 316
 	}
317 317
 }
318 318
 

+ 1
- 2
lib/ios/RNNTabBarController.m View File

@@ -56,8 +56,7 @@
56 56
 #pragma mark UITabBarControllerDelegate
57 57
 
58 58
 - (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
59
-	[_eventEmitter sendOnNavigationEvent:@"bottomTabSelected" params:@{@"selectedTabIndex": @(tabBarController.selectedIndex), @"unselectedTabIndex": @(_currentTabIndex)}];
60
-	
59
+	[_eventEmitter sendBottomTabSelected:@(tabBarController.selectedIndex) unselected:@(_currentTabIndex)];
61 60
 	_currentTabIndex = tabBarController.selectedIndex;
62 61
 }
63 62
 

+ 5
- 6
lib/src/Navigation.ts View File

@@ -9,10 +9,10 @@ import { LayoutTreeCrawler } from './commands/LayoutTreeCrawler';
9 9
 import { EventsRegistry } from './events/EventsRegistry';
10 10
 import { ComponentProvider } from 'react-native';
11 11
 import { Element } from './adapters/Element';
12
-import { ComponentEventsObserver } from './events/ComponentEventsObserver';
13 12
 import { CommandsObserver } from './events/CommandsObserver';
14 13
 import { Constants } from './adapters/Constants';
15 14
 import { ComponentType } from 'react';
15
+import { ComponentEventsObserver } from './events/ComponentEventsObserver';
16 16
 
17 17
 export class Navigation {
18 18
   public readonly Element: React.ComponentType<{ elementId: any; resizeMode?: any; }>;
@@ -33,16 +33,16 @@ export class Navigation {
33 33
     this.store = new Store();
34 34
     this.nativeEventsReceiver = new NativeEventsReceiver();
35 35
     this.uniqueIdProvider = new UniqueIdProvider();
36
-    this.componentRegistry = new ComponentRegistry(this.store);
36
+    this.componentEventsObserver = new ComponentEventsObserver(this.nativeEventsReceiver);
37
+    this.componentRegistry = new ComponentRegistry(this.store, this.componentEventsObserver);
37 38
     this.layoutTreeParser = new LayoutTreeParser();
38 39
     this.layoutTreeCrawler = new LayoutTreeCrawler(this.uniqueIdProvider, this.store);
39 40
     this.nativeCommandsSender = new NativeCommandsSender();
40 41
     this.commandsObserver = new CommandsObserver();
41 42
     this.commands = new Commands(this.nativeCommandsSender, this.layoutTreeParser, this.layoutTreeCrawler, this.commandsObserver, this.uniqueIdProvider);
42
-    this.eventsRegistry = new EventsRegistry(this.nativeEventsReceiver, this.commandsObserver);
43
-    this.componentEventsObserver = new ComponentEventsObserver(this.eventsRegistry, this.store);
43
+    this.eventsRegistry = new EventsRegistry(this.nativeEventsReceiver, this.commandsObserver, this.componentEventsObserver);
44 44
 
45
-    this.componentEventsObserver.registerForAllComponents();
45
+    this.componentEventsObserver.registerOnceForAllComponentEvents();
46 46
   }
47 47
 
48 48
   /**
@@ -145,7 +145,6 @@ export class Navigation {
145 145
   }
146 146
 
147 147
   /**
148
-   * 
149 148
    * Resolves arguments passed on launch
150 149
    */
151 150
   public getLaunchArgs(): Promise<any> {

+ 29
- 9
lib/src/adapters/NativeEventsReceiver.ts View File

@@ -1,5 +1,13 @@
1 1
 import { NativeModules, NativeEventEmitter } from 'react-native';
2 2
 import { EventSubscription } from '../interfaces/EventSubscription';
3
+import {
4
+  ComponentDidAppearEvent,
5
+  ComponentDidDisappearEvent,
6
+  NavigationButtonPressedEvent,
7
+  SearchBarUpdatedEvent,
8
+  SearchBarCancelPressedEvent
9
+} from '../interfaces/ComponentEvents';
10
+import { CommandCompletedEvent, BottomTabSelectedEvent } from '../interfaces/Events';
3 11
 
4 12
 export class NativeEventsReceiver {
5 13
   private emitter;
@@ -18,22 +26,34 @@ export class NativeEventsReceiver {
18 26
   }
19 27
 
20 28
   public registerAppLaunchedListener(callback: () => void): EventSubscription {
21
-    return this.emitter.addListener('RNN.appLaunched', callback);
29
+    return this.emitter.addListener('RNN.AppLaunched', callback);
22 30
   }
23 31
 
24
-  public registerComponentDidAppearListener(callback: (data) => void): EventSubscription {
25
-    return this.emitter.addListener('RNN.componentDidAppear', callback);
32
+  public registerComponentDidAppearListener(callback: (event: ComponentDidAppearEvent) => void): EventSubscription {
33
+    return this.emitter.addListener('RNN.ComponentDidAppear', callback);
26 34
   }
27 35
 
28
-  public registerComponentDidDisappearListener(callback: (data) => void): EventSubscription {
29
-    return this.emitter.addListener('RNN.componentDidDisappear', callback);
36
+  public registerComponentDidDisappearListener(callback: (event: ComponentDidDisappearEvent) => void): EventSubscription {
37
+    return this.emitter.addListener('RNN.ComponentDidDisappear', callback);
30 38
   }
31 39
 
32
-  public registerCommandCompletedListener(callback: (data) => void): EventSubscription {
33
-    return this.emitter.addListener('RNN.commandCompleted', callback);
40
+  public registerNavigationButtonPressedListener(callback: (event: NavigationButtonPressedEvent) => void): EventSubscription {
41
+    return this.emitter.addListener('RNN.NavigationButtonPressed', callback);
34 42
   }
35 43
 
36
-  public registerNativeEventListener(callback: (data) => void): EventSubscription {
37
-    return this.emitter.addListener('RNN.nativeEvent', callback);
44
+  public registerSearchBarUpdatedListener(callback: (event: SearchBarUpdatedEvent) => void): EventSubscription {
45
+    return this.emitter.addListener('RNN.SearchBarUpdated', callback);
46
+  }
47
+
48
+  public registerSearchBarCancelPressedListener(callback: (event: SearchBarCancelPressedEvent) => void): EventSubscription {
49
+    return this.emitter.addListener('RNN.SearchBarCancelPressed', callback);
50
+  }
51
+
52
+  public registerCommandCompletedListener(callback: (data: CommandCompletedEvent) => void): EventSubscription {
53
+    return this.emitter.addListener('RNN.CommandCompleted', callback);
54
+  }
55
+
56
+  public registerBottomTabSelectedListener(callback: (data: BottomTabSelectedEvent) => void): EventSubscription {
57
+    return this.emitter.addListener('RNN.BottomTabSelected', callback);
38 58
   }
39 59
 }

+ 1
- 1
lib/src/components/ComponentRegistry.test.tsx View File

@@ -23,7 +23,7 @@ describe('ComponentRegistry', () => {
23 23
   beforeEach(() => {
24 24
     store = new Store();
25 25
     mockRegistry = AppRegistry.registerComponent = jest.fn(AppRegistry.registerComponent);
26
-    uut = new ComponentRegistry(store);
26
+    uut = new ComponentRegistry(store, {} as any);
27 27
   });
28 28
 
29 29
   it('registers component component by componentName into AppRegistry', () => {

+ 4
- 6
lib/src/components/ComponentRegistry.ts View File

@@ -1,17 +1,15 @@
1 1
 import { AppRegistry, ComponentProvider } from 'react-native';
2 2
 import { ComponentWrapper } from './ComponentWrapper';
3 3
 import { ComponentType } from 'react';
4
+import { Store } from './Store';
5
+import { ComponentEventsObserver } from '../events/ComponentEventsObserver';
4 6
 
5 7
 export class ComponentRegistry {
6
-  private store;
7
-
8
-  constructor(store) {
9
-    this.store = store;
10
-  }
8
+  constructor(private readonly store: Store, private readonly componentEventsObserver: ComponentEventsObserver) { }
11 9
 
12 10
   registerComponent(componentName: string, getComponentClassFunc: ComponentProvider): ComponentType<any> {
13 11
     const OriginalComponentClass = getComponentClassFunc();
14
-    const NavigationComponent = ComponentWrapper.wrap(componentName, OriginalComponentClass, this.store);
12
+    const NavigationComponent = ComponentWrapper.wrap(componentName, OriginalComponentClass, this.store, this.componentEventsObserver);
15 13
     this.store.setOriginalComponentClassForName(componentName, OriginalComponentClass);
16 14
     AppRegistry.registerComponent(componentName, () => NavigationComponent);
17 15
     return NavigationComponent;

+ 51
- 147
lib/src/components/ComponentWrapper.test.tsx View File

@@ -4,28 +4,27 @@ import * as renderer from 'react-test-renderer';
4 4
 import { ComponentWrapper } from './ComponentWrapper';
5 5
 import { Store } from './Store';
6 6
 
7
-declare module 'react-test-renderer' {
8
-  interface ReactTestInstance {
9
-    [P: string]: any;
10
-  }
11
-}
12
-
13 7
 describe('ComponentWrapper', () => {
14
-  let store;
15 8
   const componentName = 'example.MyComponent';
16
-  let childRef;
9
+  let store;
10
+  let myComponentProps;
11
+  const componentEventsObserver = { unmounted: jest.fn() };
17 12
 
18
-  class MyComponent extends React.Component {
13
+  class MyComponent extends React.Component<any, any> {
19 14
     static options = {
20 15
       title: 'MyComponentTitle'
21 16
     };
22 17
 
23 18
     render() {
24
-      return <Text>{'Hello, World!'}</Text>;
19
+      myComponentProps = this.props;
20
+      if (this.props.renderCount) {
21
+        this.props.renderCount();
22
+      }
23
+      return <Text>{this.props.text || 'Hello, World!'}</Text>;
25 24
     }
26 25
   }
27 26
 
28
-  class TestParent extends React.Component<any, { propsFromState: {} }> {
27
+  class TestParent extends React.Component<any, any> {
29 28
     private ChildClass;
30 29
 
31 30
     constructor(props) {
@@ -37,7 +36,6 @@ describe('ComponentWrapper', () => {
37 36
     render() {
38 37
       return (
39 38
         <this.ChildClass
40
-          ref={(r) => childRef = r}
41 39
           componentId='component1'
42 40
           {...this.state.propsFromState}
43 41
         />
@@ -50,7 +48,7 @@ describe('ComponentWrapper', () => {
50 48
   });
51 49
 
52 50
   it('must have componentId as prop', () => {
53
-    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
51
+    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store, componentEventsObserver);
54 52
     const orig = console.error;
55 53
     console.error = (a) => a;
56 54
     expect(() => {
@@ -60,174 +58,80 @@ describe('ComponentWrapper', () => {
60 58
   });
61 59
 
62 60
   it('wraps the component', () => {
63
-    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
61
+    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store, componentEventsObserver);
64 62
     expect(NavigationComponent).not.toBeInstanceOf(MyComponent);
65 63
     const tree = renderer.create(<NavigationComponent componentId={'component1'} />);
66 64
     expect(tree.toJSON()!.children).toEqual(['Hello, World!']);
67
-    expect(tree.getInstance()!.originalComponentRef).toBeInstanceOf(MyComponent);
68 65
   });
69 66
 
70 67
   it('injects props from wrapper into original component', () => {
71
-    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
72
-    const tree = renderer.create(<NavigationComponent componentId={'component1'} myProp={'yo'} />);
73
-    expect(tree.getInstance()!.originalComponentRef.props.myProp).toEqual('yo');
68
+    const renderCount = jest.fn();
69
+    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store, componentEventsObserver);
70
+    const tree = renderer.create(<NavigationComponent componentId={'component1'} text={'yo'} renderCount={renderCount} />);
71
+    expect(tree.toJSON()!.children).toEqual(['yo']);
72
+    expect(renderCount).toHaveBeenCalledTimes(1);
74 73
   });
75 74
 
76
-  it('updates props from wrapper into original component', () => {
77
-    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
75
+  it('updates props from wrapper into original component on state change', () => {
76
+    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store, componentEventsObserver);
78 77
     const tree = renderer.create(<TestParent ChildClass={NavigationComponent} />);
79
-    expect(childRef.props.foo).toEqual(undefined);
80
-    tree.getInstance()!.setState({ propsFromState: { foo: 'yo' } });
81
-    expect(childRef.props.foo).toEqual('yo');
78
+    expect(myComponentProps.foo).toEqual(undefined);
79
+    (tree.getInstance() as any).setState({ propsFromState: { foo: 'yo' } });
80
+    expect(myComponentProps.foo).toEqual('yo');
82 81
   });
83 82
 
84 83
   it('pulls props from the store and injects them into the inner component', () => {
85 84
     store.setPropsForId('component123', { numberProp: 1, stringProp: 'hello', objectProp: { a: 2 } });
86
-    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
87
-    const tree = renderer.create(<NavigationComponent componentId={'component123'} />);
88
-    const originalComponentProps = tree.getInstance()!.originalComponentRef.props;
89
-    expect(originalComponentProps).toEqual({ componentId: 'component123', numberProp: 1, stringProp: 'hello', objectProp: { a: 2 } });
85
+    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store, componentEventsObserver);
86
+    renderer.create(<NavigationComponent componentId={'component123'} />);
87
+    expect(myComponentProps).toEqual({ componentId: 'component123', numberProp: 1, stringProp: 'hello', objectProp: { a: 2 } });
90 88
   });
91 89
 
92 90
   it('updates props from store into inner component', () => {
93
-    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
91
+    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store, componentEventsObserver);
94 92
     const tree = renderer.create(<TestParent ChildClass={NavigationComponent} />);
95 93
     store.setPropsForId('component1', { myProp: 'hello' });
96
-    expect(childRef.originalComponentRef.props.foo).toEqual(undefined);
97
-    expect(childRef.originalComponentRef.props.myProp).toEqual(undefined);
98
-    tree.getInstance()!.setState({ propsFromState: { foo: 'yo' } });
99
-    expect(childRef.originalComponentRef.props.foo).toEqual('yo');
100
-    expect(childRef.originalComponentRef.props.myProp).toEqual('hello');
94
+    expect(myComponentProps.foo).toEqual(undefined);
95
+    expect(myComponentProps.myProp).toEqual(undefined);
96
+    (tree.getInstance() as any).setState({ propsFromState: { foo: 'yo' } });
97
+    expect(myComponentProps.foo).toEqual('yo');
98
+    expect(myComponentProps.myProp).toEqual('hello');
101 99
   });
102 100
 
103 101
   it('protects id from change', () => {
104
-    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
102
+    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store, componentEventsObserver);
105 103
     const tree = renderer.create(<TestParent ChildClass={NavigationComponent} />);
106
-    expect(childRef.originalComponentRef.props.componentId).toEqual('component1');
107
-    tree.getInstance()!.setState({ propsFromState: { id: 'ERROR' } });
108
-    expect(childRef.originalComponentRef.props.componentId).toEqual('component1');
104
+    expect(myComponentProps.componentId).toEqual('component1');
105
+    (tree.getInstance() as any).setState({ propsFromState: { id: 'ERROR' } });
106
+    expect(myComponentProps.componentId).toEqual('component1');
109 107
   });
110 108
 
111 109
   it('assignes key by id', () => {
112
-    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
113
-    const tree = renderer.create(<NavigationComponent componentId={'component1'} />);
114
-    expect(tree.getInstance()!.originalComponentRef.props.componentId).toEqual('component1');
115
-    expect(tree.getInstance()!.originalComponentRef._reactInternalInstance.key).toEqual('component1');
116
-  });
117
-
118
-  it('saves self ref into store', () => {
119
-    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
120
-    const tree = renderer.create(<NavigationComponent componentId={'component1'} />);
121
-    expect(store.getRefForId('component1')).toBeDefined();
122
-    expect(store.getRefForId('component1')).toBe(tree.getInstance());
123
-  });
124
-
125
-  it('cleans ref from store on unMount', () => {
126
-    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
127
-    const tree = renderer.create(<NavigationComponent componentId={'component1'} />);
128
-    expect(store.getRefForId('component1')).toBeDefined();
129
-    tree.unmount();
130
-    expect(store.getRefForId('component1')).toBeUndefined();
131
-  });
132
-
133
-  it('holds ref to OriginalComponent', () => {
134
-    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
110
+    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store, componentEventsObserver);
135 111
     const tree = renderer.create(<NavigationComponent componentId={'component1'} />);
136
-    expect(tree.getInstance()!.originalComponentRef).toBeDefined();
137
-    expect(tree.getInstance()!.originalComponentRef).toBeInstanceOf(MyComponent);
112
+    expect(myComponentProps.componentId).toEqual('component1');
113
+    expect((tree.getInstance() as any)._reactInternalInstance.child.key).toEqual('component1');
138 114
   });
139 115
 
140
-  it('cleans ref to internal component on unount', () => {
141
-    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
142
-    const tree = renderer.create(<NavigationComponent componentId={'component1'} />);
143
-    const instance = tree.getInstance()!;
144
-    expect(instance.originalComponentRef).toBeInstanceOf(React.Component);
116
+  it('cleans props from store on unMount', () => {
117
+    store.setPropsForId('component123', { foo: 'bar' });
118
+    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store, componentEventsObserver);
119
+    const tree = renderer.create(<NavigationComponent componentId={'component123'} />);
120
+    expect(store.getPropsForId('component123')).toEqual({ foo: 'bar' });
145 121
     tree.unmount();
146
-    expect(instance.originalComponentRef).toBeFalsy();
122
+    expect(store.getPropsForId('component123')).toEqual({});
147 123
   });
148 124
 
149 125
   it(`merges static members from wrapped component`, () => {
150
-    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store) as any;
126
+    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store, componentEventsObserver) as any;
151 127
     expect(NavigationComponent.options).toEqual({ title: 'MyComponentTitle' });
152 128
   });
153 129
 
154
-  describe('component lifecycle', () => {
155
-    const componentDidAppearCallback = jest.fn();
156
-    const componentDidDisappearCallback = jest.fn();
157
-    const onNavigationButtonPressedCallback = jest.fn();
158
-    const onSearchBarCallback = jest.fn();
159
-    const onSearchBarCancelCallback = jest.fn();
160
-
161
-    class MyLifecycleComponent extends MyComponent {
162
-      componentDidAppear() {
163
-        componentDidAppearCallback();
164
-      }
165
-
166
-      componentDidDisappear() {
167
-        componentDidDisappearCallback();
168
-      }
169
-
170
-      onNavigationButtonPressed() {
171
-        onNavigationButtonPressedCallback();
172
-      }
173
-
174
-      onSearchBarUpdated() {
175
-        onSearchBarCallback();
176
-      }
177
-
178
-      onSearchBarCancelPressed() {
179
-        onSearchBarCancelCallback();
180
-      }
181
-    }
182
-
183
-    it('componentDidAppear, componentDidDisappear and onNavigationButtonPressed are optional', () => {
184
-      const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
185
-      const tree = renderer.create(<NavigationComponent componentId={'component1'} />);
186
-      expect(() => tree.getInstance()!.componentDidAppear()).not.toThrow();
187
-      expect(() => tree.getInstance()!.componentDidDisappear()).not.toThrow();
188
-      expect(() => tree.getInstance()!.onNavigationButtonPressed()).not.toThrow();
189
-      expect(() => tree.getInstance()!.onSearchBarUpdated()).not.toThrow();
190
-      expect(() => tree.getInstance()!.onSearchBarCancelPressed()).not.toThrow();
191
-    });
192
-
193
-    it('calls componentDidAppear on OriginalComponent', () => {
194
-      const NavigationComponent = ComponentWrapper.wrap(componentName, MyLifecycleComponent, store);
195
-      const tree = renderer.create(<NavigationComponent componentId={'component1'} />);
196
-      expect(componentDidAppearCallback).toHaveBeenCalledTimes(0);
197
-      tree.getInstance()!.componentDidAppear();
198
-      expect(componentDidAppearCallback).toHaveBeenCalledTimes(1);
199
-    });
200
-
201
-    it('calls componentDidDisappear on OriginalComponent', () => {
202
-      const NavigationComponent = ComponentWrapper.wrap(componentName, MyLifecycleComponent, store);
203
-      const tree = renderer.create(<NavigationComponent componentId={'component1'} />);
204
-      expect(componentDidDisappearCallback).toHaveBeenCalledTimes(0);
205
-      tree.getInstance()!.componentDidDisappear();
206
-      expect(componentDidDisappearCallback).toHaveBeenCalledTimes(1);
207
-    });
208
-
209
-    it('calls onNavigationButtonPressed on OriginalComponent', () => {
210
-      const NavigationComponent = ComponentWrapper.wrap(componentName, MyLifecycleComponent, store);
211
-      const tree = renderer.create(<NavigationComponent componentId={'component1'} />);
212
-      expect(onNavigationButtonPressedCallback).toHaveBeenCalledTimes(0);
213
-      tree.getInstance()!.onNavigationButtonPressed();
214
-      expect(onNavigationButtonPressedCallback).toHaveBeenCalledTimes(1);
215
-    });
216
-
217
-    it('calls onSearchBarUpdated on OriginalComponent', () => {
218
-      const NavigationComponent = ComponentWrapper.wrap(componentName, MyLifecycleComponent, store);
219
-      const tree = renderer.create(<NavigationComponent componentId={'component1'} />);
220
-      expect(onSearchBarCallback).toHaveBeenCalledTimes(0);
221
-      tree.getInstance()!.onSearchBarUpdated();
222
-      expect(onSearchBarCallback).toHaveBeenCalledTimes(1);
223
-    });
224
-
225
-    it('calls onSearchBarCancelPressed on OriginalComponent', () => {
226
-      const NavigationComponent = ComponentWrapper.wrap(componentName, MyLifecycleComponent, store);
227
-      const tree = renderer.create(<NavigationComponent componentId={'component1'} />);
228
-      expect(onSearchBarCancelCallback).toHaveBeenCalledTimes(0);
229
-      tree.getInstance()!.onSearchBarCancelPressed();
230
-      expect(onSearchBarCancelCallback).toHaveBeenCalledTimes(1);
231
-    });
130
+  it(`calls unmounted on componentEventsObserver`, () => {
131
+    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store, componentEventsObserver);
132
+    const tree = renderer.create(<NavigationComponent componentId={'component123'} />);
133
+    expect(componentEventsObserver.unmounted).not.toHaveBeenCalled();
134
+    tree.unmount();
135
+    expect(componentEventsObserver.unmounted).toHaveBeenCalledTimes(1);
232 136
   });
233 137
 });

+ 6
- 43
lib/src/components/ComponentWrapper.tsx View File

@@ -4,7 +4,11 @@ import * as ReactLifecyclesCompat from 'react-lifecycles-compat';
4 4
 
5 5
 export class ComponentWrapper {
6 6
 
7
-  static wrap(componentName: string, OriginalComponentClass: React.ComponentType<any>, store): React.ComponentType<any> {
7
+  static wrap(
8
+    componentName: string,
9
+    OriginalComponentClass: React.ComponentType<any>,
10
+    store,
11
+    componentEventsObserver): React.ComponentType<any> {
8 12
 
9 13
     class WrappedComponent extends React.Component<any, { componentId: string; allProps: {}; }> {
10 14
 
@@ -14,60 +18,23 @@ export class ComponentWrapper {
14 18
         };
15 19
       }
16 20
 
17
-      private originalComponentRef;
18
-
19 21
       constructor(props) {
20 22
         super(props);
21 23
         this._assertComponentId();
22
-        this._saveComponentRef = this._saveComponentRef.bind(this);
23 24
         this.state = {
24 25
           componentId: props.componentId,
25 26
           allProps: {}
26 27
         };
27 28
       }
28 29
 
29
-      componentDidMount() {
30
-        store.setRefForId(this.state.componentId, this);
31
-      }
32
-
33 30
       componentWillUnmount() {
34 31
         store.cleanId(this.state.componentId);
35
-      }
36
-
37
-      componentDidAppear() {
38
-        if (this.originalComponentRef.componentDidAppear) {
39
-          this.originalComponentRef.componentDidAppear();
40
-        }
41
-      }
42
-
43
-      componentDidDisappear() {
44
-        if (this.originalComponentRef.componentDidDisappear) {
45
-          this.originalComponentRef.componentDidDisappear();
46
-        }
47
-      }
48
-
49
-      onNavigationButtonPressed(buttonId) {
50
-        if (this.originalComponentRef.onNavigationButtonPressed) {
51
-          this.originalComponentRef.onNavigationButtonPressed(buttonId);
52
-        }
53
-      }
54
-
55
-      onSearchBarUpdated(text, isFocused) {
56
-        if (this.originalComponentRef.onSearchBarUpdated) {
57
-          this.originalComponentRef.onSearchBarUpdated(text, isFocused);
58
-        }
59
-      }
60
-
61
-      onSearchBarCancelPressed() {
62
-        if (this.originalComponentRef.onSearchBarCancelPressed) {
63
-          this.originalComponentRef.onSearchBarCancelPressed();
64
-        }
32
+        componentEventsObserver.unmounted(this.state.componentId);
65 33
       }
66 34
 
67 35
       render() {
68 36
         return (
69 37
           <OriginalComponentClass
70
-            ref={this._saveComponentRef}
71 38
             {...this.state.allProps}
72 39
             componentId={this.state.componentId}
73 40
             key={this.state.componentId}
@@ -80,10 +47,6 @@ export class ComponentWrapper {
80 47
           throw new Error(`Component ${componentName} does not have a componentId!`);
81 48
         }
82 49
       }
83
-
84
-      private _saveComponentRef(r) {
85
-        this.originalComponentRef = r;
86
-      }
87 50
     }
88 51
 
89 52
     ReactLifecyclesCompat.polyfill(WrappedComponent);

+ 1
- 10
lib/src/components/Store.test.ts View File

@@ -30,20 +30,11 @@ describe('Store', () => {
30 30
     expect(uut.getOriginalComponentClassForName('example.mycomponent')).toEqual(MyComponent);
31 31
   });
32 32
 
33
-  it('holds component refs by id', () => {
34
-    const ref = {};
35
-    uut.setRefForId('refUniqueId', ref);
36
-    expect(uut.getRefForId('other')).toBeUndefined();
37
-    expect(uut.getRefForId('refUniqueId')).toBe(ref);
38
-  });
39
-
40
-  it('clean by id', () => {
41
-    uut.setRefForId('refUniqueId', {});
33
+  it('clean by component id', () => {
42 34
     uut.setPropsForId('refUniqueId', { foo: 'bar' });
43 35
 
44 36
     uut.cleanId('refUniqueId');
45 37
 
46
-    expect(uut.getRefForId('refUniqueId')).toBeUndefined();
47 38
     expect(uut.getPropsForId('refUniqueId')).toEqual({});
48 39
   });
49 40
 });

+ 0
- 10
lib/src/components/Store.ts View File

@@ -3,7 +3,6 @@ import * as _ from 'lodash';
3 3
 export class Store {
4 4
   private componentsByName = {};
5 5
   private propsById = {};
6
-  private refsById = {};
7 6
 
8 7
   setPropsForId(componentId: string, props) {
9 8
     _.set(this.propsById, componentId, props);
@@ -21,16 +20,7 @@ export class Store {
21 20
     return _.get(this.componentsByName, componentName);
22 21
   }
23 22
 
24
-  setRefForId(id: string, ref) {
25
-    _.set(this.refsById, id, ref);
26
-  }
27
-
28
-  getRefForId(id: string) {
29
-    return _.get(this.refsById, id);
30
-  }
31
-
32 23
   cleanId(id: string) {
33
-    _.unset(this.refsById, id);
34 24
     _.unset(this.propsById, id);
35 25
   }
36 26
 }

+ 1
- 1
lib/src/events/CommandsObserver.ts View File

@@ -15,6 +15,6 @@ export class CommandsObserver {
15 15
   }
16 16
 
17 17
   public notify(commandName: string, params: {}): void {
18
-    _.forEach(this.listeners, (listener) => listener(commandName, params));
18
+    _.forEach(this.listeners, (listener: CommandsListener) => listener(commandName, params));
19 19
   }
20 20
 }

+ 0
- 139
lib/src/events/ComponentEventsObserver.test.ts View File

@@ -1,139 +0,0 @@
1
-import { ComponentEventsObserver } from './ComponentEventsObserver';
2
-import { Store } from '../components/Store';
3
-
4
-describe(`ComponentEventsObserver`, () => {
5
-  let uut: ComponentEventsObserver;
6
-  let eventRegistry;
7
-  let store: Store;
8
-  let mockComponentRef;
9
-  const refId = 'myUniqueId';
10
-
11
-  beforeEach(() => {
12
-    eventRegistry = {
13
-      registerComponentDidAppearListener: jest.fn(),
14
-      registerComponentDidDisappearListener: jest.fn(),
15
-      registerNativeEventListener: jest.fn()
16
-    };
17
-
18
-    mockComponentRef = {
19
-      componentDidAppear: jest.fn(),
20
-      componentDidDisappear: jest.fn(),
21
-      onNavigationButtonPressed: jest.fn(),
22
-      onSearchBarUpdated: jest.fn(),
23
-      onSearchBarCancelPressed: jest.fn()
24
-    };
25
-
26
-    store = new Store();
27
-    store.setRefForId(refId, mockComponentRef);
28
-
29
-    uut = new ComponentEventsObserver(eventRegistry, store);
30
-  });
31
-
32
-  it('register for lifecycle events on eventRegistry', () => {
33
-    expect(eventRegistry.registerComponentDidAppearListener).toHaveBeenCalledTimes(0);
34
-    expect(eventRegistry.registerComponentDidDisappearListener).toHaveBeenCalledTimes(0);
35
-    expect(eventRegistry.registerNativeEventListener).toHaveBeenCalledTimes(0);
36
-    uut.registerForAllComponents();
37
-    expect(eventRegistry.registerComponentDidAppearListener).toHaveBeenCalledTimes(1);
38
-    expect(eventRegistry.registerComponentDidDisappearListener).toHaveBeenCalledTimes(1);
39
-    expect(eventRegistry.registerNativeEventListener).toHaveBeenCalledTimes(1);
40
-  });
41
-
42
-  it('bubbles lifecycle to component from store', () => {
43
-    const params = {};
44
-    expect(mockComponentRef.componentDidAppear).toHaveBeenCalledTimes(0);
45
-    expect(mockComponentRef.componentDidDisappear).toHaveBeenCalledTimes(0);
46
-    expect(mockComponentRef.onNavigationButtonPressed).toHaveBeenCalledTimes(0);
47
-    expect(mockComponentRef.onSearchBarUpdated).toHaveBeenCalledTimes(0);
48
-    expect(mockComponentRef.onSearchBarCancelPressed).toHaveBeenCalledTimes(0);
49
-    uut.registerForAllComponents();
50
-    eventRegistry.registerComponentDidAppearListener.mock.calls[0][0](refId);
51
-    eventRegistry.registerComponentDidDisappearListener.mock.calls[0][0](refId);
52
-    eventRegistry.registerNativeEventListener.mock.calls[0][0](refId, params);
53
-    expect(mockComponentRef.componentDidAppear).toHaveBeenCalledTimes(1);
54
-    expect(mockComponentRef.componentDidDisappear).toHaveBeenCalledTimes(1);
55
-    expect(mockComponentRef.onNavigationButtonPressed).toHaveBeenCalledTimes(0);
56
-    expect(mockComponentRef.onSearchBarUpdated).toHaveBeenCalledTimes(0);
57
-    expect(mockComponentRef.onSearchBarCancelPressed).toHaveBeenCalledTimes(0);
58
-  });
59
-
60
-  it('bubbles onNavigationButtonPressed to component by id', () => {
61
-    const params = {
62
-      componentId: refId,
63
-      buttonId: 'theButtonId'
64
-    };
65
-    expect(mockComponentRef.onNavigationButtonPressed).toHaveBeenCalledTimes(0);
66
-    uut.registerForAllComponents();
67
-
68
-    eventRegistry.registerNativeEventListener.mock.calls[0][0]('some other event name', params);
69
-    expect(mockComponentRef.onNavigationButtonPressed).toHaveBeenCalledTimes(0);
70
-
71
-    eventRegistry.registerNativeEventListener.mock.calls[0][0]('buttonPressed', params);
72
-    expect(mockComponentRef.onNavigationButtonPressed).toHaveBeenCalledTimes(1);
73
-    expect(mockComponentRef.onNavigationButtonPressed).toHaveBeenCalledWith('theButtonId');
74
-  });
75
-
76
-  it('bubbles onSearchUpdated to component by id', () => {
77
-    const params = {
78
-      componentId: refId,
79
-      text: 'query',
80
-      isFocused: true,
81
-    };
82
-    expect(mockComponentRef.onSearchBarUpdated).toHaveBeenCalledTimes(0);
83
-    uut.registerForAllComponents();
84
-
85
-    eventRegistry.registerNativeEventListener.mock.calls[0][0]('buttonPressed', params);
86
-    expect(mockComponentRef.onSearchBarUpdated).toHaveBeenCalledTimes(0);
87
-
88
-    eventRegistry.registerNativeEventListener.mock.calls[0][0]('searchBarUpdated', params);
89
-    expect(mockComponentRef.onSearchBarUpdated).toHaveBeenCalledTimes(1);
90
-    expect(mockComponentRef.onSearchBarUpdated).toHaveBeenCalledWith('query', true);
91
-    const paramsForUnexisted = {
92
-      componentId: 'NOT_EXISTED',
93
-      text: 'query',
94
-      isFocused: true,
95
-    };
96
-    eventRegistry.registerNativeEventListener.mock.calls[0][0]('searchBarUpdated', paramsForUnexisted);
97
-    expect(mockComponentRef.onSearchBarUpdated).toHaveBeenCalledTimes(1);
98
-    expect(mockComponentRef.onSearchBarUpdated).toHaveBeenCalledWith('query', true);
99
-  });
100
-
101
-  it('bubbles onSearchBarCancelPressed to component by id', () => {
102
-    const params = {
103
-      componentId: refId,
104
-    };
105
-    expect(mockComponentRef.onSearchBarCancelPressed).toHaveBeenCalledTimes(0);
106
-    uut.registerForAllComponents();
107
-
108
-    eventRegistry.registerNativeEventListener.mock.calls[0][0]('buttonPressed', params);
109
-    expect(mockComponentRef.onSearchBarCancelPressed).toHaveBeenCalledTimes(0);
110
-
111
-    eventRegistry.registerNativeEventListener.mock.calls[0][0]('searchBarCancelPressed', params);
112
-    expect(mockComponentRef.onSearchBarCancelPressed).toHaveBeenCalledTimes(1);
113
-
114
-    const paramsForUnexisted = {
115
-      componentId: 'NOT_EXISTED',
116
-    };
117
-    eventRegistry.registerNativeEventListener.mock.calls[0][0]('searchBarCancelPressed', paramsForUnexisted);
118
-    expect(mockComponentRef.onSearchBarCancelPressed).toHaveBeenCalledTimes(1);
119
-  });
120
-
121
-  it('defensive unknown id', () => {
122
-    uut.registerForAllComponents();
123
-    expect(() => {
124
-      eventRegistry.registerComponentDidAppearListener.mock.calls[0][0]('bad id');
125
-      eventRegistry.registerComponentDidDisappearListener.mock.calls[0][0]('bad id');
126
-      eventRegistry.registerNativeEventListener.mock.calls[0][0]('buttonPressed', { componentId: 'bad id' });
127
-    }).not.toThrow();
128
-  });
129
-
130
-  it('defensive method impl', () => {
131
-    store.setRefForId('myId', {});
132
-    uut.registerForAllComponents();
133
-    expect(() => {
134
-      eventRegistry.registerComponentDidAppearListener.mock.calls[0][0]('myId');
135
-      eventRegistry.registerComponentDidDisappearListener.mock.calls[0][0]('myId');
136
-      eventRegistry.registerNativeEventListener.mock.calls[0][0]('bad event name', {});
137
-    }).not.toThrow();
138
-  });
139
-});

+ 181
- 0
lib/src/events/ComponentEventsObserver.test.tsx View File

@@ -0,0 +1,181 @@
1
+import * as React from 'react';
2
+import * as renderer from 'react-test-renderer';
3
+import { ComponentEventsObserver } from './ComponentEventsObserver';
4
+import { NativeEventsReceiver } from '../adapters/NativeEventsReceiver.mock';
5
+
6
+describe('ComponentEventsObserver', () => {
7
+  const mockEventsReceiver = new NativeEventsReceiver();
8
+  const uut = new ComponentEventsObserver(mockEventsReceiver);
9
+  const didAppearFn = jest.fn();
10
+  const didDisappearFn = jest.fn();
11
+  const didMountFn = jest.fn();
12
+  const willUnmountFn = jest.fn();
13
+  const navigationButtonPressedFn = jest.fn();
14
+  const searchBarUpdatedFn = jest.fn();
15
+  const searchBarCancelPressedFn = jest.fn();
16
+  let subscription;
17
+
18
+  class SimpleScreen extends React.Component<any, any> {
19
+    render() {
20
+      return 'Hello';
21
+    }
22
+  }
23
+
24
+  class BoundScreen extends React.Component<any, any> {
25
+    constructor(props) {
26
+      super(props);
27
+      subscription = uut.bindComponent(this);
28
+    }
29
+
30
+    componentDidMount() {
31
+      didMountFn();
32
+    }
33
+
34
+    componentWillUnmount() {
35
+      willUnmountFn();
36
+    }
37
+
38
+    componentDidAppear() {
39
+      didAppearFn();
40
+    }
41
+
42
+    componentDidDisappear() {
43
+      didDisappearFn();
44
+    }
45
+
46
+    navigationButtonPressed(event) {
47
+      navigationButtonPressedFn(event);
48
+    }
49
+
50
+    searchBarUpdated(event) {
51
+      searchBarUpdatedFn(event);
52
+    }
53
+
54
+    searchBarCancelPressed(event) {
55
+      searchBarCancelPressedFn(event);
56
+    }
57
+
58
+    render() {
59
+      return 'Hello';
60
+    }
61
+  }
62
+
63
+  it(`bindComponent expects a component with componentId`, () => {
64
+    const tree = renderer.create(<SimpleScreen />);
65
+    expect(() => uut.bindComponent(tree.getInstance() as any)).toThrow('');
66
+    const tree2 = renderer.create(<SimpleScreen componentId={123} />);
67
+    expect(() => uut.bindComponent(tree2.getInstance() as any)).toThrow('');
68
+  });
69
+
70
+  it(`bindComponent notifies listeners by componentId on events`, () => {
71
+    const tree = renderer.create(<BoundScreen componentId={'myCompId'} />);
72
+    expect(tree.toJSON()).toBeDefined();
73
+    expect(didMountFn).toHaveBeenCalledTimes(1);
74
+    expect(didAppearFn).not.toHaveBeenCalled();
75
+    expect(didDisappearFn).not.toHaveBeenCalled();
76
+    expect(willUnmountFn).not.toHaveBeenCalled();
77
+
78
+    uut.notifyComponentDidAppear({ componentId: 'myCompId', componentName: 'doesnt matter' });
79
+    expect(didAppearFn).toHaveBeenCalledTimes(1);
80
+
81
+    uut.notifyComponentDidDisappear({ componentId: 'myCompId', componentName: 'doesnt matter' });
82
+    expect(didDisappearFn).toHaveBeenCalledTimes(1);
83
+
84
+    uut.notifyNavigationButtonPressed({ componentId: 'myCompId', buttonId: 'myButtonId' });
85
+    expect(navigationButtonPressedFn).toHaveBeenCalledTimes(1);
86
+    expect(navigationButtonPressedFn).toHaveBeenCalledWith({ buttonId: 'myButtonId', componentId: 'myCompId' });
87
+
88
+    uut.notifySearchBarUpdated({ componentId: 'myCompId', text: 'theText', isFocused: true });
89
+    expect(searchBarUpdatedFn).toHaveBeenCalledTimes(1);
90
+    expect(searchBarUpdatedFn).toHaveBeenCalledWith({ componentId: 'myCompId', text: 'theText', isFocused: true });
91
+
92
+    uut.notifySearchBarCancelPressed({ componentId: 'myCompId' });
93
+    expect(searchBarCancelPressedFn).toHaveBeenCalledTimes(1);
94
+    expect(searchBarCancelPressedFn).toHaveBeenCalledWith({ componentId: 'myCompId' });
95
+
96
+    tree.unmount();
97
+    expect(willUnmountFn).toHaveBeenCalledTimes(1);
98
+  });
99
+
100
+  it(`doesnt call other componentIds`, () => {
101
+    renderer.create(<BoundScreen componentId={'myCompId'} />);
102
+    uut.notifyComponentDidAppear({ componentId: 'other', componentName: 'doesnt matter' });
103
+    expect(didAppearFn).not.toHaveBeenCalled();
104
+  });
105
+
106
+  it(`doesnt call unimplemented methods`, () => {
107
+    const tree = renderer.create(<SimpleScreen componentId={'myCompId'} />);
108
+    expect((tree.getInstance() as any).componentDidAppear).toBeUndefined();
109
+    uut.bindComponent(tree.getInstance() as any);
110
+    uut.notifyComponentDidAppear({ componentId: 'myCompId', componentName: 'doesnt matter' });
111
+  });
112
+
113
+  it(`returns unregister fn`, () => {
114
+    renderer.create(<BoundScreen componentId={'123'} />);
115
+
116
+    uut.notifyComponentDidAppear({ componentId: '123', componentName: 'doesnt matter' });
117
+    expect(didAppearFn).toHaveBeenCalledTimes(1);
118
+
119
+    subscription.remove();
120
+
121
+    uut.notifyComponentDidAppear({ componentId: '123', componentName: 'doesnt matter' });
122
+    expect(didAppearFn).toHaveBeenCalledTimes(1);
123
+  });
124
+
125
+  it(`removeAllListenersForComponentId`, () => {
126
+    renderer.create(<BoundScreen componentId={'123'} />);
127
+    renderer.create(<BoundScreen componentId={'123'} />);
128
+
129
+    uut.unmounted('123');
130
+
131
+    uut.notifyComponentDidAppear({ componentId: '123', componentName: 'doesnt matter' });
132
+    expect(didAppearFn).not.toHaveBeenCalled();
133
+  });
134
+
135
+  it(`supports multiple listeners with same componentId`, () => {
136
+    const tree1 = renderer.create(<SimpleScreen componentId={'myCompId'} />);
137
+    const tree2 = renderer.create(<SimpleScreen componentId={'myCompId'} />);
138
+    const instance1 = tree1.getInstance() as any;
139
+    const instance2 = tree2.getInstance() as any;
140
+    instance1.componentDidAppear = jest.fn();
141
+    instance2.componentDidAppear = jest.fn();
142
+
143
+    const result1 = uut.bindComponent(instance1);
144
+    const result2 = uut.bindComponent(instance2);
145
+    expect(result1).not.toEqual(result2);
146
+
147
+    uut.notifyComponentDidAppear({ componentId: 'myCompId', componentName: 'doesnt matter' });
148
+
149
+    expect(instance1.componentDidAppear).toHaveBeenCalledTimes(1);
150
+    expect(instance2.componentDidAppear).toHaveBeenCalledTimes(1);
151
+
152
+    result2.remove();
153
+
154
+    uut.notifyComponentDidAppear({ componentId: 'myCompId', componentName: 'doesnt matter' });
155
+    expect(instance1.componentDidAppear).toHaveBeenCalledTimes(2);
156
+    expect(instance2.componentDidAppear).toHaveBeenCalledTimes(1);
157
+
158
+    result1.remove();
159
+
160
+    uut.notifyComponentDidAppear({ componentId: 'myCompId', componentName: 'doesnt matter' });
161
+    expect(instance1.componentDidAppear).toHaveBeenCalledTimes(2);
162
+    expect(instance2.componentDidAppear).toHaveBeenCalledTimes(1);
163
+  });
164
+
165
+  it(`register for all native component events notifies self on events, once`, () => {
166
+    expect(mockEventsReceiver.registerComponentDidAppearListener).not.toHaveBeenCalled();
167
+    expect(mockEventsReceiver.registerComponentDidDisappearListener).not.toHaveBeenCalled();
168
+    expect(mockEventsReceiver.registerNavigationButtonPressedListener).not.toHaveBeenCalled();
169
+    expect(mockEventsReceiver.registerSearchBarUpdatedListener).not.toHaveBeenCalled();
170
+    expect(mockEventsReceiver.registerSearchBarCancelPressedListener).not.toHaveBeenCalled();
171
+    uut.registerOnceForAllComponentEvents();
172
+    uut.registerOnceForAllComponentEvents();
173
+    uut.registerOnceForAllComponentEvents();
174
+    uut.registerOnceForAllComponentEvents();
175
+    expect(mockEventsReceiver.registerComponentDidAppearListener).toHaveBeenCalledTimes(1);
176
+    expect(mockEventsReceiver.registerComponentDidDisappearListener).toHaveBeenCalledTimes(1);
177
+    expect(mockEventsReceiver.registerNavigationButtonPressedListener).toHaveBeenCalledTimes(1);
178
+    expect(mockEventsReceiver.registerSearchBarUpdatedListener).toHaveBeenCalledTimes(1);
179
+    expect(mockEventsReceiver.registerSearchBarCancelPressedListener).toHaveBeenCalledTimes(1);
180
+  });
181
+});

+ 66
- 41
lib/src/events/ComponentEventsObserver.ts View File

@@ -1,55 +1,80 @@
1
-import { EventsRegistry } from './EventsRegistry';
2
-import { Store } from '../components/Store';
3
-
4
-const BUTTON_PRESSED_EVENT_NAME = 'buttonPressed';
5
-const ON_SEARCH_BAR_UPDATED = 'searchBarUpdated';
6
-const ON_SEARCH_BAR_CANCEL_PRESSED = 'searchBarCancelPressed';
1
+import * as _ from 'lodash';
2
+import { EventSubscription } from '../interfaces/EventSubscription';
3
+import {
4
+  ComponentDidAppearEvent,
5
+  ComponentDidDisappearEvent,
6
+  NavigationButtonPressedEvent,
7
+  SearchBarUpdatedEvent,
8
+  SearchBarCancelPressedEvent,
9
+  ComponentEvent
10
+} from '../interfaces/ComponentEvents';
11
+import { NativeEventsReceiver } from '../adapters/NativeEventsReceiver';
7 12
 
8 13
 export class ComponentEventsObserver {
9
-  constructor(private eventsRegistry: EventsRegistry, private store: Store) {
10
-    this.componentDidAppear = this.componentDidAppear.bind(this);
11
-    this.componentDidDisappear = this.componentDidDisappear.bind(this);
12
-    this.onNativeEvent = this.onNativeEvent.bind(this);
14
+  private readonly listeners = {};
15
+  private alreadyRegistered = false;
16
+
17
+  constructor(private readonly nativeEventsReceiver: NativeEventsReceiver) {
18
+    this.notifyComponentDidAppear = this.notifyComponentDidAppear.bind(this);
19
+    this.notifyComponentDidDisappear = this.notifyComponentDidDisappear.bind(this);
20
+    this.notifyNavigationButtonPressed = this.notifyNavigationButtonPressed.bind(this);
21
+    this.notifySearchBarUpdated = this.notifySearchBarUpdated.bind(this);
22
+    this.notifySearchBarCancelPressed = this.notifySearchBarCancelPressed.bind(this);
13 23
   }
14 24
 
15
-  public registerForAllComponents(): void {
16
-    this.eventsRegistry.registerComponentDidAppearListener(this.componentDidAppear);
17
-    this.eventsRegistry.registerComponentDidDisappearListener(this.componentDidDisappear);
18
-    this.eventsRegistry.registerNativeEventListener(this.onNativeEvent);
25
+  public registerOnceForAllComponentEvents() {
26
+    if (this.alreadyRegistered) { return; }
27
+    this.alreadyRegistered = true;
28
+    this.nativeEventsReceiver.registerComponentDidAppearListener(this.notifyComponentDidAppear);
29
+    this.nativeEventsReceiver.registerComponentDidDisappearListener(this.notifyComponentDidDisappear);
30
+    this.nativeEventsReceiver.registerNavigationButtonPressedListener(this.notifyNavigationButtonPressed);
31
+    this.nativeEventsReceiver.registerSearchBarUpdatedListener(this.notifySearchBarUpdated);
32
+    this.nativeEventsReceiver.registerSearchBarCancelPressedListener(this.notifySearchBarCancelPressed);
19 33
   }
20 34
 
21
-  private componentDidAppear(componentId: string) {
22
-    const componentRef = this.store.getRefForId(componentId);
23
-    if (componentRef && componentRef.componentDidAppear) {
24
-      componentRef.componentDidAppear();
35
+  public bindComponent(component: React.Component<any>): EventSubscription {
36
+    const componentId = component.props.componentId;
37
+    if (!_.isString(componentId)) {
38
+      throw new Error(`bindComponent expects a component with a componentId in props`);
39
+    }
40
+    if (_.isNil(this.listeners[componentId])) {
41
+      this.listeners[componentId] = {};
25 42
     }
43
+    const key = _.uniqueId();
44
+    this.listeners[componentId][key] = component;
45
+
46
+    return { remove: () => _.unset(this.listeners[componentId], key) };
26 47
   }
27 48
 
28
-  private componentDidDisappear(componentId: string) {
29
-    const componentRef = this.store.getRefForId(componentId);
30
-    if (componentRef && componentRef.componentDidDisappear) {
31
-      componentRef.componentDidDisappear();
32
-    }
49
+  public unmounted(componentId: string) {
50
+    _.unset(this.listeners, componentId);
33 51
   }
34 52
 
35
-  private onNativeEvent(name: string, params: any) {
36
-    if (name === BUTTON_PRESSED_EVENT_NAME) {
37
-      const componentRef = this.store.getRefForId(params.componentId);
38
-      if (componentRef && componentRef.onNavigationButtonPressed) {
39
-        componentRef.onNavigationButtonPressed(params.buttonId);
40
-      }
41
-    }
42
-    if (name === ON_SEARCH_BAR_UPDATED) {
43
-      const componentRef = this.store.getRefForId(params.componentId);
44
-      if (componentRef && componentRef.onSearchBarUpdated) {
45
-        componentRef.onSearchBarUpdated(params.text, params.isFocused);
46
-      }
47
-    }
48
-    if (name === ON_SEARCH_BAR_CANCEL_PRESSED) {
49
-      const componentRef = this.store.getRefForId(params.componentId);
50
-      if (componentRef && componentRef.onSearchBarCancelPressed) {
51
-        componentRef.onSearchBarCancelPressed();
53
+  notifyComponentDidAppear(event: ComponentDidAppearEvent) {
54
+    this.triggerOnAllListenersByComponentId(event, 'componentDidAppear');
55
+  }
56
+
57
+  notifyComponentDidDisappear(event: ComponentDidDisappearEvent) {
58
+    this.triggerOnAllListenersByComponentId(event, 'componentDidDisappear');
59
+  }
60
+
61
+  notifyNavigationButtonPressed(event: NavigationButtonPressedEvent) {
62
+    this.triggerOnAllListenersByComponentId(event, 'navigationButtonPressed');
63
+  }
64
+
65
+  notifySearchBarUpdated(event: SearchBarUpdatedEvent) {
66
+    this.triggerOnAllListenersByComponentId(event, 'searchBarUpdated');
67
+  }
68
+
69
+  notifySearchBarCancelPressed(event: SearchBarCancelPressedEvent) {
70
+    this.triggerOnAllListenersByComponentId(event, 'searchBarCancelPressed');
71
+  }
72
+
73
+  private triggerOnAllListenersByComponentId(event: ComponentEvent, method: string) {
74
+    _.forEach(this.listeners[event.componentId], (component) => {
75
+      if (_.isObject(component) && _.isFunction(component[method])) {
76
+        component[method](event);
52 77
       }
53
-    }
78
+    });
54 79
   }
55 80
 }

+ 0
- 99
lib/src/events/EventsRegistry.test.ts View File

@@ -1,99 +0,0 @@
1
-import { EventsRegistry } from './EventsRegistry';
2
-import { NativeEventsReceiver } from '../adapters/NativeEventsReceiver.mock';
3
-import { CommandsObserver } from './CommandsObserver';
4
-
5
-describe('EventsRegistry', () => {
6
-  let uut: EventsRegistry;
7
-  const mockNativeEventsReceiver = new NativeEventsReceiver();
8
-  let commandsObserver: CommandsObserver;
9
-
10
-  beforeEach(() => {
11
-    commandsObserver = new CommandsObserver();
12
-    uut = new EventsRegistry(mockNativeEventsReceiver, commandsObserver);
13
-  });
14
-
15
-  it('exposes appLaunched event', () => {
16
-    const subscription = {};
17
-    const cb = jest.fn();
18
-    mockNativeEventsReceiver.registerAppLaunchedListener.mockReturnValueOnce(subscription);
19
-
20
-    const result = uut.registerAppLaunchedListener(cb);
21
-
22
-    expect(result).toBe(subscription);
23
-    expect(mockNativeEventsReceiver.registerAppLaunchedListener).toHaveBeenCalledTimes(1);
24
-    expect(mockNativeEventsReceiver.registerAppLaunchedListener).toHaveBeenCalledWith(cb);
25
-  });
26
-
27
-  it('exposes componentDidAppear event', () => {
28
-    const subscription = {};
29
-    const cb = jest.fn();
30
-    mockNativeEventsReceiver.registerComponentDidAppearListener.mockReturnValueOnce(subscription);
31
-
32
-    const result = uut.registerComponentDidAppearListener(cb);
33
-
34
-    expect(result).toBe(subscription);
35
-    expect(mockNativeEventsReceiver.registerComponentDidAppearListener).toHaveBeenCalledTimes(1);
36
-
37
-    mockNativeEventsReceiver.registerComponentDidAppearListener.mock.calls[0][0]({ componentId: 'theId', componentName: 'theName' });
38
-    expect(cb).toHaveBeenCalledWith('theId', 'theName');
39
-  });
40
-
41
-  it('exposes componentDidDisappear event', () => {
42
-    const subscription = {};
43
-    const cb = jest.fn();
44
-    mockNativeEventsReceiver.registerComponentDidDisappearListener.mockReturnValueOnce(subscription);
45
-
46
-    const result = uut.registerComponentDidDisappearListener(cb);
47
-
48
-    expect(result).toBe(subscription);
49
-    expect(mockNativeEventsReceiver.registerComponentDidDisappearListener).toHaveBeenCalledTimes(1);
50
-
51
-    mockNativeEventsReceiver.registerComponentDidDisappearListener.mock.calls[0][0]({ componentId: 'theId', componentName: 'theName' });
52
-    expect(cb).toHaveBeenCalledWith('theId', 'theName');
53
-  });
54
-
55
-  it('exposes registerCommandListener registers listener to commandObserver', () => {
56
-    const cb = jest.fn();
57
-    const result = uut.registerCommandListener(cb);
58
-    expect(result).toBeDefined();
59
-    commandsObserver.notify('theCommandName', { x: 1 });
60
-    expect(cb).toHaveBeenCalledTimes(1);
61
-    expect(cb).toHaveBeenCalledWith('theCommandName', { x: 1 });
62
-  });
63
-
64
-  it('registerCommandListener unregister', () => {
65
-    const cb = jest.fn();
66
-    const result = uut.registerCommandListener(cb);
67
-    result.remove();
68
-    commandsObserver.notify('theCommandName', { x: 1 });
69
-    expect(cb).not.toHaveBeenCalled();
70
-  });
71
-
72
-  it('registerCommandCompletedListener', () => {
73
-    const subscription = {};
74
-    const cb = jest.fn();
75
-    mockNativeEventsReceiver.registerCommandCompletedListener.mockReturnValueOnce(subscription);
76
-
77
-    const result = uut.registerCommandCompletedListener(cb);
78
-
79
-    expect(result).toBe(subscription);
80
-    expect(mockNativeEventsReceiver.registerCommandCompletedListener).toHaveBeenCalledTimes(1);
81
-
82
-    mockNativeEventsReceiver.registerCommandCompletedListener.mock.calls[0][0]({ commandId: 'theCommandId', completionTime: 12345, params: { a: 1 } });
83
-    expect(cb).toHaveBeenCalledWith('theCommandId', 12345, { a: 1 });
84
-  });
85
-
86
-  it('registerNativeEventListener', () => {
87
-    const subscription = {};
88
-    const cb = jest.fn();
89
-    mockNativeEventsReceiver.registerNativeEventListener.mockReturnValueOnce(subscription);
90
-
91
-    const result = uut.registerNativeEventListener(cb);
92
-
93
-    expect(result).toBe(subscription);
94
-    expect(mockNativeEventsReceiver.registerNativeEventListener).toHaveBeenCalledTimes(1);
95
-
96
-    mockNativeEventsReceiver.registerNativeEventListener.mock.calls[0][0]({ name: 'the event name', params: { a: 1 } });
97
-    expect(cb).toHaveBeenCalledWith('the event name', { a: 1 });
98
-  });
99
-});

+ 100
- 0
lib/src/events/EventsRegistry.test.tsx View File

@@ -0,0 +1,100 @@
1
+import { EventsRegistry } from './EventsRegistry';
2
+import { NativeEventsReceiver } from '../adapters/NativeEventsReceiver.mock';
3
+import { CommandsObserver } from './CommandsObserver';
4
+
5
+describe('EventsRegistry', () => {
6
+  let uut: EventsRegistry;
7
+  const mockNativeEventsReceiver = new NativeEventsReceiver();
8
+  let commandsObserver: CommandsObserver;
9
+  const mockScreenEventsRegistry = {} as any;
10
+
11
+  beforeEach(() => {
12
+    commandsObserver = new CommandsObserver();
13
+    uut = new EventsRegistry(mockNativeEventsReceiver, commandsObserver, mockScreenEventsRegistry);
14
+  });
15
+
16
+  it('exposes appLaunched event', () => {
17
+    const subscription = {};
18
+    const cb = jest.fn();
19
+    mockNativeEventsReceiver.registerAppLaunchedListener.mockReturnValueOnce(subscription);
20
+
21
+    const result = uut.registerAppLaunchedListener(cb);
22
+
23
+    expect(result).toBe(subscription);
24
+    expect(mockNativeEventsReceiver.registerAppLaunchedListener).toHaveBeenCalledTimes(1);
25
+    expect(mockNativeEventsReceiver.registerAppLaunchedListener).toHaveBeenCalledWith(cb);
26
+  });
27
+
28
+  it('delegates didAppear to nativeEventsReceiver', () => {
29
+    const cb = jest.fn();
30
+    uut.registerComponentDidAppearListener(cb);
31
+    expect(mockNativeEventsReceiver.registerComponentDidAppearListener).toHaveBeenCalledTimes(1);
32
+    expect(mockNativeEventsReceiver.registerComponentDidAppearListener).toHaveBeenCalledWith(cb);
33
+  });
34
+
35
+  it('delegates didDisappear to nativeEventsReceiver', () => {
36
+    const cb = jest.fn();
37
+    uut.registerComponentDidDisappearListener(cb);
38
+    expect(mockNativeEventsReceiver.registerComponentDidDisappearListener).toHaveBeenCalledTimes(1);
39
+    expect(mockNativeEventsReceiver.registerComponentDidDisappearListener).toHaveBeenCalledWith(cb);
40
+  });
41
+
42
+  it('delegates commandCompleted to nativeEventsReceiver', () => {
43
+    const cb = jest.fn();
44
+    uut.registerCommandCompletedListener(cb);
45
+    expect(mockNativeEventsReceiver.registerCommandCompletedListener).toHaveBeenCalledTimes(1);
46
+    expect(mockNativeEventsReceiver.registerCommandCompletedListener).toHaveBeenCalledWith(cb);
47
+  });
48
+
49
+  it('delegates BottomTabsSelected to nativeEventsReceiver', () => {
50
+    const cb = jest.fn();
51
+    uut.registerBottomTabSelectedListener(cb);
52
+    expect(mockNativeEventsReceiver.registerBottomTabSelectedListener).toHaveBeenCalledTimes(1);
53
+    expect(mockNativeEventsReceiver.registerBottomTabSelectedListener).toHaveBeenCalledWith(cb);
54
+  });
55
+
56
+  it('delegates navigationButtonPressed to nativeEventsReceiver', () => {
57
+    const cb = jest.fn();
58
+    uut.registerNavigationButtonPressedListener(cb);
59
+    expect(mockNativeEventsReceiver.registerNavigationButtonPressedListener).toHaveBeenCalledTimes(1);
60
+    expect(mockNativeEventsReceiver.registerNavigationButtonPressedListener).toHaveBeenCalledWith(cb);
61
+  });
62
+
63
+  it('delegates searchBarUpdated to nativeEventsReceiver', () => {
64
+    const cb = jest.fn();
65
+    uut.registerSearchBarUpdatedListener(cb);
66
+    expect(mockNativeEventsReceiver.registerSearchBarUpdatedListener).toHaveBeenCalledTimes(1);
67
+    expect(mockNativeEventsReceiver.registerSearchBarUpdatedListener).toHaveBeenCalledWith(cb);
68
+  });
69
+
70
+  it('delegates searchBarCancelPressed to nativeEventsReceiver', () => {
71
+    const cb = jest.fn();
72
+    uut.registerSearchBarCancelPressedListener(cb);
73
+    expect(mockNativeEventsReceiver.registerSearchBarCancelPressedListener).toHaveBeenCalledTimes(1);
74
+    expect(mockNativeEventsReceiver.registerSearchBarCancelPressedListener).toHaveBeenCalledWith(cb);
75
+  });
76
+
77
+  it('delegates registerCommandListener to commandObserver', () => {
78
+    const cb = jest.fn();
79
+    const result = uut.registerCommandListener(cb);
80
+    expect(result).toBeDefined();
81
+    commandsObserver.notify('theCommandName', { x: 1 });
82
+    expect(cb).toHaveBeenCalledTimes(1);
83
+    expect(cb).toHaveBeenCalledWith('theCommandName', { x: 1 });
84
+  });
85
+
86
+  it('registerCommandListener unregister', () => {
87
+    const cb = jest.fn();
88
+    const result = uut.registerCommandListener(cb);
89
+    result.remove();
90
+    commandsObserver.notify('theCommandName', { x: 1 });
91
+    expect(cb).not.toHaveBeenCalled();
92
+  });
93
+
94
+  it(`delegates bindComponent to ComponentObserver`, () => {
95
+    const subscription = {};
96
+    mockScreenEventsRegistry.bindComponent = jest.fn();
97
+    mockScreenEventsRegistry.bindComponent.mockReturnValueOnce(subscription);
98
+    expect(uut.bindComponent({} as React.Component<any>)).toEqual(subscription);
99
+  });
100
+});

+ 36
- 11
lib/src/events/EventsRegistry.ts View File

@@ -1,31 +1,56 @@
1 1
 import { NativeEventsReceiver } from '../adapters/NativeEventsReceiver';
2 2
 import { CommandsObserver } from './CommandsObserver';
3 3
 import { EventSubscription } from '../interfaces/EventSubscription';
4
+import { ComponentEventsObserver } from './ComponentEventsObserver';
5
+import {
6
+  ComponentDidAppearEvent,
7
+  ComponentDidDisappearEvent,
8
+  NavigationButtonPressedEvent,
9
+  SearchBarUpdatedEvent,
10
+  SearchBarCancelPressedEvent
11
+} from '../interfaces/ComponentEvents';
12
+import { CommandCompletedEvent, BottomTabSelectedEvent } from '../interfaces/Events';
4 13
 
5 14
 export class EventsRegistry {
6
-  constructor(private nativeEventsReceiver: NativeEventsReceiver, private commandsObserver: CommandsObserver) { }
15
+  constructor(private nativeEventsReceiver: NativeEventsReceiver, private commandsObserver: CommandsObserver, private componentEventsObserver: ComponentEventsObserver) { }
7 16
 
8 17
   public registerAppLaunchedListener(callback: () => void): EventSubscription {
9 18
     return this.nativeEventsReceiver.registerAppLaunchedListener(callback);
10 19
   }
11 20
 
12
-  public registerComponentDidAppearListener(callback: (componentId: string, componentName: string) => void): EventSubscription {
13
-    return this.nativeEventsReceiver.registerComponentDidAppearListener(({ componentId, componentName }) => callback(componentId, componentName));
21
+  public registerComponentDidAppearListener(callback: (event: ComponentDidAppearEvent) => void): EventSubscription {
22
+    return this.nativeEventsReceiver.registerComponentDidAppearListener(callback);
14 23
   }
15 24
 
16
-  public registerComponentDidDisappearListener(callback: (componentId: string, componentName: string) => void): EventSubscription {
17
-    return this.nativeEventsReceiver.registerComponentDidDisappearListener(({ componentId, componentName }) => callback(componentId, componentName));
25
+  public registerComponentDidDisappearListener(callback: (event: ComponentDidDisappearEvent) => void): EventSubscription {
26
+    return this.nativeEventsReceiver.registerComponentDidDisappearListener(callback);
18 27
   }
19 28
 
20
-  public registerCommandListener(callback: (name: string, params: any) => void): EventSubscription {
21
-    return this.commandsObserver.register(callback);
29
+  public registerCommandCompletedListener(callback: (event: CommandCompletedEvent) => void): EventSubscription {
30
+    return this.nativeEventsReceiver.registerCommandCompletedListener(callback);
31
+  }
32
+
33
+  public registerBottomTabSelectedListener(callback: (event: BottomTabSelectedEvent) => void): EventSubscription {
34
+    return this.nativeEventsReceiver.registerBottomTabSelectedListener(callback);
35
+  }
36
+
37
+  public registerNavigationButtonPressedListener(callback: (event: NavigationButtonPressedEvent) => void): EventSubscription {
38
+    return this.nativeEventsReceiver.registerNavigationButtonPressedListener(callback);
22 39
   }
23 40
 
24
-  public registerCommandCompletedListener(callback: (commandId: string, completionTime: number, params: any) => void): EventSubscription {
25
-    return this.nativeEventsReceiver.registerCommandCompletedListener(({ commandId, completionTime, params }) => callback(commandId, completionTime, params));
41
+  public registerSearchBarUpdatedListener(callback: (event: SearchBarUpdatedEvent) => void): EventSubscription {
42
+    return this.nativeEventsReceiver.registerSearchBarUpdatedListener(callback);
43
+  }
44
+
45
+  public registerSearchBarCancelPressedListener(callback: (event: SearchBarCancelPressedEvent) => void): EventSubscription {
46
+    return this.nativeEventsReceiver.registerSearchBarCancelPressedListener(callback);
47
+  }
48
+
49
+  public registerCommandListener(callback: (name: string, params: any) => void): EventSubscription {
50
+    return this.commandsObserver.register(callback);
26 51
   }
27 52
 
28
-  public registerNativeEventListener(callback: (name: string, params: any) => void): EventSubscription {
29
-    return this.nativeEventsReceiver.registerNativeEventListener(({ name, params }) => callback(name, params));
53
+  public bindComponent(component: React.Component<any>): EventSubscription {
54
+    return this.componentEventsObserver.bindComponent(component);
30 55
   }
31 56
 }

+ 24
- 0
lib/src/interfaces/ComponentEvents.ts View File

@@ -0,0 +1,24 @@
1
+export interface ComponentEvent {
2
+  componentId: string;
3
+}
4
+
5
+export interface ComponentDidAppearEvent extends ComponentEvent {
6
+  componentName: string;
7
+}
8
+
9
+export interface ComponentDidDisappearEvent extends ComponentEvent {
10
+  componentName: string;
11
+}
12
+
13
+export interface NavigationButtonPressedEvent extends ComponentEvent {
14
+  buttonId: string;
15
+}
16
+
17
+export interface SearchBarUpdatedEvent extends ComponentEvent {
18
+  text: string;
19
+  isFocused: boolean;
20
+}
21
+
22
+export interface SearchBarCancelPressedEvent extends ComponentEvent {
23
+  componentName?: string;
24
+}

+ 10
- 0
lib/src/interfaces/Events.ts View File

@@ -0,0 +1,10 @@
1
+export interface CommandCompletedEvent {
2
+  commandId: string;
3
+  completionTime: number;
4
+  params: any;
5
+}
6
+
7
+export interface BottomTabSelectedEvent {
8
+  selectedTabIndex: number;
9
+  unselectedTabIndex: number;
10
+}

+ 3
- 0
playground/src/screens/CustomRoundedButton.js View File

@@ -8,11 +8,13 @@ const {
8 8
   Alert,
9 9
   Platform
10 10
 } = require('react-native');
11
+const { Navigation } = require('react-native-navigation');
11 12
 
12 13
 class CustomRoundedButton extends Component {
13 14
 
14 15
   constructor(props) {
15 16
     super(props);
17
+    this.subscription = Navigation.events().bindComponent(this);
16 18
     this.state = {};
17 19
   }
18 20
 
@@ -29,6 +31,7 @@ class CustomRoundedButton extends Component {
29 31
   }
30 32
 
31 33
   componentWillUnmount() {
34
+    this.subscription.remove();
32 35
     console.log('RNN', `CRB.componentWillUnmount`);
33 36
   }
34 37
 

+ 3
- 0
playground/src/screens/CustomTopBar.js View File

@@ -8,12 +8,14 @@ const {
8 8
   Alert,
9 9
   Platform
10 10
 } = require('react-native');
11
+const { Navigation } = require('react-native-navigation');
11 12
 
12 13
 class CustomTopBar extends Component {
13 14
 
14 15
   constructor(props) {
15 16
     super(props);
16 17
     this.state = {};
18
+    this.subscription = Navigation.events().bindComponent(this);
17 19
   }
18 20
 
19 21
   componentDidAppear() {
@@ -30,6 +32,7 @@ class CustomTopBar extends Component {
30 32
 
31 33
   componentWillUnmount() {
32 34
     console.log('RNN', `CTB.componentWillUnmount`);
35
+    this.subscription.remove();
33 36
   }
34 37
 
35 38
   render() {

+ 4
- 2
playground/src/screens/LifecycleScreen.js View File

@@ -13,6 +13,7 @@ class LifecycleScreen extends Component {
13 13
     this.state = {
14 14
       text: 'nothing yet'
15 15
     };
16
+    this.subscription = Navigation.events().bindComponent(this);
16 17
   }
17 18
 
18 19
   componentDidAppear() {
@@ -24,11 +25,12 @@ class LifecycleScreen extends Component {
24 25
   }
25 26
 
26 27
   componentWillUnmount() {
28
+    this.subscription.remove();
27 29
     alert('componentWillUnmount'); // eslint-disable-line no-alert
28 30
   }
29 31
 
30
-  onNavigationButtonPressed(id) {
31
-    alert(`onNavigationButtonPressed: ${id}`); // eslint-disable-line no-alert
32
+  navigationButtonPressed(id) {
33
+    alert(`navigationButtonPressed: ${id}`); // eslint-disable-line no-alert
32 34
   }
33 35
 
34 36
   render() {

+ 9
- 4
playground/src/screens/OptionsScreen.js View File

@@ -15,6 +15,11 @@ const FAB = 'fab';
15 15
 const TOPBAR_HEIGHT = 67;
16 16
 
17 17
 class OptionsScreen extends Component {
18
+  constructor(props) {
19
+    super(props);
20
+    Navigation.events().bindComponent(this);
21
+  }
22
+
18 23
   static get options() {
19 24
     return {
20 25
       statusBar: {
@@ -137,8 +142,8 @@ class OptionsScreen extends Component {
137 142
     );
138 143
   }
139 144
 
140
-  onNavigationButtonPressed(id) {
141
-    if (id === BUTTON_ONE) {
145
+  navigationButtonPressed({buttonId}) {
146
+    if (buttonId === BUTTON_ONE) {
142 147
       Navigation.mergeOptions(this.props.componentId, {
143 148
         topBar: {
144 149
           rightButtons: [{
@@ -155,7 +160,7 @@ class OptionsScreen extends Component {
155 160
           leftButtons: []
156 161
         }
157 162
       });
158
-    } else if (id === BUTTON_TWO) {
163
+    } else if (buttonId === BUTTON_TWO) {
159 164
       Navigation.mergeOptions(this.props.componentId, {
160 165
         topBar: {
161 166
           rightButtons: [{
@@ -173,7 +178,7 @@ class OptionsScreen extends Component {
173 178
           }]
174 179
         }
175 180
       });
176
-    } else if (id === BUTTON_LEFT) {
181
+    } else if (buttonId === BUTTON_LEFT) {
177 182
       Navigation.pop(this.props.componentId);
178 183
     }
179 184
   }

+ 19
- 22
playground/src/screens/PushedScreen.js View File

@@ -1,10 +1,7 @@
1 1
 const _ = require('lodash');
2
-
3 2
 const React = require('react');
4 3
 const { Component } = require('react');
5
-
6 4
 const { View, Text, Platform } = require('react-native');
7
-
8 5
 const { Navigation } = require('react-native-navigation');
9 6
 const Button = require('./Button');
10 7
 const testIDs = require('../testIDs');
@@ -53,26 +50,26 @@ class PushedScreen extends Component {
53 50
 
54 51
   componentDidMount() {
55 52
     this.listeners.push(
56
-      Navigation.events().registerNativeEventListener((name, params) => {
57
-        if (name === 'previewContext') {
58
-          const { previewComponentId } = params;
59
-          this.setState({ previewComponentId });
60
-        }
61
-      }),
62
-      Navigation.events().registerComponentDidAppearListener((componentId, componentName) => {
63
-        if (this.state.previewComponentId === componentId) {
64
-          this.setState({ disabled: true });
65
-        }
66
-      }),
67
-      Navigation.events().registerComponentDidDisappearListener((componentId, componentName) => {
68
-        if (this.state.previewComponentId === componentId) {
69
-          this.setState({ disabled: false });
53
+      this.subscription = Navigation.events().registerComponentDidAppearListener((event) => {
54
+        if (this.state.previewComponentId === event.componentId) {
55
+          this.setState({ disabled: event.type === 'ComponentDidAppear' });
70 56
         }
71 57
       })
72 58
     );
59
+    if (Platform.OS === 'ios') {
60
+      // this.listeners.push(
61
+      //   Navigation.events().registerNativeEventListener((name, params) => {
62
+      //     if (name === 'previewContext') {
63
+      //       const { previewComponentId } = params;
64
+      //       this.setState({ previewComponentId });
65
+      //     }
66
+      //   })
67
+      // );
68
+    }
73 69
   }
74 70
 
75 71
   componentWillUnmount() {
72
+    this.subscription.remove();
76 73
     this.listeners.forEach(listener => listener.remove && listener.remove());
77 74
   }
78 75
 
@@ -83,11 +80,11 @@ class PushedScreen extends Component {
83 80
         <Text testID={testIDs.PUSHED_SCREEN_HEADER} style={styles.h1}>{`Pushed Screen`}</Text>
84 81
         <Text style={styles.h2}>{`Stack Position: ${stackPosition}`}</Text>
85 82
         <Button title='Push' testID={testIDs.PUSH_BUTTON} onPress={this.onClickPush} />
86
-          {Platform.OS === 'ios' && (
87
-            <Navigation.Element elementId='PreviewElement'>
88
-              <Button testID={testIDs.SHOW_PREVIEW_BUTTON} onPress={this.onClickPush} onPressIn={this.onClickShowPreview} title='Push Preview' />
89
-            </Navigation.Element>
90
-          )}
83
+        {Platform.OS === 'ios' && (
84
+          <Navigation.Element elementId='PreviewElement'>
85
+            <Button testID={testIDs.SHOW_PREVIEW_BUTTON} onPress={this.onClickPush} onPressIn={this.onClickShowPreview} title='Push Preview' />
86
+          </Navigation.Element>
87
+        )}
91 88
         <Button title='Pop' testID={testIDs.POP_BUTTON} onPress={this.onClickPop} />
92 89
         <Button title='Pop Previous' testID={testIDs.POP_PREVIOUS_BUTTON} onPress={this.onClickPopPrevious} />
93 90
         <Button title='Pop To Root' testID={testIDs.POP_TO_ROOT} onPress={this.onClickPopToRoot} />

+ 3
- 2
playground/src/screens/SearchScreen.js View File

@@ -38,6 +38,7 @@ class SearchControllerScreen extends Component {
38 38
     this.state = {
39 39
       query: ''
40 40
     };
41
+    Navigation.events().bindComponent(this);
41 42
   }
42 43
 
43 44
   filteredData() {
@@ -79,8 +80,8 @@ class SearchControllerScreen extends Component {
79 80
     );
80 81
   }
81 82
 
82
-  onSearchBarUpdated(query, isFocused) {
83
-    this.setState({ query, isFocused });
83
+  searchBarUpdated({text, isFocused}) {
84
+    this.setState({ query: text, isFocused });
84 85
   }
85 86
 }
86 87
 

+ 15
- 13
playground/src/screens/StaticLifecycleOverlay.js View File

@@ -12,17 +12,19 @@ class StaticLifecycleOverlay extends Component {
12 12
       events: []
13 13
     };
14 14
     this.listeners = [];
15
-    this.listeners.push(Navigation.events().registerComponentDidAppearListener((componentId, componentName) => {
15
+    this.listeners.push(Navigation.events().registerComponentDidAppearListener((event) => {
16
+      event.event = 'componentDidAppear';
16 17
       this.setState({
17
-        events: [...this.state.events, { event: 'componentDidAppear', componentId, componentName }]
18
+        events: [...this.state.events, { ...event }]
18 19
       });
19 20
     }));
20
-    this.listeners.push(Navigation.events().registerComponentDidDisappearListener((componentId, componentName) => {
21
+    this.listeners.push(Navigation.events().registerComponentDidDisappearListener((event) => {
22
+      event.event = 'componentDidDisappear';
21 23
       this.setState({
22
-        events: [...this.state.events, { event: 'componentDidDisappear', componentId, componentName }]
24
+        events: [...this.state.events, { ...event }]
23 25
       });
24 26
     }));
25
-    this.listeners.push(Navigation.events().registerCommandCompletedListener((commandId, completionTime, params) => {
27
+    this.listeners.push(Navigation.events().registerCommandCompletedListener(({commandId}) => {
26 28
       this.setState({
27 29
         events: [...this.state.events, { event: 'commandCompleted', commandId }]
28 30
       });
@@ -62,14 +64,14 @@ class StaticLifecycleOverlay extends Component {
62 64
   }
63 65
 
64 66
   renderDismissButton = () => {
65
-  return (
66
-    <TouchableOpacity
67
-      style={styles.dismissBtn}
68
-      onPress={() => Navigation.dismissOverlay(this.props.componentId)}
69
-    >
70
-      <Text testID={testIDs.DISMISS_BUTTON} style={{ color: 'red', alignSelf: 'center' }}>X</Text>
71
-    </TouchableOpacity>
72
-  );
67
+    return (
68
+      <TouchableOpacity
69
+        style={styles.dismissBtn}
70
+        onPress={() => Navigation.dismissOverlay(this.props.componentId)}
71
+      >
72
+        <Text testID={testIDs.DISMISS_BUTTON} style={{ color: 'red', alignSelf: 'center' }}>X</Text>
73
+      </TouchableOpacity>
74
+    );
73 75
   }
74 76
 }
75 77
 module.exports = StaticLifecycleOverlay;

+ 3
- 0
playground/src/screens/TopBarBackground.js View File

@@ -4,11 +4,13 @@ const {
4 4
   StyleSheet,
5 5
   View
6 6
 } = require('react-native');
7
+const { Navigation } = require('react-native-navigation');
7 8
 
8 9
 class TopBarBackground extends Component {
9 10
 
10 11
   constructor(props) {
11 12
     super(props);
13
+    this.subscription = Navigation.events().bindComponent(this);
12 14
     this.state = {};
13 15
     this.dots = new Array(55).fill('').map((ignored, i) => <View key={'dot' + i} style={[styles.dot, {backgroundColor: this.props.color}]}/>);
14 16
   }
@@ -27,6 +29,7 @@ class TopBarBackground extends Component {
27 29
 
28 30
   componentWillUnmount() {
29 31
     console.log('RNN', `TBB.componentWillUnmount`);
32
+    this.subscription.remove();
30 33
   }
31 34
 
32 35
   render() {

+ 22
- 10
scripts/gen-docs/ClassParser.ts View File

@@ -1,4 +1,3 @@
1
-import * as Handlebars from 'handlebars';
2 1
 import * as Typedoc from 'typedoc';
3 2
 
4 3
 export interface PropertyContext {
@@ -16,6 +15,7 @@ export interface MethodContext {
16 15
   arguments: ArgumentContext[];
17 16
   returnType: string;
18 17
   source: string;
18
+  line: number;
19 19
   comment?: string;
20 20
 }
21 21
 
@@ -42,16 +42,28 @@ export class ClassParser {
42 42
 
43 43
   private parseMethods(reflection: Typedoc.DeclarationReflection): MethodContext[] {
44 44
     const methodReflections = reflection.getChildrenByKind(Typedoc.ReflectionKind.Method);
45
+    const methods = methodReflections.map((m) => this.parseMethod(m))
46
+      .filter((m) => !m.source.includes('/node_modules/'))
47
+      .sort((a, b) => a.line - b.line);
48
+    return methods;
49
+  }
45 50
 
46
-    methodReflections.sort((a, b) => a.sources[0].line - b.sources[0].line);
47
-
48
-    return methodReflections.map((methodReflection) => ({
49
-      name: methodReflection.name,
50
-      arguments: this.parseArguments(methodReflection.signatures[0].parameters || []),
51
-      returnType: methodReflection.signatures[0].type.toString(),
52
-      source: `${this.sourceLinkPrefix}/${methodReflection.sources[0].fileName}#L${methodReflection.sources[0].line}`,
53
-      comment: methodReflection.signatures[0].comment ? methodReflection.signatures[0].comment.shortText : ''
54
-    }));
51
+  private parseMethod(methodReflection: Typedoc.DeclarationReflection) {
52
+    const name = methodReflection.name;
53
+    const line = methodReflection.sources[0].line;
54
+    const fileName = methodReflection.sources[0].fileName;
55
+    const source = `${this.sourceLinkPrefix}/${fileName}#L${line}`;
56
+    const comment = methodReflection.signatures[0].comment ? methodReflection.signatures[0].comment.shortText : '';
57
+    const args = this.parseArguments(methodReflection.signatures[0].parameters || []);
58
+    const returnType = methodReflection.signatures[0].type.toString();
59
+    return {
60
+      name,
61
+      arguments: args,
62
+      returnType,
63
+      source,
64
+      line,
65
+      comment
66
+    };
55 67
   }
56 68
 
57 69
   private parseArguments(parameters: Typedoc.ParameterReflection[]): ArgumentContext[] {