@27appstudio/react-native-bottom-toolbar 0.2.0 → 0.3.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
@@ -112,6 +112,21 @@ When `items` contains no space, iOS puts the buttons at the trailing edge. When
112
112
 
113
113
  `options.tintColor` sets the icon and title color. iOS defaults to the navigation bar's tint (matching the header buttons); Android defaults to the Material 3 theme.
114
114
 
115
+ ### `getHeight(): Promise<number>` and `useBottomToolbarHeight(): number`
116
+
117
+ The room the toolbar takes at the bottom, in points, to keep a list's last row clear of it (its touches take the whole band, not just the buttons):
118
+
119
+ - iOS: the band the bar adds over the content above the bottom safe area, measured from the screen's safe area (iOS 26 floats the bar inside a full-screen frame, so the bar's own frame says nothing). 0 where the bar doesn't sit under the content, such as a foldable's side column. While hidden, the band last measured, else the bar's own height.
120
+ - Android: the floating button or toolbar's height plus its margin, above the navigation bar.
121
+
122
+ `useBottomToolbarHeight()` keeps it up to date: it measures again once a `show` has settled and when the window changes size (rotation, a foldable opening). It reads 0 until the first answer.
123
+
124
+ ```tsx
125
+ const toolbarHeight = useBottomToolbarHeight();
126
+ // ...
127
+ <FlatList ListFooterComponent={<View style={{ height: toolbarHeight }} />} />;
128
+ ```
129
+
115
130
  ### `hide()`
116
131
 
117
132
  ### `addPressListener(listener: (id: string) => void): () => void`
@@ -15,6 +15,7 @@ import androidx.core.view.ViewCompat
15
15
  import androidx.core.view.WindowInsetsCompat
16
16
  import androidx.core.view.updateLayoutParams
17
17
  import java.lang.ref.WeakReference
18
+ import com.facebook.react.bridge.Promise
18
19
  import com.facebook.react.bridge.ReactApplicationContext
19
20
  import com.facebook.react.bridge.ReadableArray
20
21
  import com.facebook.react.bridge.ReadableMap
@@ -70,6 +71,23 @@ class BottomToolbarModule(reactContext: ReactApplicationContext) :
70
71
  }
71
72
  }
72
73
 
74
+ /**
75
+ * The room at the bottom, in dp, above the navigation bar: the shown view's height
76
+ * plus its margin, else a FAB's (the Material 3 size), which a one-button `show` adds.
77
+ */
78
+ override fun getHeight(promise: Promise) {
79
+ UiThreadUtil.runOnUiThread {
80
+ val view = shown?.get()
81
+ val density = reactApplicationContext.resources.displayMetrics.density
82
+ val height = if (view != null && view.height > 0) {
83
+ view.height / density + MARGIN_DP
84
+ } else {
85
+ FAB_SIZE_DP + MARGIN_DP
86
+ }
87
+ promise.resolve(height.toDouble())
88
+ }
89
+ }
90
+
73
91
  override fun hide() {
74
92
  UiThreadUtil.runOnUiThread { removeShown(animated = true) }
75
93
  }
@@ -191,5 +209,7 @@ class BottomToolbarModule(reactContext: ReactApplicationContext) :
191
209
  private const val EXIT_DURATION = 200L
192
210
  private const val ENTER_OFFSET_DP = 24f
193
211
  private const val MARGIN_DP = 16f
212
+ // Material 3's regular FAB.
213
+ private const val FAB_SIZE_DP = 56f
194
214
  }
195
215
  }
@@ -12,6 +12,8 @@
12
12
  * hides it on leave.
13
13
  */
14
14
  @implementation BottomToolbar {
15
+ /** The band last measured while the bar was shown, in points. */
16
+ NSNumber *_measuredHeight;
15
17
  __weak UIViewController *_owner;
16
18
  /** The navigation bar's tint, as last read undimmed. */
17
19
  UIColor *_barTint;
@@ -137,6 +139,32 @@ static UINavigationController *VisibleNavigationController(void)
137
139
  }
138
140
  }
139
141
 
