react-native-terra-ui 0.10.0 → 0.10.1
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/CHANGELOG.md +6 -0
- package/lib/module/components/toast/ToastProvider.js +9 -236
- package/lib/module/components/toast/ToastProvider.js.map +1 -1
- package/lib/module/components/toast/hooks/use-toast-manager.js +96 -0
- package/lib/module/components/toast/hooks/use-toast-manager.js.map +1 -0
- package/lib/module/components/toast/parts/ToastAnimatedItem.js +34 -50
- package/lib/module/components/toast/parts/ToastAnimatedItem.js.map +1 -1
- package/lib/module/components/toast/parts/ToastOverlay.ios.js +36 -0
- package/lib/module/components/toast/parts/ToastOverlay.ios.js.map +1 -0
- package/lib/module/components/toast/parts/ToastOverlay.js +34 -0
- package/lib/module/components/toast/parts/ToastOverlay.js.map +1 -0
- package/lib/module/components/toast/parts/ToastViewport.js +91 -0
- package/lib/module/components/toast/parts/ToastViewport.js.map +1 -0
- package/lib/typescript/src/components/toast/ToastProvider.d.ts.map +1 -1
- package/lib/typescript/src/components/toast/hooks/use-toast-manager.d.ts +6 -0
- package/lib/typescript/src/components/toast/hooks/use-toast-manager.d.ts.map +1 -0
- package/lib/typescript/src/components/toast/parts/ToastAnimatedItem.d.ts +3 -2
- package/lib/typescript/src/components/toast/parts/ToastAnimatedItem.d.ts.map +1 -1
- package/lib/typescript/src/components/toast/parts/ToastOverlay.d.ts +28 -0
- package/lib/typescript/src/components/toast/parts/ToastOverlay.d.ts.map +1 -0
- package/lib/typescript/src/components/toast/parts/ToastOverlay.ios.d.ts +28 -0
- package/lib/typescript/src/components/toast/parts/ToastOverlay.ios.d.ts.map +1 -0
- package/lib/typescript/src/components/toast/parts/ToastViewport.d.ts +11 -0
- package/lib/typescript/src/components/toast/parts/ToastViewport.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/components/toast/ToastProvider.tsx +7 -288
- package/src/components/toast/hooks/use-toast-manager.tsx +114 -0
- package/src/components/toast/parts/ToastAnimatedItem.tsx +32 -50
- package/src/components/toast/parts/ToastOverlay.ios.tsx +30 -0
- package/src/components/toast/parts/ToastOverlay.tsx +28 -0
- package/src/components/toast/parts/ToastViewport.tsx +104 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
4
|
+
/**
|
|
5
|
+
* Hosts the toast viewport where it already is, because on Android it is
|
|
6
|
+
* already in the right place.
|
|
7
|
+
*
|
|
8
|
+
* The iOS variant explains the problem: a sheet there is a separate view
|
|
9
|
+
* controller, so a toast in the root tree paints underneath it. Android does
|
|
10
|
+
* not have that problem for these screens. `react-native-screens` handles
|
|
11
|
+
* `formSheet` as a `StackPresentation` inside the screen stack (see
|
|
12
|
+
* `Screen.kt`), which lives in the Activity's own window — the same window as
|
|
13
|
+
* this provider — so the viewport's `elevation: 9999` puts it on top the
|
|
14
|
+
* ordinary way. Verified on an API 36 emulator: a toast raised from a
|
|
15
|
+
* `formSheet` route renders over that sheet.
|
|
16
|
+
*
|
|
17
|
+
* Not to be confused with `ScreenModalFragment`, which *is* a
|
|
18
|
+
* `BottomSheetDialogFragment` and so does carry its own `Window`. It backs a
|
|
19
|
+
* different presentation, not the `formSheet` route above. A toast raised from
|
|
20
|
+
* something hosted in a real `Dialog` — that fragment, or React Native's own
|
|
21
|
+
* `Modal` — would still be behind it, and `elevation` cannot cross windows.
|
|
22
|
+
* Untested, and left alone: `FullWindowOverlay` is iOS-only (elsewhere it warns
|
|
23
|
+
* and renders a plain `View`), so the remedy there would be to own a window
|
|
24
|
+
* too, via a React Native `Modal`, which brings its own back-button and
|
|
25
|
+
* touch-capture behaviour.
|
|
26
|
+
*/
|
|
27
|
+
export function ToastOverlay({
|
|
28
|
+
children
|
|
29
|
+
}) {
|
|
30
|
+
return /*#__PURE__*/_jsx(_Fragment, {
|
|
31
|
+
children: children
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=ToastOverlay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["ToastOverlay","children","_jsx","_Fragment"],"sourceRoot":"../../../../../src","sources":["components/toast/parts/ToastOverlay.tsx"],"mappings":";;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASA,YAAYA,CAAC;EAAEC;AAAkC,CAAC,EAAE;EAClE,oBAAOC,IAAA,CAAAC,SAAA;IAAAF,QAAA,EAAGA;EAAQ,CAAG,CAAC;AACxB","ignoreList":[]}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { useEffect } from "react";
|
|
4
|
+
import { Keyboard, Platform } from "react-native";
|
|
5
|
+
import Animated, { useAnimatedStyle, useSharedValue, withTiming } from "react-native-reanimated";
|
|
6
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
7
|
+
import { ToastAnimatedItem } from "./ToastAnimatedItem.js";
|
|
8
|
+
import { ToastOverlay } from "./ToastOverlay";
|
|
9
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
10
|
+
export function ToastViewport({
|
|
11
|
+
placement,
|
|
12
|
+
toasts,
|
|
13
|
+
manager,
|
|
14
|
+
maxVisibleToasts,
|
|
15
|
+
offset
|
|
16
|
+
}) {
|
|
17
|
+
const safeAreaInsets = useSafeAreaInsets();
|
|
18
|
+
const heights = useSharedValue({});
|
|
19
|
+
const keyboardHeight = useSharedValue(0);
|
|
20
|
+
const baseBottom = offset?.bottom ?? safeAreaInsets.bottom + (Platform.OS === "ios" ? 6 : 12);
|
|
21
|
+
const baseTop = offset?.top ?? safeAreaInsets.top + (Platform.OS === "ios" ? 0 : 12);
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (placement !== "bottom") return;
|
|
24
|
+
const showEvent = Platform.OS === "ios" ? "keyboardWillShow" : "keyboardDidShow";
|
|
25
|
+
const hideEvent = Platform.OS === "ios" ? "keyboardWillHide" : "keyboardDidHide";
|
|
26
|
+
const showSub = Keyboard.addListener(showEvent, e => {
|
|
27
|
+
keyboardHeight.value = withTiming(e.endCoordinates.height);
|
|
28
|
+
});
|
|
29
|
+
const hideSub = Keyboard.addListener(hideEvent, () => {
|
|
30
|
+
keyboardHeight.value = withTiming(0);
|
|
31
|
+
});
|
|
32
|
+
return () => {
|
|
33
|
+
showSub.remove();
|
|
34
|
+
hideSub.remove();
|
|
35
|
+
};
|
|
36
|
+
}, [keyboardHeight, placement]);
|
|
37
|
+
const gap = Platform.OS === "ios" ? 6 : 12;
|
|
38
|
+
const keyboardStyle = useAnimatedStyle(() => {
|
|
39
|
+
if (placement === "top") return {
|
|
40
|
+
top: baseTop
|
|
41
|
+
};
|
|
42
|
+
if (keyboardHeight.value > 0) {
|
|
43
|
+
return {
|
|
44
|
+
bottom: keyboardHeight.value + gap
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
bottom: baseBottom
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
const activeIds = new Set(toasts.map(toast => toast.id));
|
|
53
|
+
const nextHeights = {};
|
|
54
|
+
for (const [id, height] of Object.entries(heights.value)) {
|
|
55
|
+
if (activeIds.has(id)) {
|
|
56
|
+
nextHeights[id] = height;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
heights.value = nextHeights;
|
|
60
|
+
}, [heights, toasts]);
|
|
61
|
+
const frontToast = toasts[toasts.length - 1];
|
|
62
|
+
if (!frontToast) return null;
|
|
63
|
+
return /*#__PURE__*/_jsx(ToastOverlay, {
|
|
64
|
+
children: /*#__PURE__*/_jsx(Animated.View, {
|
|
65
|
+
pointerEvents: "box-none",
|
|
66
|
+
style: [{
|
|
67
|
+
position: "absolute",
|
|
68
|
+
zIndex: 9999,
|
|
69
|
+
elevation: 9999,
|
|
70
|
+
left: offset?.left ?? safeAreaInsets.left + 12,
|
|
71
|
+
right: offset?.right ?? safeAreaInsets.right + 12
|
|
72
|
+
}, keyboardStyle],
|
|
73
|
+
children: toasts.map((toast, index) => /*#__PURE__*/_jsx(ToastAnimatedItem, {
|
|
74
|
+
heights: heights,
|
|
75
|
+
id: toast.id,
|
|
76
|
+
index: index,
|
|
77
|
+
maxVisibleToasts: maxVisibleToasts,
|
|
78
|
+
placement: placement,
|
|
79
|
+
total: toasts.length,
|
|
80
|
+
frontId: frontToast.id,
|
|
81
|
+
children: toast.component({
|
|
82
|
+
id: toast.id,
|
|
83
|
+
index,
|
|
84
|
+
hide: manager.hide,
|
|
85
|
+
show: manager.show
|
|
86
|
+
})
|
|
87
|
+
}, toast.id))
|
|
88
|
+
})
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=ToastViewport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["useEffect","Keyboard","Platform","Animated","useAnimatedStyle","useSharedValue","withTiming","useSafeAreaInsets","ToastAnimatedItem","ToastOverlay","jsx","_jsx","ToastViewport","placement","toasts","manager","maxVisibleToasts","offset","safeAreaInsets","heights","keyboardHeight","baseBottom","bottom","OS","baseTop","top","showEvent","hideEvent","showSub","addListener","e","value","endCoordinates","height","hideSub","remove","gap","keyboardStyle","activeIds","Set","map","toast","id","nextHeights","Object","entries","has","frontToast","length","children","View","pointerEvents","style","position","zIndex","elevation","left","right","index","total","frontId","component","hide","show"],"sourceRoot":"../../../../../src","sources":["components/toast/parts/ToastViewport.tsx"],"mappings":";;AAAA,SAASA,SAAS,QAAQ,OAAO;AACjC,SAASC,QAAQ,EAAEC,QAAQ,QAAQ,cAAc;AAEjD,OAAOC,QAAQ,IAAIC,gBAAgB,EAAEC,cAAc,EAAEC,UAAU,QAAQ,yBAAyB;AAChG,SAASC,iBAAiB,QAAQ,gCAAgC;AAGlE,SAASC,iBAAiB;AAC1B,SAASC,YAAY;AAAyB,SAAAC,GAAA,IAAAC,IAAA;AAU9C,OAAO,SAASC,aAAaA,CAAC;EAAEC,SAAS;EAAEC,MAAM;EAAEC,OAAO;EAAEC,gBAAgB;EAAEC;AAA2B,CAAC,EAAE;EAC1G,MAAMC,cAAc,GAAGX,iBAAiB,CAAC,CAAC;EAC1C,MAAMY,OAAO,GAAGd,cAAc,CAA0B,CAAC,CAAC,CAAC;EAC3D,MAAMe,cAAc,GAAGf,cAAc,CAAC,CAAC,CAAC;EAExC,MAAMgB,UAAU,GAAGJ,MAAM,EAAEK,MAAM,IAAIJ,cAAc,CAACI,MAAM,IAAIpB,QAAQ,CAACqB,EAAE,KAAK,KAAK,GAAG,CAAC,GAAG,EAAE,CAAC;EAC7F,MAAMC,OAAO,GAAGP,MAAM,EAAEQ,GAAG,IAAIP,cAAc,CAACO,GAAG,IAAIvB,QAAQ,CAACqB,EAAE,KAAK,KAAK,GAAG,CAAC,GAAG,EAAE,CAAC;EAEpFvB,SAAS,CAAC,MAAM;IACd,IAAIa,SAAS,KAAK,QAAQ,EAAE;IAC5B,MAAMa,SAAS,GAAGxB,QAAQ,CAACqB,EAAE,KAAK,KAAK,GAAG,kBAAkB,GAAG,iBAAiB;IAChF,MAAMI,SAAS,GAAGzB,QAAQ,CAACqB,EAAE,KAAK,KAAK,GAAG,kBAAkB,GAAG,iBAAiB;IAChF,MAAMK,OAAO,GAAG3B,QAAQ,CAAC4B,WAAW,CAACH,SAAS,EAAGI,CAAC,IAAK;MACrDV,cAAc,CAACW,KAAK,GAAGzB,UAAU,CAACwB,CAAC,CAACE,cAAc,CAACC,MAAM,CAAC;IAC5D,CAAC,CAAC;IACF,MAAMC,OAAO,GAAGjC,QAAQ,CAAC4B,WAAW,CAACF,SAAS,EAAE,MAAM;MACpDP,cAAc,CAACW,KAAK,GAAGzB,UAAU,CAAC,CAAC,CAAC;IACtC,CAAC,CAAC;IACF,OAAO,MAAM;MACXsB,OAAO,CAACO,MAAM,CAAC,CAAC;MAChBD,OAAO,CAACC,MAAM,CAAC,CAAC;IAClB,CAAC;EACH,CAAC,EAAE,CAACf,cAAc,EAAEP,SAAS,CAAC,CAAC;EAE/B,MAAMuB,GAAG,GAAGlC,QAAQ,CAACqB,EAAE,KAAK,KAAK,GAAG,CAAC,GAAG,EAAE;EAE1C,MAAMc,aAAa,GAAGjC,gBAAgB,CAAC,MAAM;IAC3C,IAAIS,SAAS,KAAK,KAAK,EAAE,OAAO;MAAEY,GAAG,EAAED;IAAQ,CAAC;IAChD,IAAIJ,cAAc,CAACW,KAAK,GAAG,CAAC,EAAE;MAC5B,OAAO;QAAET,MAAM,EAAEF,cAAc,CAACW,KAAK,GAAGK;MAAI,CAAC;IAC/C;IACA,OAAO;MAAEd,MAAM,EAAED;IAAW,CAAC;EAC/B,CAAC,CAAC;EAEFrB,SAAS,CAAC,MAAM;IACd,MAAMsC,SAAS,GAAG,IAAIC,GAAG,CAACzB,MAAM,CAAC0B,GAAG,CAAEC,KAAK,IAAKA,KAAK,CAACC,EAAE,CAAC,CAAC;IAC1D,MAAMC,WAAoC,GAAG,CAAC,CAAC;IAC/C,KAAK,MAAM,CAACD,EAAE,EAAET,MAAM,CAAC,IAAIW,MAAM,CAACC,OAAO,CAAC1B,OAAO,CAACY,KAAK,CAAC,EAAE;MACxD,IAAIO,SAAS,CAACQ,GAAG,CAACJ,EAAE,CAAC,EAAE;QACrBC,WAAW,CAACD,EAAE,CAAC,GAAGT,MAAM;MAC1B;IACF;IACAd,OAAO,CAACY,KAAK,GAAGY,WAAW;EAC7B,CAAC,EAAE,CAACxB,OAAO,EAAEL,MAAM,CAAC,CAAC;EAErB,MAAMiC,UAAU,GAAGjC,MAAM,CAACA,MAAM,CAACkC,MAAM,GAAG,CAAC,CAAC;EAC5C,IAAI,CAACD,UAAU,EAAE,OAAO,IAAI;EAE5B,oBACEpC,IAAA,CAACF,YAAY;IAAAwC,QAAA,eACXtC,IAAA,CAACR,QAAQ,CAAC+C,IAAI;MACZC,aAAa,EAAC,UAAU;MACxBC,KAAK,EAAE,CACL;QACEC,QAAQ,EAAE,UAAU;QACpBC,MAAM,EAAE,IAAI;QACZC,SAAS,EAAE,IAAI;QACfC,IAAI,EAAEvC,MAAM,EAAEuC,IAAI,IAAItC,cAAc,CAACsC,IAAI,GAAG,EAAE;QAC9CC,KAAK,EAAExC,MAAM,EAAEwC,KAAK,IAAIvC,cAAc,CAACuC,KAAK,GAAG;MACjD,CAAC,EACDpB,aAAa,CACb;MAAAY,QAAA,EAEDnC,MAAM,CAAC0B,GAAG,CAAC,CAACC,KAAK,EAAEiB,KAAK,kBACvB/C,IAAA,CAACH,iBAAiB;QAEhBW,OAAO,EAAEA,OAAQ;QACjBuB,EAAE,EAAED,KAAK,CAACC,EAAG;QACbgB,KAAK,EAAEA,KAAM;QACb1C,gBAAgB,EAAEA,gBAAiB;QACnCH,SAAS,EAAEA,SAAU;QACrB8C,KAAK,EAAE7C,MAAM,CAACkC,MAAO;QACrBY,OAAO,EAAEb,UAAU,CAACL,EAAG;QAAAO,QAAA,EAEtBR,KAAK,CAACoB,SAAS,CAAC;UACfnB,EAAE,EAAED,KAAK,CAACC,EAAE;UACZgB,KAAK;UACLI,IAAI,EAAE/C,OAAO,CAAC+C,IAAI;UAClBC,IAAI,EAAEhD,OAAO,CAACgD;QAChB,CAAC;MAAC,GAdGtB,KAAK,CAACC,EAeM,CACpB;IAAC,CACW;EAAC,CACJ,CAAC;AAEnB","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ToastProvider.d.ts","sourceRoot":"","sources":["../../../../../src/components/toast/ToastProvider.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ToastProvider.d.ts","sourceRoot":"","sources":["../../../../../src/components/toast/ToastProvider.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAS,CAAC;AAIhE,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,gBAAwB,EACxB,eAAsB,EACtB,gBAAoB,EACpB,MAAM,GACP,EAAE,kBAAkB,+BAwBpB;AAED,wBAAgB,QAAQ,IAAI,YAAY,CAMvC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ToastItem, ToastManager, ToastPlacement } from "../types.js";
|
|
2
|
+
export declare function useToastManager(defaultPlacement: ToastPlacement, defaultDuration: number): {
|
|
3
|
+
toasts: ToastItem[];
|
|
4
|
+
manager: ToastManager;
|
|
5
|
+
};
|
|
6
|
+
//# sourceMappingURL=use-toast-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-toast-manager.d.ts","sourceRoot":"","sources":["../../../../../../src/components/toast/hooks/use-toast-manager.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAGV,SAAS,EACT,YAAY,EACZ,cAAc,EAEf,MAAM,aAAU,CAAC;AAmBlB,wBAAgB,eAAe,CAAC,gBAAgB,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM;;;EAoFxF"}
|
|
@@ -8,11 +8,12 @@ export interface ToastAnimatedItemProps {
|
|
|
8
8
|
index: number;
|
|
9
9
|
maxVisibleToasts: number;
|
|
10
10
|
placement: ToastPlacement;
|
|
11
|
-
total:
|
|
11
|
+
total: number;
|
|
12
|
+
frontId: ToastId;
|
|
12
13
|
}
|
|
13
14
|
export declare const enteringTop: import("react-native-reanimated").ReanimatedKeyframe;
|
|
14
15
|
export declare const exitingTop: import("react-native-reanimated").ReanimatedKeyframe;
|
|
15
16
|
export declare const enteringBottom: import("react-native-reanimated").ReanimatedKeyframe;
|
|
16
17
|
export declare const exitingBottom: import("react-native-reanimated").ReanimatedKeyframe;
|
|
17
|
-
export declare function ToastAnimatedItem({ children, heights, id, index, maxVisibleToasts, placement, total, }: ToastAnimatedItemProps): import("react").JSX.Element;
|
|
18
|
+
export declare function ToastAnimatedItem({ children, heights, id, index, maxVisibleToasts, placement, total, frontId, }: ToastAnimatedItemProps): import("react").JSX.Element;
|
|
18
19
|
//# sourceMappingURL=ToastAnimatedItem.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ToastAnimatedItem.d.ts","sourceRoot":"","sources":["../../../../../../src/components/toast/parts/ToastAnimatedItem.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAG1C,OAAiB,
|
|
1
|
+
{"version":3,"file":"ToastAnimatedItem.d.ts","sourceRoot":"","sources":["../../../../../../src/components/toast/parts/ToastAnimatedItem.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAG1C,OAAiB,EAGf,KAAK,WAAW,EAIjB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAU,CAAC;AAExD,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,YAAY,CAAC;IACvB,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9C,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,cAAc,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,eAAO,MAAM,WAAW,sDAUR,CAAC;AAEjB,eAAO,MAAM,UAAU,sDAUP,CAAC;AAEjB,eAAO,MAAM,cAAc,sDAUX,CAAC;AAEjB,eAAO,MAAM,aAAa,sDAUV,CAAC;AAIjB,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,OAAO,EACP,EAAE,EACF,KAAK,EACL,gBAAgB,EAChB,SAAS,EACT,KAAK,EACL,OAAO,GACR,EAAE,sBAAsB,+BAqDxB"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Hosts the toast viewport where it already is, because on Android it is
|
|
4
|
+
* already in the right place.
|
|
5
|
+
*
|
|
6
|
+
* The iOS variant explains the problem: a sheet there is a separate view
|
|
7
|
+
* controller, so a toast in the root tree paints underneath it. Android does
|
|
8
|
+
* not have that problem for these screens. `react-native-screens` handles
|
|
9
|
+
* `formSheet` as a `StackPresentation` inside the screen stack (see
|
|
10
|
+
* `Screen.kt`), which lives in the Activity's own window — the same window as
|
|
11
|
+
* this provider — so the viewport's `elevation: 9999` puts it on top the
|
|
12
|
+
* ordinary way. Verified on an API 36 emulator: a toast raised from a
|
|
13
|
+
* `formSheet` route renders over that sheet.
|
|
14
|
+
*
|
|
15
|
+
* Not to be confused with `ScreenModalFragment`, which *is* a
|
|
16
|
+
* `BottomSheetDialogFragment` and so does carry its own `Window`. It backs a
|
|
17
|
+
* different presentation, not the `formSheet` route above. A toast raised from
|
|
18
|
+
* something hosted in a real `Dialog` — that fragment, or React Native's own
|
|
19
|
+
* `Modal` — would still be behind it, and `elevation` cannot cross windows.
|
|
20
|
+
* Untested, and left alone: `FullWindowOverlay` is iOS-only (elsewhere it warns
|
|
21
|
+
* and renders a plain `View`), so the remedy there would be to own a window
|
|
22
|
+
* too, via a React Native `Modal`, which brings its own back-button and
|
|
23
|
+
* touch-capture behaviour.
|
|
24
|
+
*/
|
|
25
|
+
export declare function ToastOverlay({ children }: {
|
|
26
|
+
children: ReactNode;
|
|
27
|
+
}): import("react").JSX.Element;
|
|
28
|
+
//# sourceMappingURL=ToastOverlay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ToastOverlay.d.ts","sourceRoot":"","sources":["../../../../../../src/components/toast/parts/ToastOverlay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,+BAEjE"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Lifts the toast viewport into a window of its own, above every presented
|
|
4
|
+
* view controller.
|
|
5
|
+
*
|
|
6
|
+
* Without this, a toast raised from a screen presented as `modal` or
|
|
7
|
+
* `formSheet` is invisible. `ToastProvider` sits above the app's navigator, so
|
|
8
|
+
* its viewport belongs to the root view controller's view; UIKit presents a
|
|
9
|
+
* sheet as a *separate* view controller on top of that one. They are different
|
|
10
|
+
* presentation layers, so no `zIndex` or `elevation` can lift the viewport over
|
|
11
|
+
* the sheet — the toast is mounted and animating, just underneath. The reader
|
|
12
|
+
* presses something, the app reports the result, and nothing appears.
|
|
13
|
+
*
|
|
14
|
+
* `FullWindowOverlay` hosts its children in its own `UIWindow`, which sits
|
|
15
|
+
* above presented controllers, so the viewport paints over a sheet the same way
|
|
16
|
+
* it paints over an ordinary screen.
|
|
17
|
+
*
|
|
18
|
+
* It covers the whole window, but it does not swallow the app's touches:
|
|
19
|
+
* `RNSFullWindowOverlay`'s `pointInside:` walks its subviews and answers NO
|
|
20
|
+
* anywhere they are not, so presses fall through to whatever is underneath.
|
|
21
|
+
*
|
|
22
|
+
* Mounted only alongside a non-empty viewport — `ToastViewport` returns `null`
|
|
23
|
+
* before reaching this — so an idle app carries no extra window.
|
|
24
|
+
*/
|
|
25
|
+
export declare function ToastOverlay({ children }: {
|
|
26
|
+
children: ReactNode;
|
|
27
|
+
}): import("react").JSX.Element;
|
|
28
|
+
//# sourceMappingURL=ToastOverlay.ios.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ToastOverlay.ios.d.ts","sourceRoot":"","sources":["../../../../../../src/components/toast/parts/ToastOverlay.ios.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAIvC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,+BAEjE"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ToastItem, ToastManager, ToastPlacement, ToastProviderProps } from "../types.js";
|
|
2
|
+
interface ToastViewportProps {
|
|
3
|
+
placement: ToastPlacement;
|
|
4
|
+
toasts: ToastItem[];
|
|
5
|
+
manager: ToastManager;
|
|
6
|
+
maxVisibleToasts: number;
|
|
7
|
+
offset?: ToastProviderProps["offset"];
|
|
8
|
+
}
|
|
9
|
+
export declare function ToastViewport({ placement, toasts, manager, maxVisibleToasts, offset }: ToastViewportProps): import("react").JSX.Element | null;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=ToastViewport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ToastViewport.d.ts","sourceRoot":"","sources":["../../../../../../src/components/toast/parts/ToastViewport.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAW,SAAS,EAAE,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,aAAU,CAAC;AAIrG,UAAU,kBAAkB;IAC1B,SAAS,EAAE,cAAc,CAAC;IAC1B,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,OAAO,EAAE,YAAY,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,kBAAkB,CAAC,QAAQ,CAAC,CAAC;CACvC;AAED,wBAAgB,aAAa,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,EAAE,kBAAkB,sCAqFzG"}
|
package/package.json
CHANGED
|
@@ -1,207 +1,19 @@
|
|
|
1
|
-
import { createContext,
|
|
2
|
-
import { Keyboard, Platform } from "react-native";
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
3
2
|
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
import { ToastAnimatedItem } from "./parts/ToastAnimatedItem";
|
|
8
|
-
import { DefaultToast } from "./Toast";
|
|
9
|
-
import type {
|
|
10
|
-
ToastComponentProps,
|
|
11
|
-
ToastDuration,
|
|
12
|
-
ToastId,
|
|
13
|
-
ToastItem,
|
|
14
|
-
ToastManager,
|
|
15
|
-
ToastPlacement,
|
|
16
|
-
ToastProviderProps,
|
|
17
|
-
ToastShowConfig,
|
|
18
|
-
ToastShowOptions,
|
|
19
|
-
ToastShowOptionsWithComponent,
|
|
20
|
-
} from "./types";
|
|
21
|
-
import { setToastManager } from "./utils/controller";
|
|
22
|
-
|
|
23
|
-
type ToastAction =
|
|
24
|
-
| { type: "show"; item: ToastItem }
|
|
25
|
-
| { type: "hide"; ids: ToastId[] }
|
|
26
|
-
| { type: "hideAll" }
|
|
27
|
-
| { type: "hidePlacement"; placement: ToastPlacement };
|
|
28
|
-
|
|
29
|
-
const DEFAULT_DURATION = 4000;
|
|
30
|
-
const DEFAULT_MAX_VISIBLE_TOASTS = 3;
|
|
3
|
+
import { useToastManager } from "./hooks/use-toast-manager";
|
|
4
|
+
import { ToastViewport } from "./parts/ToastViewport";
|
|
5
|
+
import type { ToastManager, ToastProviderProps } from "./types";
|
|
31
6
|
|
|
32
7
|
const ToastManagerContext = createContext<ToastManager | null>(null);
|
|
33
8
|
|
|
34
|
-
function toastReducer(state: ToastItem[], action: ToastAction): ToastItem[] {
|
|
35
|
-
switch (action.type) {
|
|
36
|
-
case "show":
|
|
37
|
-
return [...state.filter((toast) => toast.id !== action.item.id), action.item];
|
|
38
|
-
case "hide":
|
|
39
|
-
return state.filter((toast) => !action.ids.some((id) => id === toast.id));
|
|
40
|
-
case "hideAll":
|
|
41
|
-
return [];
|
|
42
|
-
case "hidePlacement":
|
|
43
|
-
return state.filter((toast) => toast.placement !== action.placement);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function createDefaultToastComponent(config: ToastShowConfig, manager: ToastManager) {
|
|
48
|
-
return ({ id, hide }: ToastComponentProps) => (
|
|
49
|
-
<DefaultToast
|
|
50
|
-
id={id}
|
|
51
|
-
hide={(toastId) => hide(String(toastId))}
|
|
52
|
-
variant={config.variant}
|
|
53
|
-
label={config.label}
|
|
54
|
-
description={config.description}
|
|
55
|
-
icon={config.icon}
|
|
56
|
-
actionLabel={config.actionLabel}
|
|
57
|
-
showCloseButton={config.showCloseButton}
|
|
58
|
-
onActionPress={() => config.onActionPress?.(manager)}
|
|
59
|
-
/>
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function isCustomToast(options: ToastShowOptions): options is ToastShowOptionsWithComponent {
|
|
64
|
-
return typeof options === "object" && options !== null && "component" in options;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function normalizePlacement(placement: ToastPlacement | undefined, fallback: ToastPlacement): ToastPlacement {
|
|
68
|
-
return placement ?? fallback;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function normalizeDuration(duration: ToastDuration | undefined, fallback: number): ToastDuration {
|
|
72
|
-
return duration ?? fallback;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function toastId(counter: number): ToastId {
|
|
76
|
-
return `toast-${Date.now()}-${counter}`;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
9
|
export function ToastProvider({
|
|
80
10
|
children,
|
|
81
11
|
defaultPlacement = "top",
|
|
82
|
-
defaultDuration =
|
|
83
|
-
maxVisibleToasts =
|
|
12
|
+
defaultDuration = 4000,
|
|
13
|
+
maxVisibleToasts = 3,
|
|
84
14
|
offset,
|
|
85
15
|
}: ToastProviderProps) {
|
|
86
|
-
const
|
|
87
|
-
const toastsRef = useRef<ToastItem[]>([]);
|
|
88
|
-
const counterRef = useRef(0);
|
|
89
|
-
const timeoutsRef = useRef<Map<ToastId, ReturnType<typeof setTimeout>>>(new Map());
|
|
90
|
-
|
|
91
|
-
useEffect(() => {
|
|
92
|
-
toastsRef.current = toasts;
|
|
93
|
-
}, [toasts]);
|
|
94
|
-
|
|
95
|
-
const clearToastTimeout = useCallback((id: ToastId) => {
|
|
96
|
-
const timeout = timeoutsRef.current.get(id);
|
|
97
|
-
if (timeout) clearTimeout(timeout);
|
|
98
|
-
timeoutsRef.current.delete(id);
|
|
99
|
-
}, []);
|
|
100
|
-
|
|
101
|
-
const hide = useCallback<ToastManager["hide"]>(
|
|
102
|
-
(id) => {
|
|
103
|
-
if (id === "all") {
|
|
104
|
-
for (const toast of toastsRef.current) {
|
|
105
|
-
clearToastTimeout(toast.id);
|
|
106
|
-
toast.onHide?.();
|
|
107
|
-
}
|
|
108
|
-
toastsRef.current = [];
|
|
109
|
-
dispatch({ type: "hideAll" });
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
const lastToastId = toastsRef.current[toastsRef.current.length - 1]?.id;
|
|
113
|
-
const ids = id === undefined ? (lastToastId ? [lastToastId] : []) : Array.isArray(id) ? id : [id];
|
|
114
|
-
|
|
115
|
-
if (ids.length === 0) return;
|
|
116
|
-
|
|
117
|
-
for (const toastId of ids) {
|
|
118
|
-
clearToastTimeout(toastId);
|
|
119
|
-
toastsRef.current.find((toast) => toast.id === toastId)?.onHide?.();
|
|
120
|
-
}
|
|
121
|
-
toastsRef.current = toastsRef.current.filter((toast) => !ids.some((toastId) => toastId === toast.id));
|
|
122
|
-
dispatch({ type: "hide", ids });
|
|
123
|
-
},
|
|
124
|
-
[clearToastTimeout]
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
const managerRef = useRef<ToastManager>({
|
|
128
|
-
show: () => "",
|
|
129
|
-
hide,
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
const show = useCallback<ToastManager["show"]>(
|
|
133
|
-
(options) => {
|
|
134
|
-
const nextId =
|
|
135
|
-
typeof options === "object" && options !== null && "id" in options
|
|
136
|
-
? (options.id ?? toastId(counterRef.current++))
|
|
137
|
-
: toastId(counterRef.current++);
|
|
138
|
-
|
|
139
|
-
const item =
|
|
140
|
-
typeof options === "string"
|
|
141
|
-
? {
|
|
142
|
-
id: nextId,
|
|
143
|
-
component: createDefaultToastComponent({ label: options }, managerRef.current),
|
|
144
|
-
duration: defaultDuration,
|
|
145
|
-
placement: defaultPlacement,
|
|
146
|
-
}
|
|
147
|
-
: isCustomToast(options)
|
|
148
|
-
? {
|
|
149
|
-
id: nextId,
|
|
150
|
-
component: options.component,
|
|
151
|
-
duration: normalizeDuration(options.duration, defaultDuration),
|
|
152
|
-
placement: normalizePlacement(options.placement, defaultPlacement),
|
|
153
|
-
onHide: options.onHide,
|
|
154
|
-
}
|
|
155
|
-
: {
|
|
156
|
-
id: nextId,
|
|
157
|
-
component: createDefaultToastComponent(options, managerRef.current),
|
|
158
|
-
duration: normalizeDuration(options.duration, defaultDuration),
|
|
159
|
-
placement: normalizePlacement(options.placement, defaultPlacement),
|
|
160
|
-
onHide: options.onHide,
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
const oppositePlacement = item.placement === "top" ? "bottom" : "top";
|
|
164
|
-
const oppositeToasts = toastsRef.current.filter((t) => t.placement === oppositePlacement);
|
|
165
|
-
if (oppositeToasts.length > 0) {
|
|
166
|
-
for (const t of oppositeToasts) {
|
|
167
|
-
clearToastTimeout(t.id);
|
|
168
|
-
t.onHide?.();
|
|
169
|
-
}
|
|
170
|
-
toastsRef.current = toastsRef.current.filter((t) => t.placement !== oppositePlacement);
|
|
171
|
-
dispatch({ type: "hidePlacement", placement: oppositePlacement });
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
clearToastTimeout(item.id);
|
|
175
|
-
toastsRef.current = [...toastsRef.current.filter((toast) => toast.id !== item.id), item];
|
|
176
|
-
dispatch({ type: "show", item });
|
|
177
|
-
if (typeof options === "object" && options !== null && "onShow" in options) {
|
|
178
|
-
options.onShow?.();
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (item.duration !== "persistent" && Number.isFinite(item.duration) && item.duration > 0) {
|
|
182
|
-
const timeout = setTimeout(() => hide(item.id), item.duration);
|
|
183
|
-
timeoutsRef.current.set(item.id, timeout);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return item.id;
|
|
187
|
-
},
|
|
188
|
-
[clearToastTimeout, defaultDuration, defaultPlacement, hide]
|
|
189
|
-
);
|
|
190
|
-
|
|
191
|
-
const manager = useMemo<ToastManager>(() => ({ show, hide }), [show, hide]);
|
|
192
|
-
managerRef.current = manager;
|
|
193
|
-
|
|
194
|
-
useEffect(() => {
|
|
195
|
-
setToastManager(manager);
|
|
196
|
-
return () => {
|
|
197
|
-
setToastManager(null);
|
|
198
|
-
for (const timeout of timeoutsRef.current.values()) {
|
|
199
|
-
clearTimeout(timeout);
|
|
200
|
-
}
|
|
201
|
-
timeoutsRef.current.clear();
|
|
202
|
-
};
|
|
203
|
-
}, [manager]);
|
|
204
|
-
|
|
16
|
+
const { toasts, manager } = useToastManager(defaultPlacement, defaultDuration);
|
|
205
17
|
const topToasts = toasts.filter((toast) => toast.placement === "top");
|
|
206
18
|
const bottomToasts = toasts.filter((toast) => toast.placement === "bottom");
|
|
207
19
|
|
|
@@ -226,99 +38,6 @@ export function ToastProvider({
|
|
|
226
38
|
);
|
|
227
39
|
}
|
|
228
40
|
|
|
229
|
-
interface ToastViewportProps {
|
|
230
|
-
placement: ToastPlacement;
|
|
231
|
-
toasts: ToastItem[];
|
|
232
|
-
manager: ToastManager;
|
|
233
|
-
maxVisibleToasts: number;
|
|
234
|
-
offset?: ToastProviderProps["offset"];
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function ToastViewport({ placement, toasts, manager, maxVisibleToasts, offset }: ToastViewportProps) {
|
|
238
|
-
const safeAreaInsets = useSafeAreaInsets();
|
|
239
|
-
const heights = useSharedValue<Record<ToastId, number>>({});
|
|
240
|
-
const total = useSharedValue(0);
|
|
241
|
-
const keyboardHeight = useSharedValue(0);
|
|
242
|
-
|
|
243
|
-
const baseBottom = offset?.bottom ?? safeAreaInsets.bottom + (Platform.OS === "ios" ? 6 : 12);
|
|
244
|
-
const baseTop = offset?.top ?? safeAreaInsets.top + (Platform.OS === "ios" ? 0 : 12);
|
|
245
|
-
|
|
246
|
-
useEffect(() => {
|
|
247
|
-
if (placement !== "bottom") return;
|
|
248
|
-
const showEvent = Platform.OS === "ios" ? "keyboardWillShow" : "keyboardDidShow";
|
|
249
|
-
const hideEvent = Platform.OS === "ios" ? "keyboardWillHide" : "keyboardDidHide";
|
|
250
|
-
const showSub = Keyboard.addListener(showEvent, (e) => {
|
|
251
|
-
keyboardHeight.value = withTiming(e.endCoordinates.height);
|
|
252
|
-
});
|
|
253
|
-
const hideSub = Keyboard.addListener(hideEvent, () => {
|
|
254
|
-
keyboardHeight.value = withTiming(0);
|
|
255
|
-
});
|
|
256
|
-
return () => {
|
|
257
|
-
showSub.remove();
|
|
258
|
-
hideSub.remove();
|
|
259
|
-
};
|
|
260
|
-
}, [keyboardHeight, placement]);
|
|
261
|
-
|
|
262
|
-
const gap = Platform.OS === "ios" ? 6 : 12;
|
|
263
|
-
|
|
264
|
-
const keyboardStyle = useAnimatedStyle(() => {
|
|
265
|
-
if (placement === "top") return { top: baseTop };
|
|
266
|
-
if (keyboardHeight.value > 0) {
|
|
267
|
-
return { bottom: keyboardHeight.value + gap };
|
|
268
|
-
}
|
|
269
|
-
return { bottom: baseBottom };
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
useEffect(() => {
|
|
273
|
-
total.value = toasts.length;
|
|
274
|
-
const activeIds = new Set(toasts.map((toast) => toast.id));
|
|
275
|
-
const nextHeights: Record<ToastId, number> = {};
|
|
276
|
-
for (const [id, height] of Object.entries(heights.value)) {
|
|
277
|
-
if (activeIds.has(id)) {
|
|
278
|
-
nextHeights[id] = height;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
heights.value = nextHeights;
|
|
282
|
-
}, [heights, toasts, total]);
|
|
283
|
-
|
|
284
|
-
if (toasts.length === 0) return null;
|
|
285
|
-
|
|
286
|
-
return (
|
|
287
|
-
<Animated.View
|
|
288
|
-
pointerEvents="box-none"
|
|
289
|
-
style={[
|
|
290
|
-
{
|
|
291
|
-
position: "absolute",
|
|
292
|
-
zIndex: 9999,
|
|
293
|
-
elevation: 9999,
|
|
294
|
-
left: offset?.left ?? safeAreaInsets.left + 12,
|
|
295
|
-
right: offset?.right ?? safeAreaInsets.right + 12,
|
|
296
|
-
},
|
|
297
|
-
keyboardStyle,
|
|
298
|
-
]}
|
|
299
|
-
>
|
|
300
|
-
{toasts.map((toast, index) => (
|
|
301
|
-
<ToastAnimatedItem
|
|
302
|
-
key={toast.id}
|
|
303
|
-
heights={heights}
|
|
304
|
-
id={toast.id}
|
|
305
|
-
index={index}
|
|
306
|
-
maxVisibleToasts={maxVisibleToasts}
|
|
307
|
-
placement={placement}
|
|
308
|
-
total={total}
|
|
309
|
-
>
|
|
310
|
-
{toast.component({
|
|
311
|
-
id: toast.id,
|
|
312
|
-
index,
|
|
313
|
-
hide: manager.hide,
|
|
314
|
-
show: manager.show,
|
|
315
|
-
})}
|
|
316
|
-
</ToastAnimatedItem>
|
|
317
|
-
))}
|
|
318
|
-
</Animated.View>
|
|
319
|
-
);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
41
|
export function useToast(): ToastManager {
|
|
323
42
|
const manager = useContext(ToastManagerContext);
|
|
324
43
|
if (!manager) {
|