@applicaster/zapp-react-native-ui-components 16.0.0-alpha.1054344748 → 16.0.0-alpha.1108851646

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -66,7 +66,7 @@ export class AnimationManager {
66
66
  *
67
67
  * @param {object} to contains style to animate and scenes index
68
68
  * @param {string} action push|back
69
- * @param {number} [stepsBack] number of steps back, used by the back action only
69
+ * @param {number} stepsBack number of steps back
70
70
  */
71
71
  stateForAction({ to, scenes }, action, stepsBack) {
72
72
  let rootIndex = to.index;
@@ -17,14 +17,13 @@ const fullWidthDimensions = {
17
17
  type Props = {
18
18
  children: any;
19
19
  style: any;
20
- pointerEvents?: ViewProps["pointerEvents"];
21
- overlayStyle?: ViewStyle;
22
- animating?: boolean;
20
+ pointerEvents: ViewProps["pointerEvents"];
21
+ overlayStyle: ViewStyle;
22
+ animating: boolean;
23
23
  contentStyle: ViewStyle;
24
24
  pathname: string;
25
25
  isActive: boolean;
26
26
  screenData: NavigationScreenData;
27
- screenUniqueId?: string;
28
27
  };
29
28
 
30
29
  export function CurrentScreenContextProvider({
@@ -1,6 +1,6 @@
1
1
  import * as React from "react";
2
2
  import { View } from "react-native";
3
- import { append, compose, identity, tail, update } from "ramda";
3
+ import { append, compose, identity, tail, take, update } from "ramda";
4
4
 
5
5
  import { AnimationManager } from "./AnimationManager";
6
6
  import { Scene } from "./Scene";
@@ -18,23 +18,11 @@ export const NAV_ACTION_REPLACE = "REPLACE";
18
18
 
19
19
  export const NAV_ACTION_BACK = "POP";
20
20
 
21
- const RIVER_SEGMENT_REGEX = /river\//g;
22
-
23
- // Number of "river/" segments in the route, i.e. its depth in the navigation stack
24
- function countRiverSegments(route?: string): number {
25
- return (route?.match(RIVER_SEGMENT_REGEX) || []).length;
26
- }
27
-
28
- // The screen element rendered for the current route
29
- type SceneChild = React.ReactElement<{
30
- pathname: string;
31
- screenData: NavigationScreenData;
32
- }>;
33
-
34
- type TransitionerScene = {
35
- children: SceneChild;
36
- key: string;
37
- screenUniqueId: string;
21
+ type SceneProps = {
22
+ children: React.ReactNode;
23
+ overlayStyle: any;
24
+ style: any;
25
+ animating: boolean;
38
26
  };
39
27
 
40
28
  /**
@@ -43,7 +31,7 @@ type TransitionerScene = {
43
31
  * @param {Object} children
44
32
  * @returns { children: Object, key: string }
45
33
  */
46
- function prepareScene(children: SceneChild): TransitionerScene {
34
+ function prepareScene(children) {
47
35
  return {
48
36
  children,
49
37
  key: children.props.pathname,
@@ -57,10 +45,7 @@ function prepareScene(children: SceneChild): TransitionerScene {
57
45
  * @param {any} props Component's props
58
46
  * @param {any} state Component's state
59
47
  */
60
- function scenesForBackTransition(
61
- props: Pick<Props, "children">,
62
- { scenes }: Pick<State, "scenes">
63
- ): TransitionerScene[] {
48
+ function scenesForBackTransition(props: SceneProps, { scenes }) {
64
49
  return scenes.length > 1
65
50
  ? identity(scenes)
66
51
  : [prepareScene(props.children), scenes[0]];
@@ -68,8 +53,7 @@ function scenesForBackTransition(
68
53
 
69
54
  type Props = {
70
55
  transitionConfig: any;
71
- isRTL?: boolean;
72
- children: SceneChild;
56
+ children: React.ReactNode;
73
57
  navigator: {
74
58
  previousAction: string;
75
59
  currentRoute: string;
@@ -94,12 +78,13 @@ type Props = {
94
78
  };
95
79
 
96
80
  type State = {
81
+ animatedValue: any;
97
82
  animating: boolean;
98
83
  duration: number;
99
84
  easing: any;
100
85
  from: any;
101
86
  to: any;
102
- scenes: TransitionerScene[];
87
+ scenes: any;
103
88
  };
104
89
 
105
90
  const styles = {
@@ -107,18 +92,11 @@ const styles = {
107
92
  } as const;
108
93
 
109
94
  export class TransitionerComponent extends React.PureComponent<Props, State> {
110
- private animationManager: AnimationManager;
111
-
112
- private transitionId: number;
113
-
114
95
  constructor(props: Props) {
115
96
  super(props);
116
97
 
117
98
  this.animationManager = new AnimationManager(props);
118
99
 
119
- // Bumped by every animated transition; end callbacks of older ones are ignored
120
- this.transitionId = 0;
121
-
122
100
  this.state = {
123
101
  scenes: [prepareScene(props.children)], // starting with index: 0
124
102
  ...this.animationManager.initialState(),
@@ -135,36 +113,28 @@ export class TransitionerComponent extends React.PureComponent<Props, State> {
135
113
  * and trigger push animation with callbacks.
136
114
  */
137
115
  pushTransitionStart() {
138
- const transitionId = ++this.transitionId;
139
-
140
116
  this.setState(
141
117
  {
142
- // Append right after the visible scene: stateForAction(PUSH) animates to.index + 1
143
- scenes: [
144
- ...this.state.scenes.slice(0, this.state.to.index + 1),
145
- prepareScene(this.props.children),
146
- ],
118
+ scenes: append(prepareScene(this.props.children), this.state.scenes),
147
119
  ...this.animationManager.stateForAction(this.state, NAV_ACTION_PUSH),
148
120
  },
149
121
  () => {
150
- this.animationManager.animate(this.state, () =>
151
- this.pushTransitionEnd(transitionId)
152
- );
122
+ this.animationManager.animate(this.state, this.pushTransitionEnd);
153
123
  }
154
124
  );
155
125
  }
156
126
 
157
127
  /**
158
128
  * Trigger back animation.
159
- * The scenes after the target one are removed in the "backTransitionEnd" callback,
129
+ * The last scene in the array is removed in the "backTransitionEnd" callback,
160
130
  * only after the animation is complete.
161
131
  */
162
132
  backTransitionStart(previousRoute: string) {
163
- const transitionId = ++this.transitionId;
164
133
  const currentRoute = this.props.navigator?.currentRoute;
165
134
 
166
135
  const diffLengthRoutes =
167
- countRiverSegments(previousRoute) - countRiverSegments(currentRoute);
136
+ (previousRoute?.match(/river\//g) || []).length -
137
+ (currentRoute?.match(/river\//g) || []).length;
168
138
 
169
139
  const stepsBack =
170
140
  diffLengthRoutes && diffLengthRoutes > 0 ? diffLengthRoutes : 1;
@@ -180,32 +150,30 @@ export class TransitionerComponent extends React.PureComponent<Props, State> {
180
150
  },
181
151
  () => {
182
152
  this.animationManager.animate(this.state, () =>
183
- this.backTransitionEnd(transitionId)
153
+ this.backTransitionEnd(stepsBack)
184
154
  );
185
155
  }
186
156
  );
187
157
  }
188
158
 
189
- shouldSkipAnimationForRoute(route: string) {
159
+ shouldSkipAnimationForRoute(route) {
190
160
  // We are skipping animations for some routes, likes hooks & the player mainly
191
161
 
192
162
  return route.includes("/playable") || route.includes("/hook");
193
163
  }
194
164
 
195
- shouldResetStateForRoute(route: string) {
165
+ shouldResetStateForRoute(route) {
196
166
  // We are resetting the transitioner state for root routes
197
167
 
198
- return countRiverSegments(route) === 1;
168
+ return (route?.match(/river\//g) || []).length === 1;
199
169
  }
200
170
 
201
- transitionWithoutAnimation(resetState?: boolean) {
202
- // Scenes after the visible one are only leaving (back animation in flight),
203
- // so the new scene replaces the one being backed to
204
- const scenes = this.state.scenes.slice(0, this.state.to.index + 1);
171
+ transitionWithoutAnimation(resetState) {
172
+ const { scenes } = this.state;
205
173
  const newScene = prepareScene(this.props.children);
206
174
  const isLastSceneHook = scenes[scenes.length - 1].key.startsWith("/hook");
207
175
 
208
- let newState: Pick<State, "scenes"> | Pick<State, "scenes" | "to">;
176
+ let newState;
209
177
 
210
178
  if (resetState) {
211
179
  newState = {
@@ -295,27 +263,20 @@ export class TransitionerComponent extends React.PureComponent<Props, State> {
295
263
  }
296
264
  }
297
265
 
298
- // Sets state.animating to false,
299
- // unless a newer transition has started since this one
300
- pushTransitionEnd(transitionId: number) {
301
- if (transitionId !== this.transitionId) return;
302
-
266
+ // Sets state.animating to false
267
+ pushTransitionEnd() {
303
268
  this.setState({
304
269
  animating: false,
305
270
  });
306
271
  }
307
272
 
308
273
  // Sets state.animating to false
309
- // and drops every scene after the target one. Reads the latest state,
310
- // so it's correct however many backs were merged into this animation.
311
- // Ignored if a newer transition has started since this one
312
- backTransitionEnd(transitionId: number) {
313
- if (transitionId !== this.transitionId) return;
314
-
315
- this.setState(({ scenes, to }) => ({
274
+ // and remove last scene from scenes stack
275
+ backTransitionEnd(stepsBack) {
276
+ this.setState({
316
277
  animating: false,
317
- scenes: scenes.slice(0, to.index + 1),
318
- }));
278
+ scenes: take(this.state.scenes.length - stepsBack, this.state.scenes),
279
+ });
319
280
  }
320
281
 
321
282
  // On initial state, both "to" and "from" point to a single scene
@@ -1,12 +1,10 @@
1
1
  import React from "react";
2
- import { Animated, Text, View } from "react-native";
3
- import { v4 as uuidV4 } from "uuid";
2
+ import { Text, View } from "react-native";
4
3
 
5
4
  import { AnimationManager } from "../AnimationManager";
6
5
  import {
7
6
  NAV_ACTION_BACK,
8
7
  NAV_ACTION_PUSH,
9
- NAV_ACTION_REPLACE,
10
8
  TransitionerComponent,
11
9
  } from "../Transitioner";
12
10
  import { act, render } from "@testing-library/react-native";
@@ -68,26 +66,6 @@ const sceneFor = (route) => (
68
66
  </View>
69
67
  );
70
68
 
71
- const root = "/river/root";
72
- const child = `${root}/river/child`;
73
- const grandchild = `${child}/river/grandchild`;
74
-
75
- const propsFor = (route, previousAction, children = sceneFor(route)) => ({
76
- navigator: {
77
- previousAction,
78
- currentRoute: route,
79
- screenData: {},
80
- },
81
- transitionConfig: sceneTransitionConfig,
82
- layoutData: {
83
- isTablet: false,
84
- isTabletPortrait: false,
85
- },
86
- children,
87
- });
88
-
89
- const sceneKeys = (ref) => ref.current.state.scenes.map((scene) => scene.key);
90
-
91
69
  describe("<TransitionerComponent />", () => {
92
70
  it("renders correctly", () => {
93
71
  const wrapper = render(
@@ -105,12 +83,26 @@ describe("<TransitionerComponent />", () => {
105
83
  .mockImplementation((_, done) => done());
106
84
 
107
85
  const ref = React.createRef();
86
+ const root = "/river/root";
87
+ const child = `${root}/river/child`;
88
+ const grandchild = `${child}/river/grandchild`;
89
+
90
+ const propsFor = (route, previousAction) => ({
91
+ navigator: {
92
+ previousAction,
93
+ currentRoute: route,
94
+ screenData: {},
95
+ },
96
+ transitionConfig: sceneTransitionConfig,
97
+ layoutData: {
98
+ isTablet: false,
99
+ isTabletPortrait: false,
100
+ },
101
+ children: sceneFor(route),
102
+ });
108
103
 
109
104
  const { rerender } = render(
110
- <TransitionerComponent
111
- ref={ref}
112
- {...propsFor(root, NAV_ACTION_REPLACE)}
113
- />
105
+ <TransitionerComponent ref={ref} {...propsFor(root, "REPLACE")} />
114
106
  );
115
107
 
116
108
  act(() => {
@@ -137,286 +129,6 @@ describe("<TransitionerComponent />", () => {
137
129
  );
138
130
  });
139
131
 
140
- expect(sceneKeys(ref)).toEqual([root]);
141
- });
142
-
143
- describe("with interruptible animations", () => {
144
- // End callback of the animation that is currently "running"
145
- let runningAnimationEnd = null;
146
-
147
- const finishAnimation = () => {
148
- act(() => {
149
- const done = runningAnimationEnd;
150
- runningAnimationEnd = null;
151
- done?.();
152
- });
153
- };
154
-
155
- const mountTransitioner = ({
156
- initialRoute = root,
157
- renderScene = sceneFor,
158
- } = {}) => {
159
- const ref = React.createRef();
160
-
161
- const transitioner = (route, action) => (
162
- <TransitionerComponent
163
- ref={ref}
164
- {...propsFor(route, action, renderScene(route))}
165
- />
166
- );
167
-
168
- const { rerender } = render(
169
- transitioner(initialRoute, NAV_ACTION_REPLACE)
170
- );
171
-
172
- const navigate = (route, action) => {
173
- act(() => {
174
- rerender(transitioner(route, action));
175
- });
176
- };
177
-
178
- return { ref, navigate };
179
- };
180
-
181
- beforeEach(() => {
182
- let sceneCount = 0;
183
- uuidV4.mockImplementation(() => `scene-${++sceneCount}`);
184
-
185
- jest
186
- .spyOn(AnimationManager.prototype, "animate")
187
- .mockImplementation((_, done) => {
188
- runningAnimationEnd = done;
189
- });
190
-
191
- const setValue = Animated.Value.prototype.setValue;
192
-
193
- // RN 0.77: setValue() stops the running animation, which calls
194
- // its end callback synchronously with { finished: false }
195
- jest
196
- .spyOn(Animated.Value.prototype, "setValue")
197
- .mockImplementation(function (value) {
198
- const interrupted = runningAnimationEnd;
199
- runningAnimationEnd = null;
200
- interrupted?.();
201
-
202
- return setValue.call(this, value);
203
- });
204
- });
205
-
206
- afterEach(() => {
207
- runningAnimationEnd = null;
208
- jest.restoreAllMocks();
209
- uuidV4.mockReset();
210
- });
211
-
212
- it("drops interrupted scenes when going back twice during a back animation", () => {
213
- const { ref, navigate } = mountTransitioner();
214
-
215
- navigate(child, NAV_ACTION_PUSH);
216
- finishAnimation();
217
- navigate(grandchild, NAV_ACTION_PUSH);
218
- finishAnimation();
219
-
220
- navigate(child, NAV_ACTION_BACK);
221
- navigate(root, NAV_ACTION_BACK);
222
-
223
- expect(ref.current.state.animating).toBe(true);
224
-
225
- finishAnimation();
226
-
227
- expect(sceneKeys(ref)).toEqual([root]);
228
- expect(ref.current.state.animating).toBe(false);
229
- });
230
-
231
- it("remounts the screen when revisiting it after a quick double back", () => {
232
- const mounts = {};
233
-
234
- const MountProbe = ({ pathname }) => {
235
- React.useEffect(() => {
236
- mounts[pathname] = (mounts[pathname] || 0) + 1;
237
- }, [pathname]);
238
-
239
- return <Text>{pathname}</Text>;
240
- };
241
-
242
- const { ref, navigate } = mountTransitioner({
243
- renderScene: (route) => <MountProbe pathname={route} />,
244
- });
245
-
246
- navigate(child, NAV_ACTION_PUSH);
247
- finishAnimation();
248
-
249
- const originalChildId = ref.current.state.scenes[1].screenUniqueId;
250
-
251
- navigate(grandchild, NAV_ACTION_PUSH);
252
- finishAnimation();
253
- navigate(child, NAV_ACTION_BACK);
254
- navigate(root, NAV_ACTION_BACK);
255
- finishAnimation();
256
-
257
- navigate(child, NAV_ACTION_PUSH);
258
- finishAnimation();
259
-
260
- const { scenes, to } = ref.current.state;
261
-
262
- expect(scenes).toHaveLength(2);
263
- expect(to.index).toBe(1);
264
- expect(scenes[1].screenUniqueId).not.toBe(originalChildId);
265
- expect(mounts[child]).toBe(2);
266
- });
267
-
268
- it("keeps a single scene when going back from the first scene", () => {
269
- const { ref, navigate } = mountTransitioner({ initialRoute: child });
270
-
271
- navigate(root, NAV_ACTION_BACK);
272
- finishAnimation();
273
-
274
- expect(sceneKeys(ref)).toEqual([root]);
275
- expect(ref.current.state.to.index).toBe(0);
276
- expect(ref.current.state.animating).toBe(false);
277
- });
278
-
279
- it("pushes a fresh scene after a completed single back", () => {
280
- const { ref, navigate } = mountTransitioner();
281
-
282
- navigate(child, NAV_ACTION_PUSH);
283
- finishAnimation();
284
-
285
- const originalChildId = ref.current.state.scenes[1].screenUniqueId;
286
-
287
- navigate(root, NAV_ACTION_BACK);
288
- finishAnimation();
289
- navigate(child, NAV_ACTION_PUSH);
290
- finishAnimation();
291
-
292
- const { scenes, to } = ref.current.state;
293
-
294
- expect(sceneKeys(ref)).toEqual([root, child]);
295
- expect(to.index).toBe(1);
296
- expect(scenes[1].screenUniqueId).not.toBe(originalChildId);
297
- });
298
-
299
- it("ignores the end of a back animation that a newer back superseded", () => {
300
- const { ref, navigate } = mountTransitioner();
301
-
302
- navigate(child, NAV_ACTION_PUSH);
303
- finishAnimation();
304
- navigate(grandchild, NAV_ACTION_PUSH);
305
- finishAnimation();
306
-
307
- navigate(child, NAV_ACTION_BACK);
308
-
309
- const supersededEnd = runningAnimationEnd;
310
-
311
- // Deliver the interrupted animation's end late, after the newer one started
312
- Animated.Value.prototype.setValue.mockImplementation(() => {});
313
-
314
- navigate(root, NAV_ACTION_BACK);
315
-
316
- act(() => {
317
- supersededEnd();
318
- });
319
-
320
- expect(ref.current.state.animating).toBe(true);
321
- expect(sceneKeys(ref)).toEqual([root, child, grandchild]);
322
-
323
- finishAnimation();
324
-
325
- expect(sceneKeys(ref)).toEqual([root]);
326
- expect(ref.current.state.animating).toBe(false);
327
- });
328
-
329
- it("drops the pushed scene when going back before its push animation ends", () => {
330
- const { ref, navigate } = mountTransitioner();
331
-
332
- navigate(child, NAV_ACTION_PUSH);
333
- navigate(root, NAV_ACTION_BACK);
334
-
335
- expect(ref.current.state.animating).toBe(true);
336
-
337
- finishAnimation();
338
-
339
- expect(sceneKeys(ref)).toEqual([root]);
340
- expect(ref.current.state.animating).toBe(false);
341
- });
342
-
343
- it("pushes right after the visible scene even when stale scenes are left behind", () => {
344
- const { ref, navigate } = mountTransitioner();
345
-
346
- navigate(child, NAV_ACTION_PUSH);
347
- finishAnimation();
348
-
349
- // Leftover past to.index, as some other path could leave behind
350
- act(() => {
351
- ref.current.setState(({ scenes }) => ({
352
- scenes: [
353
- ...scenes,
354
- {
355
- ...scenes[1],
356
- key: `${root}/river/stale`,
357
- screenUniqueId: "stale",
358
- },
359
- ],
360
- }));
361
- });
362
-
363
- navigate(grandchild, NAV_ACTION_PUSH);
364
- finishAnimation();
365
-
366
- const { scenes, to } = ref.current.state;
367
-
368
- expect(sceneKeys(ref)).toEqual([root, child, grandchild]);
369
- expect(scenes[to.index].key).toBe(grandchild);
370
- });
371
-
372
- it("shows the replaced screen when going home during a back animation", () => {
373
- const home = "/river/home";
374
- const { ref, navigate } = mountTransitioner();
375
-
376
- navigate(child, NAV_ACTION_PUSH);
377
- finishAnimation();
378
-
379
- navigate(root, NAV_ACTION_BACK);
380
- navigate(home, NAV_ACTION_REPLACE);
381
-
382
- const { scenes, to } = ref.current.state;
383
-
384
- expect(scenes[to.index].key).toBe(home);
385
-
386
- finishAnimation();
387
-
388
- expect(sceneKeys(ref)).toEqual([home]);
389
- expect(ref.current.state.animating).toBe(false);
390
- });
391
-
392
- it("shows the pushed screen when pushing during a back animation", () => {
393
- const sibling = `${root}/river/sibling`;
394
- const { ref, navigate } = mountTransitioner();
395
-
396
- navigate(child, NAV_ACTION_PUSH);
397
- finishAnimation();
398
-
399
- navigate(root, NAV_ACTION_BACK);
400
- navigate(sibling, NAV_ACTION_PUSH);
401
-
402
- const { scenes, to } = ref.current.state;
403
-
404
- expect(scenes[to.index].key).toBe(sibling);
405
-
406
- finishAnimation();
407
-
408
- expect(sceneKeys(ref)).not.toContain(child);
409
- expect(ref.current.state.animating).toBe(false);
410
- });
411
-
412
- it("keeps both scenes when pushing during a push animation", () => {
413
- const { ref, navigate } = mountTransitioner();
414
-
415
- navigate(child, NAV_ACTION_PUSH);
416
- navigate(grandchild, NAV_ACTION_PUSH);
417
-
418
- expect(sceneKeys(ref)).toEqual([child, grandchild]);
419
- expect(ref.current.state.to.index).toBe(1);
420
- });
132
+ expect(ref.current.state.scenes.map((scene) => scene.key)).toEqual([root]);
421
133
  });
422
134
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-ui-components",
3
- "version": "16.0.0-alpha.1054344748",
3
+ "version": "16.0.0-alpha.1108851646",
4
4
  "description": "Applicaster Zapp React Native ui components for the Quick Brick App",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -28,10 +28,10 @@
28
28
  },
29
29
  "homepage": "https://github.com/applicaster/quickbrick#readme",
30
30
  "dependencies": {
31
- "@applicaster/applicaster-types": "16.0.0-alpha.1054344748",
32
- "@applicaster/zapp-react-native-bridge": "16.0.0-alpha.1054344748",
33
- "@applicaster/zapp-react-native-redux": "16.0.0-alpha.1054344748",
34
- "@applicaster/zapp-react-native-utils": "16.0.0-alpha.1054344748",
31
+ "@applicaster/applicaster-types": "16.0.0-alpha.1108851646",
32
+ "@applicaster/zapp-react-native-bridge": "16.0.0-alpha.1108851646",
33
+ "@applicaster/zapp-react-native-redux": "16.0.0-alpha.1108851646",
34
+ "@applicaster/zapp-react-native-utils": "16.0.0-alpha.1108851646",
35
35
  "fast-json-stable-stringify": "^2.1.0",
36
36
  "promise": "^8.3.0",
37
37
  "react-native-sortables": "1.7.1",