142
+ - (void)getHeight:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
143
+ {
144
+ dispatch_async(dispatch_get_main_queue(), ^{
145
+ UINavigationController *navigation =
146
+ self->_owner.navigationController ?: VisibleNavigationController();
147
+ UIToolbar *toolbar = navigation.toolbar;
148
+ UIView *top = navigation.topViewController.viewIfLoaded;
149
+ if (toolbar == nil || top == nil) {
150
+ resolve(self->_measuredHeight ?: @0);
151
+ return;
152
+ }
153
+ if (!navigation.toolbarHidden) {
154
+ // UIKit adds the bar's band to the screen's bottom safe area: measured there, as
155
+ // the bar's own frame is the whole screen on iOS 26 (it floats inside it). 0 where
156
+ // the bar doesn't sit under the content, e.g. a foldable's side column.
157
+ CGFloat band = top.safeAreaInsets.bottom - navigation.view.safeAreaInsets.bottom;
158
+ self->_measuredHeight = @(MAX(0, band));
159
+ resolve(self->_measuredHeight);
160
+ return;
161
+ }
162
+ // Hidden: the band last measured, else the bar's own height (without iOS 26's gap
163
+ // under it, which only shows once the bar does).
164
+ resolve(self->_measuredHeight ?: @([toolbar sizeThatFits:navigation.view.bounds.size].height));
165
+ });
166
+ }
167
+
140
168
  - (void)hide
141
169
  {
142
170
  // The owner is kept (weakly) for the next `show`: a modal over it hides the toolbar,
@@ -1 +1 @@
1
- {"version":3,"names":["TurboModuleRegistry","get"],"sourceRoot":"../../src","sources":["NativeBottomToolbar.ts"],"mappings":";;AAEA,SAASA,mBAAmB,QAAQ,cAAc;AA6BlD,eAAeA,mBAAmB,CAACC,GAAG,CAAO,eAAe,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["TurboModuleRegistry","get"],"sourceRoot":"../../src","sources":["NativeBottomToolbar.ts"],"mappings":";;AAEA,SAASA,mBAAmB,QAAQ,cAAc;AAkClD,eAAeA,mBAAmB,CAACC,GAAG,CAAO,eAAe,CAAC","ignoreList":[]}
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
 
3
- import { processColor } from 'react-native';
3
+ import { useEffect, useState } from 'react';
4
+ import { processColor, useWindowDimensions } from 'react-native';
4
5
  import NativeBottomToolbar from "./NativeBottomToolbar.js";
5
6
  /** False where the native module is missing: web, Expo Go, an unlinked build. */
6
7
  export const isAvailable = NativeBottomToolbar != null;
@@ -39,14 +40,66 @@ export function show(items, options = {}) {
39
40
  } : item), typeof tintColor === 'number' ? {
40
41
  tintColor
41
42
  } : {});
43
+ // Once the bar has slid in (and any push has landed), its band is final.
44
+ setTimeout(refreshHeight, SETTLE_MS);
42
45
  }
43
46
 
47
+ /** Longer than UIKit's toolbar and push animations. */
48
+ const SETTLE_MS = 600;
49
+
44
50
  /** Hides the toolbar. */
45
51
  export function hide() {
46
52
  buttons = [];
47
53
  NativeBottomToolbar?.hide();
48
54
  }
49
55
 
