@27appstudio/react-native-bottom-toolbar 0.1.2 → 0.2.0

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.
package/README.md CHANGED
@@ -70,7 +70,7 @@ function ExpenseScreen({
70
70
  }
71
71
  ```
72
72
 
73
- The hook shows the toolbar once the push has landed (or right away on the stack's first screen), hides it when the screen loses focus, and brings it back when a modal over the screen closes.
73
+ The hook shows the toolbar once the push has landed (or right away on the stack's first screen), hides it when the screen loses focus, and brings it back when a modal over the screen closes. New `items` or `options` update a toolbar already on screen, and an empty array hides it, so memoize them (`useMemo`) rather than building a new array on every render.
74
74
 
75
75
  ### Without React Navigation
76
76
 
@@ -124,7 +124,7 @@ Called for every button tap, in addition to that button's own `onPress`.
124
124
 
125
125
  ### `useBottomToolbar(navigation, items, options?)`
126
126
 
127
- Imported from `/react-navigation`. `items` and `options` are read each time the toolbar is shown, so passing a new array doesn't resubscribe. To change a toolbar that's already on screen, call `show` again.
127
+ Imported from `/react-navigation`. New `items` or `options` update the toolbar while the screen is on top (an empty array hides it); they never resubscribe the navigation listeners. Memoize them, or every render sends them to the native side again.
128
128
 
129
129
  ## Platform notes
130
130
 
@@ -10,9 +10,10 @@ import { hide, show } from "../index.js";
10
10
  *
11
11
  * The toolbar belongs to the whole stack: it is shown once the push has landed
12
12
  * (`transitionEnd`), or at once on the stack's first screen; again on
13
- * returning from a modal (`focus`); and hidden on `blur`. `items` and
14
- * `options` are read at show time, so a new array (a language change) needs
15
- * no resubscribe; call `show` to update a toolbar already on screen.
13
+ * returning from a modal (`focus`); and hidden on `blur`. While the screen is
14
+ * on top, new `items` or `options` update the toolbar (an empty array hides
15
+ * it), so memoize them (`useMemo`): a new array on every render would send it
16
+ * to the native side every time.
16
17
  */
17
18
  export function useBottomToolbar(navigation, items, options) {
18
19
  const latest = useRef({
@@ -23,24 +24,31 @@ export function useBottomToolbar(navigation, items, options) {
23
24
  items,
24
25
  options
25
26
  };
27
+ // Whether the push has landed, so `show` reaches this screen and not the one
28
+ // below; and what was last sent, so an update only goes out when it changed.
29
+ const arrived = useRef(false);
30
+ const shown = useRef(undefined);
26
31
  useEffect(() => {
27
32
  // Back from a pop, both focus and transitionEnd show: the latter comes after
28
33
  // the popped screen's unmount, whose `hide` would otherwise win.
29
- const showLatest = () => show(latest.current.items, latest.current.options);
34
+ const showLatest = () => {
35
+ shown.current = latest.current;
36
+ show(latest.current.items, latest.current.options);
37
+ };
30
38
  // A pushed screen mounts before its push starts, so showing now would put
31
39
  // the items on the screen below; the first screen has no push to wait for.
32
- let arrived = navigation.isFocused() && navigation.getState().index === 0;
33
- if (arrived) {
40
+ arrived.current = navigation.isFocused() && navigation.getState().index === 0;
41
+ if (arrived.current) {
34
42
  showLatest();
35
43
  }
36
44
  const offTransitionEnd = navigation.addListener('transitionEnd', event => {
37
45
  if (!event.data.closing) {
38
- arrived = true;
46
+ arrived.current = true;
39
47
  showLatest();
40
48
  }
41
49
  });
42
50
  const offFocus = navigation.addListener('focus', () => {
43
- if (arrived) {
51
+ if (arrived.current) {
44
52
  showLatest();
45
53
  }
46
54
  });
@@ -52,5 +60,18 @@ export function useBottomToolbar(navigation, items, options) {
52
60
  hide();
53
61
  };
54
62
  }, [navigation]);
63
+
64
+ // New items or options on a screen whose toolbar is up: update it in place.
65
+ useEffect(() => {
66
+ const last = shown.current;
67
+ if (!arrived.current || !navigation.isFocused() || last && last.items === items && last.options === options) {
68
+ return;
69
+ }
70
+ shown.current = {
71
+ items,
72
+ options
73
+ };
74
+ show(items, options);
75
+ }, [items, navigation, options]);
55
76
  }
56
77
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["useEffect","useRef","hide","show","useBottomToolbar","navigation","items","options","latest","current","showLatest","arrived","isFocused","getState","index","offTransitionEnd","addListener","event","data","closing","offFocus","offBlur"],"sourceRoot":"../../../src","sources":["react-navigation/index.ts"],"mappings":";;AAIA,SAASA,SAAS,EAAEC,MAAM,QAAQ,OAAO;AAEzC,SAASC,IAAI,EAAEC,IAAI,QAAQ,aAAU;;AAErC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,gBAAgBA,CAC9BC,UAAoD,EACpDC,KAAoB,EACpBC,OAAwB,EACxB;EACA,MAAMC,MAAM,GAAGP,MAAM,CAAC;IAAEK,KAAK;IAAEC;EAAQ,CAAC,CAAC;EACzCC,MAAM,CAACC,OAAO,GAAG;IAAEH,KAAK;IAAEC;EAAQ,CAAC;EAEnCP,SAAS,CAAC,MAAM;IACd;IACA;IACA,MAAMU,UAAU,GAAGA,CAAA,KAAMP,IAAI,CAACK,MAAM,CAACC,OAAO,CAACH,KAAK,EAAEE,MAAM,CAACC,OAAO,CAACF,OAAO,CAAC;IAC3E;IACA;IACA,IAAII,OAAO,GAAGN,UAAU,CAACO,SAAS,CAAC,CAAC,IAAIP,UAAU,CAACQ,QAAQ,CAAC,CAAC,CAACC,KAAK,KAAK,CAAC;IACzE,IAAIH,OAAO,EAAE;MACXD,UAAU,CAAC,CAAC;IACd;IAEA,MAAMK,gBAAgB,GAAGV,UAAU,CAACW,WAAW,CAC7C,eAAe,EACdC,KAAK,IAAK;MACT,IAAI,CAACA,KAAK,CAACC,IAAI,CAACC,OAAO,EAAE;QACvBR,OAAO,GAAG,IAAI;QACdD,UAAU,CAAC,CAAC;MACd;IACF,CACF,CAAC;IACD,MAAMU,QAAQ,GAAGf,UAAU,CAACW,WAAW,CAAC,OAAO,EAAE,MAAM;MACrD,IAAIL,OAAO,EAAE;QACXD,UAAU,CAAC,CAAC;MACd;IACF,CAAC,CAAC;IACF,MAAMW,OAAO,GAAGhB,UAAU,CAACW,WAAW,CAAC,MAAM,EAAEd,IAAI,CAAC;IAEpD,OAAO,MAAM;MACXa,gBAAgB,CAAC,CAAC;MAClBK,QAAQ,CAAC,CAAC;MACVC,OAAO,CAAC,CAAC;MACTnB,IAAI,CAAC,CAAC;IACR,CAAC;EACH,CAAC,EAAE,CAACG,UAAU,CAAC,CAAC;AAClB","ignoreList":[]}
1
+ {"version":3,"names":["useEffect","useRef","hide","show","useBottomToolbar","navigation","items","options","latest","current","arrived","shown","undefined","showLatest","isFocused","getState","index","offTransitionEnd","addListener","event","data","closing","offFocus","offBlur","last"],"sourceRoot":"../../../src","sources":["react-navigation/index.ts"],"mappings":";;AAIA,SAASA,SAAS,EAAEC,MAAM,QAAQ,OAAO;AAEzC,SAASC,IAAI,EAAEC,IAAI,QAAQ,aAAU;;AAErC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,gBAAgBA,CAC9BC,UAAoD,EACpDC,KAAoB,EACpBC,OAAwB,EACxB;EACA,MAAMC,MAAM,GAAGP,MAAM,CAAC;IAAEK,KAAK;IAAEC;EAAQ,CAAC,CAAC;EACzCC,MAAM,CAACC,OAAO,GAAG;IAAEH,KAAK;IAAEC;EAAQ,CAAC;EACnC;EACA;EACA,MAAMG,OAAO,GAAGT,MAAM,CAAC,KAAK,CAAC;EAC7B,MAAMU,KAAK,GAAGV,MAAM,CAElBW,SAAS,CAAC;EAEZZ,SAAS,CAAC,MAAM;IACd;IACA;IACA,MAAMa,UAAU,GAAGA,CAAA,KAAM;MACvBF,KAAK,CAACF,OAAO,GAAGD,MAAM,CAACC,OAAO;MAC9BN,IAAI,CAACK,MAAM,CAACC,OAAO,CAACH,KAAK,EAAEE,MAAM,CAACC,OAAO,CAACF,OAAO,CAAC;IACpD,CAAC;IACD;IACA;IACAG,OAAO,CAACD,OAAO,GACbJ,UAAU,CAACS,SAAS,CAAC,CAAC,IAAIT,UAAU,CAACU,QAAQ,CAAC,CAAC,CAACC,KAAK,KAAK,CAAC;IAC7D,IAAIN,OAAO,CAACD,OAAO,EAAE;MACnBI,UAAU,CAAC,CAAC;IACd;IAEA,MAAMI,gBAAgB,GAAGZ,UAAU,CAACa,WAAW,CAC7C,eAAe,EACdC,KAAK,IAAK;MACT,IAAI,CAACA,KAAK,CAACC,IAAI,CAACC,OAAO,EAAE;QACvBX,OAAO,CAACD,OAAO,GAAG,IAAI;QACtBI,UAAU,CAAC,CAAC;MACd;IACF,CACF,CAAC;IACD,MAAMS,QAAQ,GAAGjB,UAAU,CAACa,WAAW,CAAC,OAAO,EAAE,MAAM;MACrD,IAAIR,OAAO,CAACD,OAAO,EAAE;QACnBI,UAAU,CAAC,CAAC;MACd;IACF,CAAC,CAAC;IACF,MAAMU,OAAO,GAAGlB,UAAU,CAACa,WAAW,CAAC,MAAM,EAAEhB,IAAI,CAAC;IAEpD,OAAO,MAAM;MACXe,gBAAgB,CAAC,CAAC;MAClBK,QAAQ,CAAC,CAAC;MACVC,OAAO,CAAC,CAAC;MACTrB,IAAI,CAAC,CAAC;IACR,CAAC;EACH,CAAC,EAAE,CAACG,UAAU,CAAC,CAAC;;EAEhB;EACAL,SAAS,CAAC,MAAM;IACd,MAAMwB,IAAI,GAAGb,KAAK,CAACF,OAAO;IAC1B,IACE,CAACC,OAAO,CAACD,OAAO,IAChB,CAACJ,UAAU,CAACS,SAAS,CAAC,CAAC,IACtBU,IAAI,IAAIA,IAAI,CAAClB,KAAK,KAAKA,KAAK,IAAIkB,IAAI,CAACjB,OAAO,KAAKA,OAAQ,EAC1D;MACA;IACF;IACAI,KAAK,CAACF,OAAO,GAAG;MAAEH,KAAK;MAAEC;IAAQ,CAAC;IAClCJ,IAAI,CAACG,KAAK,EAAEC,OAAO,CAAC;EACtB,CAAC,EAAE,CAACD,KAAK,EAAED,UAAU,EAAEE,OAAO,CAAC,CAAC;AAClC","ignoreList":[]}
@@ -8,9 +8,10 @@ import type { ToolbarItem, ToolbarOptions } from '../types.js';
8
8
  *
9
9
  * The toolbar belongs to the whole stack: it is shown once the push has landed
10
10
  * (`transitionEnd`), or at once on the stack's first screen; again on
11
- * returning from a modal (`focus`); and hidden on `blur`. `items` and
12
- * `options` are read at show time, so a new array (a language change) needs
13
- * no resubscribe; call `show` to update a toolbar already on screen.
11
+ * returning from a modal (`focus`); and hidden on `blur`. While the screen is
12
+ * on top, new `items` or `options` update the toolbar (an empty array hides
13
+ * it), so memoize them (`useMemo`): a new array on every render would send it
14
+ * to the native side every time.
14
15
  */
15
16
  export declare function useBottomToolbar(navigation: NativeStackNavigationProp<ParamListBase>, items: ToolbarItem[], options?: ToolbarOptions): void;
16
17
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/react-navigation/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAChF,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAU,CAAC;AAM5D;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,yBAAyB,CAAC,aAAa,CAAC,EACpD,KAAK,EAAE,WAAW,EAAE,EACpB,OAAO,CAAC,EAAE,cAAc,QAuCzB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/react-navigation/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAChF,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAU,CAAC;AAM5D;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,yBAAyB,CAAC,aAAa,CAAC,EACpD,KAAK,EAAE,WAAW,EAAE,EACpB,OAAO,CAAC,EAAE,cAAc,QA+DzB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@27appstudio/react-native-bottom-toolbar",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Native bottom toolbar for React Native: UIKit's toolbar on iOS (Liquid Glass on iOS 26) and a Material 3 floating toolbar on Android.",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -13,9 +13,10 @@ import { hide, show } from '../index';
13
13
  *
14
14
  * The toolbar belongs to the whole stack: it is shown once the push has landed
15
15
  * (`transitionEnd`), or at once on the stack's first screen; again on
16
- * returning from a modal (`focus`); and hidden on `blur`. `items` and
17
- * `options` are read at show time, so a new array (a language change) needs
18
- * no resubscribe; call `show` to update a toolbar already on screen.
16
+ * returning from a modal (`focus`); and hidden on `blur`. While the screen is
17
+ * on top, new `items` or `options` update the toolbar (an empty array hides
18
+ * it), so memoize them (`useMemo`): a new array on every render would send it
19
+ * to the native side every time.
19
20
  */
20
21
  export function useBottomToolbar(
21
22
  navigation: NativeStackNavigationProp<ParamListBase>,
@@ -24,15 +25,25 @@ export function useBottomToolbar(
24
25
  ) {
25
26
  const latest = useRef({ items, options });
26
27
  latest.current = { items, options };
28
+ // Whether the push has landed, so `show` reaches this screen and not the one
29
+ // below; and what was last sent, so an update only goes out when it changed.
30
+ const arrived = useRef(false);
31
+ const shown = useRef<
32
+ { items: ToolbarItem[]; options?: ToolbarOptions } | undefined
33
+ >(undefined);
27
34
 
28
35
  useEffect(() => {
29
36
  // Back from a pop, both focus and transitionEnd show: the latter comes after
30
37
  // the popped screen's unmount, whose `hide` would otherwise win.
31
- const showLatest = () => show(latest.current.items, latest.current.options);
38
+ const showLatest = () => {
39
+ shown.current = latest.current;
40
+ show(latest.current.items, latest.current.options);
41
+ };
32
42
  // A pushed screen mounts before its push starts, so showing now would put
33
43
  // the items on the screen below; the first screen has no push to wait for.
34
- let arrived = navigation.isFocused() && navigation.getState().index === 0;
35
- if (arrived) {
44
+ arrived.current =
45
+ navigation.isFocused() && navigation.getState().index === 0;
46
+ if (arrived.current) {
36
47
  showLatest();
37
48
  }
38
49
 
@@ -40,13 +51,13 @@ export function useBottomToolbar(
40
51
  'transitionEnd',
41
52
  (event) => {
42
53
  if (!event.data.closing) {
43
- arrived = true;
54
+ arrived.current = true;
44
55
  showLatest();
45
56
  }
46
57
  }
47
58
  );
48
59
  const offFocus = navigation.addListener('focus', () => {
49
- if (arrived) {
60
+ if (arrived.current) {
50
61
  showLatest();
51
62
  }
52
63
  });
@@ -59,4 +70,18 @@ export function useBottomToolbar(
59
70
  hide();
60
71
  };
61
72
  }, [navigation]);
73
+
74
+ // New items or options on a screen whose toolbar is up: update it in place.
75
+ useEffect(() => {
76
+ const last = shown.current;
77
+ if (
78
+ !arrived.current ||
79
+ !navigation.isFocused() ||
80
+ (last && last.items === items && last.options === options)
81
+ ) {
82
+ return;
83
+ }
84
+ shown.current = { items, options };
85
+ show(items, options);
86
+ }, [items, navigation, options]);
62
87
  }