56
+ /**
57
+ * The room the toolbar takes at the bottom of the screen, in points: on iOS the
58
+ * band it adds over the content above the bottom safe area (so not the home
59
+ * indicator; 0 where the bar doesn't sit under the content, e.g. a foldable's side
60
+ * column), on Android above the navigation bar. While hidden, the size it last
61
+ * took, so a list can keep that much room under its last row without jumping when
62
+ * the toolbar comes and goes. 0 without the native module.
63
+ */
64
+ export function getHeight() {
65
+ return NativeBottomToolbar?.getHeight() ?? Promise.resolve(0);
66
+ }
67
+ let lastHeight = 0;
68
+ const heightListeners = new Set();
69
+
70
+ /** Measures the band again and tells `useBottomToolbarHeight` users if it changed. */
71
+ function refreshHeight() {
72
+ getHeight().then(height => {
73
+ if (height !== lastHeight) {
74
+ lastHeight = height;
75
+ heightListeners.forEach(listener => listener(height));
76
+ }
77
+ });
78
+ }
79
+
80
+ /**
81
+ * `getHeight`, kept up to date: measured again once a `show` has settled (the band
82
+ * is only exact once the bar is on screen) and when the window changes size (a
83
+ * rotation, a foldable opening). 0 until the first answer.
84
+ */
85
+ export function useBottomToolbarHeight() {
86
+ const [height, setHeight] = useState(lastHeight);
87
+ const {
88
+ height: windowHeight,
89
+ width: windowWidth
90
+ } = useWindowDimensions();
91
+ useEffect(() => {
92
+ heightListeners.add(setHeight);
93
+ return () => {
94
+ heightListeners.delete(setHeight);
95
+ };
96
+ }, []);
97
+ useEffect(() => {
98
+ refreshHeight();
99
+ }, [windowHeight, windowWidth]);
100
+ return height;
101
+ }
102
+
50
103
  /**
51
104
  * Calls `listener` with the id of each tapped button, in addition to that
52
105
  * button's own `onPress`. Returns the unsubscribe function.
@@ -1 +1 @@
1
- {"version":3,"names":["processColor","NativeBottomToolbar","isAvailable","buttons","show","items","options","some","isButton","hide","filter","tintColor","map","item","accessibilityLabel","title","onPress","undefined","type","addPressListener","listener","subscription","onItemPress","remove","id","find","button"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAEA,SAASA,YAAY,QAAQ,cAAc;AAE3C,OAAOC,mBAAmB,MAAM,0BAAuB;AASvD;AACA,OAAO,MAAMC,WAAW,GAAGD,mBAAmB,IAAI,IAAI;AAEtD,IAAIE,OAAwB,GAAG,EAAE;;AAEjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,IAAIA,CAACC,KAAoB,EAAEC,OAAuB,GAAG,CAAC,CAAC,EAAE;EACvE,IAAI,CAACL,mBAAmB,EAAE;IACxB;EACF;EACA,IAAI,CAACI,KAAK,CAACE,IAAI,CAACC,QAAQ,CAAC,EAAE;IACzBC,IAAI,CAAC,CAAC;IACN;EACF;EACAN,OAAO,GAAGE,KAAK,CAACK,MAAM,CAACF,QAAQ,CAAC;EAChC,MAAMG,SAAS,GAAGX,YAAY,CAACM,OAAO,CAACK,SAAS,CAAC;EACjDV,mBAAmB,CAACG,IAAI,CACtBC,KAAK,CAACO,GAAG,CAAEC,IAAI,IACbL,QAAQ,CAACK,IAAI,CAAC,GACV;IACE,GAAGA,IAAI;IACPC,kBAAkB,EAAED,IAAI,CAACC,kBAAkB,IAAID,IAAI,CAACE,KAAK;IACzDC,OAAO,EAAEC,SAAS;IAClBC,IAAI,EAAE;EACR,CAAC,GACDL,IACN,CAAC,EACD,OAAOF,SAAS,KAAK,QAAQ,GAAG;IAAEA;EAAU,CAAC,GAAG,CAAC,CACnD,CAAC;AACH;;AAEA;AACA,OAAO,SAASF,IAAIA,CAAA,EAAG;EACrBN,OAAO,GAAG,EAAE;EACZF,mBAAmB,EAAEQ,IAAI,CAAC,CAAC;AAC7B;;AAEA;AACA;AACA;AACA;AACA,OAAO,SAASU,gBAAgBA,CAACC,QAA8B,EAAE;EAC/D,MAAMC,YAAY,GAAGpB,mBAAmB,EAAEqB,WAAW,CAACF,QAAQ,CAAC;EAC/D,OAAO,MAAMC,YAAY,EAAEE,MAAM,CAAC,CAAC;AACrC;AAEA,SAASf,QAAQA,CAACK,IAAiB,EAAyB;EAC1D,OAAOA,IAAI,CAACK,IAAI,KAAKD,SAAS,IAAIJ,IAAI,CAACK,IAAI,KAAK,QAAQ;AAC1D;AAEAjB,mBAAmB,EAAEqB,WAAW,CAAEE,EAAE,IAClCrB,OAAO,CAACsB,IAAI,CAAEC,MAAM,IAAKA,MAAM,CAACF,EAAE,KAAKA,EAAE,CAAC,EAAER,OAAO,GAAG,CACxD,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["useEffect","useState","processColor","useWindowDimensions","NativeBottomToolbar","isAvailable","buttons","show","items","options","some","isButton","hide","filter","tintColor","map","item","accessibilityLabel","title","onPress","undefined","type","setTimeout","refreshHeight","SETTLE_MS","getHeight","Promise","resolve","lastHeight","heightListeners","Set","then","height","forEach","listener","useBottomToolbarHeight","setHeight","windowHeight","width","windowWidth","add","delete","addPressListener","subscription","onItemPress","remove","id","find","button"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":"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","ignoreList":[]}
@@ -19,6 +19,11 @@ type NativeToolbarOptions = {
19
19
  tintColor?: number;
20
20
  };
21
21
  export interface Spec extends TurboModule {
22
+ /**
23
+ * The room the toolbar takes at the bottom, in points: above the bottom safe area on
24
+ * iOS, above the navigation bar on Android. Its size when shown, even while hidden.
25
+ */
26
+ getHeight(): Promise<number>;
22
27
  hide(): void;
23
28
  readonly onItemPress: CodegenTypes.EventEmitter<string>;
24
29
  show(items: NativeToolbarItem[], options: NativeToolbarOptions): void;
@@ -1 +1 @@
1
- {"version":3,"file":"NativeBottomToolbar.d.ts","sourceRoot":"","sources":["../../../src/NativeBottomToolbar.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAI9D,KAAK,iBAAiB,GAAG;IACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yEAAyE;IACzE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,4CAA4C;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,KAAK,CAAC,EAAE,YAAY,CAAC,MAAM,CAAC;CAC7B,CAAC;AAEF,KAAK,oBAAoB,GAAG;IAC1B,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,IAAI,IAAI,IAAI,CAAC;IACb,QAAQ,CAAC,WAAW,EAAE,YAAY,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;IACxD,IAAI,CAAC,KAAK,EAAE,iBAAiB,EAAE,EAAE,OAAO,EAAE,oBAAoB,GAAG,IAAI,CAAC;CACvE;;AAED,wBAA8D"}
1
+ {"version":3,"file":"NativeBottomToolbar.d.ts","sourceRoot":"","sources":["../../../src/NativeBottomToolbar.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAI9D,KAAK,iBAAiB,GAAG;IACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yEAAyE;IACzE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,4CAA4C;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,KAAK,CAAC,EAAE,YAAY,CAAC,MAAM,CAAC;CAC7B,CAAC;AAEF,KAAK,oBAAoB,GAAG;IAC1B,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC;;;OAGG;IACH,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7B,IAAI,IAAI,IAAI,CAAC;IACb,QAAQ,CAAC,WAAW,EAAE,YAAY,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;IACxD,IAAI,CAAC,KAAK,EAAE,iBAAiB,EAAE,EAAE,OAAO,EAAE,oBAAoB,GAAG,IAAI,CAAC;CACvE;;AAED,wBAA8D"}
@@ -20,6 +20,21 @@ export declare const isAvailable: boolean;
20
20
  export declare function show(items: ToolbarItem[], options?: ToolbarOptions): void;
21
21
  /** Hides the toolbar. */
22
22
  export declare function hide(): void;
23
+ /**
24
+ * The room the toolbar takes at the bottom of the screen, in points: on iOS the
25
+ * band it adds over the content above the bottom safe area (so not the home
26
+ * indicator; 0 where the bar doesn't sit under the content, e.g. a foldable's side
27
+ * column), on Android above the navigation bar. While hidden, the size it last
28
+ * took, so a list can keep that much room under its last row without jumping when
29
+ * the toolbar comes and goes. 0 without the native module.
30
+ */
31
+ export declare function getHeight(): Promise<number>;
32
+ /**
33
+ * `getHeight`, kept up to date: measured again once a `show` has settled (the band
34
+ * is only exact once the bar is on screen) and when the window changes size (a
35
+ * rotation, a foldable opening). 0 until the first answer.
36
+ */
37
+ export declare function useBottomToolbarHeight(): number;
23
38
  /**
24
39
  * Calls `listener` with the id of each tapped button, in addition to that
25
40
  * button's own `onPress`. Returns the unsubscribe function.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,WAAW,EAAE,cAAc,EAAE,MAAM,YAAS,CAAC;AAM1E,YAAY,EACV,aAAa,EACb,WAAW,EACX,cAAc,EACd,YAAY,GACb,MAAM,YAAS,CAAC;AAEjB,iFAAiF;AACjF,eAAO,MAAM,WAAW,SAA8B,CAAC;AAIvD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,IAAI,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,OAAO,GAAE,cAAmB,QAuBtE;AAED,yBAAyB;AACzB,wBAAgB,IAAI,SAGnB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,0BAG9D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,WAAW,EAAE,cAAc,EAAE,MAAM,YAAS,CAAC;AAO1E,YAAY,EACV,aAAa,EACb,WAAW,EACX,cAAc,EACd,YAAY,GACb,MAAM,YAAS,CAAC;AAEjB,iFAAiF;AACjF,eAAO,MAAM,WAAW,SAA8B,CAAC;AAIvD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,IAAI,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,OAAO,GAAE,cAAmB,QAyBtE;AAKD,yBAAyB;AACzB,wBAAgB,IAAI,SAGnB;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CAE3C;AAeD;;;;GAIG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAa/C;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,0BAG9D"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@27appstudio/react-native-bottom-toolbar",
3
- "version": "0.2.0",
3
+ "version": "0.3.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",
@@ -24,6 +24,11 @@ type NativeToolbarOptions = {
24
24
  };
25
25
 
26
26
  export interface Spec extends TurboModule {
27
+ /**
28
+ * The room the toolbar takes at the bottom, in points: above the bottom safe area on
29
+ * iOS, above the navigation bar on Android. Its size when shown, even while hidden.
30
+ */
31
+ getHeight(): Promise<number>;
27
32
  hide(): void;
28
33
  readonly onItemPress: CodegenTypes.EventEmitter<string>;
29
34
  show(items: NativeToolbarItem[], options: NativeToolbarOptions): void;
package/src/index.tsx CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { ToolbarButton, ToolbarItem, ToolbarOptions } from './types';
2
2
 
3
- import { processColor } from 'react-native';
3
+ import { useEffect, useState } from 'react';
4
+ import { processColor, useWindowDimensions } from 'react-native';
4
5
 
5
6
  import NativeBottomToolbar from './NativeBottomToolbar';
6
7
 
@@ -54,14 +55,64 @@ export function show(items: ToolbarItem[], options: ToolbarOptions = {}) {
54
55
  ),
55
56
  typeof tintColor === 'number' ? { tintColor } : {}
56
57
  );
58
+ // Once the bar has slid in (and any push has landed), its band is final.
59
+ setTimeout(refreshHeight, SETTLE_MS);
57
60
  }
58
61
 
62
+ /** Longer than UIKit's toolbar and push animations. */
63
+ const SETTLE_MS = 600;
64
+
59
65
  /** Hides the toolbar. */
60
66
  export function hide() {
61
67
  buttons = [];
62
68
  NativeBottomToolbar?.hide();
63
69
  }
64
70
 
71
+ /**
72
+ * The room the toolbar takes at the bottom of the screen, in points: on iOS the
73
+ * band it adds over the content above the bottom safe area (so not the home
74
+ * indicator; 0 where the bar doesn't sit under the content, e.g. a foldable's side
75
+ * column), on Android above the navigation bar. While hidden, the size it last
76
+ * took, so a list can keep that much room under its last row without jumping when
77
+ * the toolbar comes and goes. 0 without the native module.
78
+ */
79
+ export function getHeight(): Promise<number> {
80
+ return NativeBottomToolbar?.getHeight() ?? Promise.resolve(0);
81
+ }
82
+
83
+ let lastHeight = 0;
84
+ const heightListeners = new Set<(height: number) => void>();
85
+
86
+ /** Measures the band again and tells `useBottomToolbarHeight` users if it changed. */
87
+ function refreshHeight() {
88
+ getHeight().then((height) => {
89
+ if (height !== lastHeight) {
90
+ lastHeight = height;
91
+ heightListeners.forEach((listener) => listener(height));
92
+ }
93
+ });
94
+ }
95
+
96
+ /**
97
+ * `getHeight`, kept up to date: measured again once a `show` has settled (the band
98
+ * is only exact once the bar is on screen) and when the window changes size (a
99
+ * rotation, a foldable opening). 0 until the first answer.
100
+ */
101
+ export function useBottomToolbarHeight(): number {
102
+ const [height, setHeight] = useState(lastHeight);
103
+ const { height: windowHeight, width: windowWidth } = useWindowDimensions();
104
+ useEffect(() => {
105
+ heightListeners.add(setHeight);
106
+ return () => {
107
+ heightListeners.delete(setHeight);
108
+ };
109
+ }, []);
110
+ useEffect(() => {
111
+ refreshHeight();
112
+ }, [windowHeight, windowWidth]);
113
+ return height;
114
+ }
115
+
65
116
  /**
66
117
  * Calls `listener` with the id of each tapped button, in addition to that
67
118
  * button's own `onPress`. Returns the unsubscribe function.