@oxyhq/bloom 0.64.0 → 0.66.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/lib/commonjs/connection-status/index.js +40 -0
- package/lib/commonjs/connection-status/index.js.map +1 -0
- package/lib/commonjs/connection-status/index.web.js +36 -0
- package/lib/commonjs/connection-status/index.web.js.map +1 -0
- package/lib/commonjs/connection-status/shared.js +68 -0
- package/lib/commonjs/connection-status/shared.js.map +1 -0
- package/lib/commonjs/toast/Positioner.js +9 -6
- package/lib/commonjs/toast/Positioner.js.map +1 -1
- package/lib/commonjs/toast/ToastHost.js +1 -0
- package/lib/commonjs/toast/ToastHost.js.map +1 -1
- package/lib/module/connection-status/index.js +34 -0
- package/lib/module/connection-status/index.js.map +1 -0
- package/lib/module/connection-status/index.web.js +31 -0
- package/lib/module/connection-status/index.web.js.map +1 -0
- package/lib/module/connection-status/shared.js +65 -0
- package/lib/module/connection-status/shared.js.map +1 -0
- package/lib/module/toast/Positioner.js +9 -6
- package/lib/module/toast/Positioner.js.map +1 -1
- package/lib/module/toast/ToastHost.js +1 -0
- package/lib/module/toast/ToastHost.js.map +1 -1
- package/lib/typescript/commonjs/connection-status/index.d.ts +8 -0
- package/lib/typescript/commonjs/connection-status/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/connection-status/index.web.d.ts +8 -0
- package/lib/typescript/commonjs/connection-status/index.web.d.ts.map +1 -0
- package/lib/typescript/commonjs/connection-status/shared.d.ts +24 -0
- package/lib/typescript/commonjs/connection-status/shared.d.ts.map +1 -0
- package/lib/typescript/commonjs/toast/Positioner.d.ts +6 -2
- package/lib/typescript/commonjs/toast/Positioner.d.ts.map +1 -1
- package/lib/typescript/commonjs/toast/ToastHost.d.ts.map +1 -1
- package/lib/typescript/module/connection-status/index.d.ts +8 -0
- package/lib/typescript/module/connection-status/index.d.ts.map +1 -0
- package/lib/typescript/module/connection-status/index.web.d.ts +8 -0
- package/lib/typescript/module/connection-status/index.web.d.ts.map +1 -0
- package/lib/typescript/module/connection-status/shared.d.ts +24 -0
- package/lib/typescript/module/connection-status/shared.d.ts.map +1 -0
- package/lib/typescript/module/toast/Positioner.d.ts +6 -2
- package/lib/typescript/module/toast/Positioner.d.ts.map +1 -1
- package/lib/typescript/module/toast/ToastHost.d.ts.map +1 -1
- package/package.json +24 -3
- package/src/__tests__/ToastHostWebFork.test.ts +9 -3
- package/src/__tests__/overlay-pointer-events.test.tsx +45 -0
- package/src/__tests__/pointer-events-style-form.test.ts +20 -8
- package/src/connection-status/index.tsx +38 -0
- package/src/connection-status/index.web.tsx +36 -0
- package/src/connection-status/shared.ts +83 -0
- package/src/toast/Positioner.tsx +12 -11
- package/src/toast/ToastHost.tsx +4 -1
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.ConnectionStatusToasts = ConnectionStatusToasts;
|
|
7
|
+
var _react = require("react");
|
|
8
|
+
var _netinfo = _interopRequireDefault(require("@react-native-community/netinfo"));
|
|
9
|
+
var _shared = require("./shared.js");
|
|
10
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
|
+
/**
|
|
12
|
+
* Connection loss as a toast, not a layout-shifting banner.
|
|
13
|
+
*
|
|
14
|
+
* Every Oxy app needs this and none of them should hand-roll it: a coloured bar
|
|
15
|
+
* sliding in above the header pushes the whole screen down, has to be themed
|
|
16
|
+
* again in each app, and competes with the app's own chrome. The toast host is
|
|
17
|
+
* already mounted, already themed, already stacks.
|
|
18
|
+
*
|
|
19
|
+
* NATIVE variant: `@react-native-community/netinfo` (an Expo-managed dependency
|
|
20
|
+
* every Oxy app already ships) reports both link state and reachability. The
|
|
21
|
+
* web fork uses `navigator.onLine` instead so Vite/SPA consumers need no extra
|
|
22
|
+
* dependency at all.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** Mount once at the app root. Renders nothing. */
|
|
26
|
+
function ConnectionStatusToasts(props = {}) {
|
|
27
|
+
const [isOnline, setIsOnline] = (0, _react.useState)(null);
|
|
28
|
+
(0, _react.useEffect)(() => {
|
|
29
|
+
// `isInternetReachable` is null until the first probe resolves; treat that
|
|
30
|
+
// as "connected as far as we know" so a captive-portal check never flashes
|
|
31
|
+
// an outage on launch.
|
|
32
|
+
return _netinfo.default.addEventListener(state => {
|
|
33
|
+
setIsOnline(Boolean(state.isConnected && state.isInternetReachable !== false));
|
|
34
|
+
});
|
|
35
|
+
}, []);
|
|
36
|
+
(0, _shared.useConnectionStatusToasts)(isOnline, props);
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
ConnectionStatusToasts.displayName = 'ConnectionStatusToasts';
|
|
40
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_react","require","_netinfo","_interopRequireDefault","_shared","e","__esModule","default","ConnectionStatusToasts","props","isOnline","setIsOnline","useState","useEffect","NetInfo","addEventListener","state","Boolean","isConnected","isInternetReachable","useConnectionStatusToasts","displayName"],"sourceRoot":"../../../src","sources":["connection-status/index.tsx"],"mappings":";;;;;;AAaA,IAAAA,MAAA,GAAAC,OAAA;AACA,IAAAC,QAAA,GAAAC,sBAAA,CAAAF,OAAA;AAEA,IAAAG,OAAA,GAAAH,OAAA;AAAuF,SAAAE,uBAAAE,CAAA,WAAAA,CAAA,IAAAA,CAAA,CAAAC,UAAA,GAAAD,CAAA,KAAAE,OAAA,EAAAF,CAAA;AAhBvF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAQA;AACO,SAASG,sBAAsBA,CAACC,KAAkC,GAAG,CAAC,CAAC,EAAQ;EACpF,MAAM,CAACC,QAAQ,EAAEC,WAAW,CAAC,GAAG,IAAAC,eAAQ,EAAiB,IAAI,CAAC;EAE9D,IAAAC,gBAAS,EAAC,MAAM;IACd;IACA;IACA;IACA,OAAOC,gBAAO,CAACC,gBAAgB,CAAEC,KAAK,IAAK;MACzCL,WAAW,CAACM,OAAO,CAACD,KAAK,CAACE,WAAW,IAAIF,KAAK,CAACG,mBAAmB,KAAK,KAAK,CAAC,CAAC;IAChF,CAAC,CAAC;EACJ,CAAC,EAAE,EAAE,CAAC;EAEN,IAAAC,iCAAyB,EAACV,QAAQ,EAAED,KAAK,CAAC;EAC1C,OAAO,IAAI;AACb;AAEAD,sBAAsB,CAACa,WAAW,GAAG,wBAAwB","ignoreList":[]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.ConnectionStatusToasts = ConnectionStatusToasts;
|
|
7
|
+
var _react = require("react");
|
|
8
|
+
var _shared = require("./shared.js");
|
|
9
|
+
/**
|
|
10
|
+
* Web variant — see `./index.tsx` for what this is and why it is a toast.
|
|
11
|
+
*
|
|
12
|
+
* `navigator.onLine` + the `online`/`offline` events are the browser's own
|
|
13
|
+
* signal, so a Vite/SPA consumer needs no native dependency to get the same
|
|
14
|
+
* behaviour. (The browser only knows about the link, not reachability; that is
|
|
15
|
+
* the honest limit of what a web app can detect without polling.)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** Mount once at the app root. Renders nothing. */
|
|
19
|
+
function ConnectionStatusToasts(props = {}) {
|
|
20
|
+
const [isOnline, setIsOnline] = (0, _react.useState)(null);
|
|
21
|
+
(0, _react.useEffect)(() => {
|
|
22
|
+
if (typeof navigator === 'undefined' || typeof window === 'undefined') return undefined;
|
|
23
|
+
setIsOnline(navigator.onLine);
|
|
24
|
+
const update = () => setIsOnline(navigator.onLine);
|
|
25
|
+
window.addEventListener('online', update);
|
|
26
|
+
window.addEventListener('offline', update);
|
|
27
|
+
return () => {
|
|
28
|
+
window.removeEventListener('online', update);
|
|
29
|
+
window.removeEventListener('offline', update);
|
|
30
|
+
};
|
|
31
|
+
}, []);
|
|
32
|
+
(0, _shared.useConnectionStatusToasts)(isOnline, props);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
ConnectionStatusToasts.displayName = 'ConnectionStatusToasts';
|
|
36
|
+
//# sourceMappingURL=index.web.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_react","require","_shared","ConnectionStatusToasts","props","isOnline","setIsOnline","useState","useEffect","navigator","window","undefined","onLine","update","addEventListener","removeEventListener","useConnectionStatusToasts","displayName"],"sourceRoot":"../../../src","sources":["connection-status/index.web.tsx"],"mappings":";;;;;;AAQA,IAAAA,MAAA,GAAAC,OAAA;AAEA,IAAAC,OAAA,GAAAD,OAAA;AAVA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAOA;AACO,SAASE,sBAAsBA,CAACC,KAAkC,GAAG,CAAC,CAAC,EAAQ;EACpF,MAAM,CAACC,QAAQ,EAAEC,WAAW,CAAC,GAAG,IAAAC,eAAQ,EAAiB,IAAI,CAAC;EAE9D,IAAAC,gBAAS,EAAC,MAAM;IACd,IAAI,OAAOC,SAAS,KAAK,WAAW,IAAI,OAAOC,MAAM,KAAK,WAAW,EAAE,OAAOC,SAAS;IAEvFL,WAAW,CAACG,SAAS,CAACG,MAAM,CAAC;IAC7B,MAAMC,MAAM,GAAGA,CAAA,KAAMP,WAAW,CAACG,SAAS,CAACG,MAAM,CAAC;IAClDF,MAAM,CAACI,gBAAgB,CAAC,QAAQ,EAAED,MAAM,CAAC;IACzCH,MAAM,CAACI,gBAAgB,CAAC,SAAS,EAAED,MAAM,CAAC;IAC1C,OAAO,MAAM;MACXH,MAAM,CAACK,mBAAmB,CAAC,QAAQ,EAAEF,MAAM,CAAC;MAC5CH,MAAM,CAACK,mBAAmB,CAAC,SAAS,EAAEF,MAAM,CAAC;IAC/C,CAAC;EACH,CAAC,EAAE,EAAE,CAAC;EAEN,IAAAG,iCAAyB,EAACX,QAAQ,EAAED,KAAK,CAAC;EAC1C,OAAO,IAAI;AACb;AAEAD,sBAAsB,CAACc,WAAW,GAAG,wBAAwB","ignoreList":[]}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.useConnectionStatusToasts = useConnectionStatusToasts;
|
|
7
|
+
var _react = require("react");
|
|
8
|
+
var _index = require("../toast/index.js");
|
|
9
|
+
/** One id for the whole lifecycle, so the toast updates in place. */
|
|
10
|
+
const TOAST_ID = 'bloom-connection-status';
|
|
11
|
+
const DEFAULTS = {
|
|
12
|
+
offlineMessage: 'No internet connection',
|
|
13
|
+
reconnectingMessage: 'Reconnecting…',
|
|
14
|
+
restoredMessage: 'Back online',
|
|
15
|
+
reconnectingDelayMs: 3000,
|
|
16
|
+
restoredDurationMs: 2500
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Drive the connection toast from an `isOnline` signal.
|
|
21
|
+
*
|
|
22
|
+
* Platform-agnostic half of `ConnectionStatusToasts`: the forks differ only in
|
|
23
|
+
* where the signal comes from (NetInfo on native, `navigator.onLine` on web),
|
|
24
|
+
* never in what the user sees.
|
|
25
|
+
*
|
|
26
|
+
* `isOnline === null` means "not determined yet" and shows nothing — a first
|
|
27
|
+
* paint must not accuse the user of being offline.
|
|
28
|
+
*/
|
|
29
|
+
function useConnectionStatusToasts(isOnline, {
|
|
30
|
+
offlineMessage = DEFAULTS.offlineMessage,
|
|
31
|
+
reconnectingMessage = DEFAULTS.reconnectingMessage,
|
|
32
|
+
restoredMessage = DEFAULTS.restoredMessage,
|
|
33
|
+
reconnectingDelayMs = DEFAULTS.reconnectingDelayMs,
|
|
34
|
+
restoredDurationMs = DEFAULTS.restoredDurationMs
|
|
35
|
+
} = {}) {
|
|
36
|
+
// Whether an outage toast is currently up, so coming back online only
|
|
37
|
+
// announces itself to someone who saw the outage.
|
|
38
|
+
const wasOffline = (0, _react.useRef)(false);
|
|
39
|
+
(0, _react.useEffect)(() => {
|
|
40
|
+
if (isOnline === null) return undefined;
|
|
41
|
+
if (!isOnline) {
|
|
42
|
+
wasOffline.current = true;
|
|
43
|
+
// Persistent: an outage ends when the network says so, not on a timer.
|
|
44
|
+
_index.toast.error(offlineMessage, {
|
|
45
|
+
id: TOAST_ID,
|
|
46
|
+
duration: Infinity,
|
|
47
|
+
dismissible: false
|
|
48
|
+
});
|
|
49
|
+
const escalate = setTimeout(() => {
|
|
50
|
+
_index.toast.warning(reconnectingMessage, {
|
|
51
|
+
id: TOAST_ID,
|
|
52
|
+
duration: Infinity,
|
|
53
|
+
dismissible: false
|
|
54
|
+
});
|
|
55
|
+
}, reconnectingDelayMs);
|
|
56
|
+
return () => clearTimeout(escalate);
|
|
57
|
+
}
|
|
58
|
+
if (wasOffline.current) {
|
|
59
|
+
wasOffline.current = false;
|
|
60
|
+
_index.toast.success(restoredMessage, {
|
|
61
|
+
id: TOAST_ID,
|
|
62
|
+
duration: restoredDurationMs
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return undefined;
|
|
66
|
+
}, [isOnline, offlineMessage, reconnectingMessage, restoredMessage, reconnectingDelayMs, restoredDurationMs]);
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=shared.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_react","require","_index","TOAST_ID","DEFAULTS","offlineMessage","reconnectingMessage","restoredMessage","reconnectingDelayMs","restoredDurationMs","useConnectionStatusToasts","isOnline","wasOffline","useRef","useEffect","undefined","current","toast","error","id","duration","Infinity","dismissible","escalate","setTimeout","warning","clearTimeout","success"],"sourceRoot":"../../../src","sources":["connection-status/shared.ts"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,OAAA;AAEA,IAAAC,MAAA,GAAAD,OAAA;AAEA;AACA,MAAME,QAAQ,GAAG,yBAAyB;AAe1C,MAAMC,QAAQ,GAAG;EACfC,cAAc,EAAE,wBAAwB;EACxCC,mBAAmB,EAAE,eAAe;EACpCC,eAAe,EAAE,aAAa;EAC9BC,mBAAmB,EAAE,IAAI;EACzBC,kBAAkB,EAAE;AACtB,CAAU;;AAEV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASC,yBAAyBA,CACvCC,QAAwB,EACxB;EACEN,cAAc,GAAGD,QAAQ,CAACC,cAAc;EACxCC,mBAAmB,GAAGF,QAAQ,CAACE,mBAAmB;EAClDC,eAAe,GAAGH,QAAQ,CAACG,eAAe;EAC1CC,mBAAmB,GAAGJ,QAAQ,CAACI,mBAAmB;EAClDC,kBAAkB,GAAGL,QAAQ,CAACK;AACH,CAAC,GAAG,CAAC,CAAC,EAC7B;EACN;EACA;EACA,MAAMG,UAAU,GAAG,IAAAC,aAAM,EAAC,KAAK,CAAC;EAEhC,IAAAC,gBAAS,EAAC,MAAM;IACd,IAAIH,QAAQ,KAAK,IAAI,EAAE,OAAOI,SAAS;IAEvC,IAAI,CAACJ,QAAQ,EAAE;MACbC,UAAU,CAACI,OAAO,GAAG,IAAI;MACzB;MACAC,YAAK,CAACC,KAAK,CAACb,cAAc,EAAE;QAAEc,EAAE,EAAEhB,QAAQ;QAAEiB,QAAQ,EAAEC,QAAQ;QAAEC,WAAW,EAAE;MAAM,CAAC,CAAC;MACrF,MAAMC,QAAQ,GAAGC,UAAU,CAAC,MAAM;QAChCP,YAAK,CAACQ,OAAO,CAACnB,mBAAmB,EAAE;UACjCa,EAAE,EAAEhB,QAAQ;UACZiB,QAAQ,EAAEC,QAAQ;UAClBC,WAAW,EAAE;QACf,CAAC,CAAC;MACJ,CAAC,EAAEd,mBAAmB,CAAC;MACvB,OAAO,MAAMkB,YAAY,CAACH,QAAQ,CAAC;IACrC;IAEA,IAAIX,UAAU,CAACI,OAAO,EAAE;MACtBJ,UAAU,CAACI,OAAO,GAAG,KAAK;MAC1BC,YAAK,CAACU,OAAO,CAACpB,eAAe,EAAE;QAAEY,EAAE,EAAEhB,QAAQ;QAAEiB,QAAQ,EAAEX;MAAmB,CAAC,CAAC;IAChF;IACA,OAAOM,SAAS;EAClB,CAAC,EAAE,CACDJ,QAAQ,EACRN,cAAc,EACdC,mBAAmB,EACnBC,eAAe,EACfC,mBAAmB,EACnBC,kBAAkB,CACnB,CAAC;AACJ","ignoreList":[]}
|
|
@@ -25,8 +25,12 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
|
|
|
25
25
|
* universal: `ToastHost` supplies a viewport-sized containing block on web, so
|
|
26
26
|
* there is no `.web` fork and no `position: 'fixed'` here (W7).
|
|
27
27
|
*
|
|
28
|
-
* `pointerEvents` goes
|
|
29
|
-
*
|
|
28
|
+
* `pointerEvents` goes on the PROP, never in `style`: react-native-web resolves
|
|
29
|
+
* the RN-only `box-none` value only from the prop path, so as a style entry it
|
|
30
|
+
* is silently dropped and this full-host container inherits `auto` — a
|
|
31
|
+
* transparent sheet over the whole app that swallows every tap for as long as a
|
|
32
|
+
* toast is up. RNW logs a deprecation notice for the prop once per session;
|
|
33
|
+
* that is the cheaper of the two.
|
|
30
34
|
*/
|
|
31
35
|
|
|
32
36
|
/**
|
|
@@ -99,15 +103,14 @@ const Positioner = ({
|
|
|
99
103
|
style: [(0, _positionerUtils.getContainerStyle)(), androidElevationStyle,
|
|
100
104
|
// Overrides ONE edge of the four `getContainerStyle` pins, so the box
|
|
101
105
|
// keeps a real height and the rows inside it stay in bounds.
|
|
102
|
-
insetValues,
|
|
106
|
+
insetValues, style]
|
|
103
107
|
// This container spans the whole host, so it must never swallow a touch.
|
|
104
108
|
// `box-none` passes presses through to the app while still reaching the
|
|
105
109
|
// rows; an EMPTY one on Android drops to `none` outright, because the
|
|
106
110
|
// outlet's configured position always renders a container even with no
|
|
107
111
|
// rows in it (see `Toaster`'s grouping).
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}, style],
|
|
112
|
+
,
|
|
113
|
+
pointerEvents: _reactNative.Platform.OS === 'android' && !hasChildren ? 'none' : 'box-none',
|
|
111
114
|
children: children
|
|
112
115
|
})]
|
|
113
116
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["React","_interopRequireWildcard","require","_reactNative","_reactNativeSafeAreaContext","_context","_positionerUtils","_jsxRuntime","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","FALLBACK_INSETS","initialWindowMetrics","insets","top","bottom","left","right","ANDROID_ELEVATION","androidElevationStyle","Platform","OS","elevation","Positioner","children","position","style","offset","gap","visibleToasts","useToastContext","isExpanded","collapse","toastHeights","useDynamicToastContext","useContext","SafeAreaInsetsContext","resolvedPosition","insetValues","getInsetValues","safeAreaInsets","handleOutsidePress","useCallback","shouldAllowCollapse","hasChildren","Children","count","jsxs","Fragment","jsx","Pressable","calculateOutsidePressableArea","onPress","View","getContainerStyle","pointerEvents","exports","displayName"],"sourceRoot":"../../../src","sources":["toast/Positioner.tsx"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"names":["React","_interopRequireWildcard","require","_reactNative","_reactNativeSafeAreaContext","_context","_positionerUtils","_jsxRuntime","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","FALLBACK_INSETS","initialWindowMetrics","insets","top","bottom","left","right","ANDROID_ELEVATION","androidElevationStyle","Platform","OS","elevation","Positioner","children","position","style","offset","gap","visibleToasts","useToastContext","isExpanded","collapse","toastHeights","useDynamicToastContext","useContext","SafeAreaInsetsContext","resolvedPosition","insetValues","getInsetValues","safeAreaInsets","handleOutsidePress","useCallback","shouldAllowCollapse","hasChildren","Children","count","jsxs","Fragment","jsx","Pressable","calculateOutsidePressableArea","onPress","View","getContainerStyle","pointerEvents","exports","displayName"],"sourceRoot":"../../../src","sources":["toast/Positioner.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -50,6 +50,7 @@ function ToastHost({
|
|
|
50
50
|
(0, _jsxRuntime.jsx)(_index.OverlayRoot, {
|
|
51
51
|
style: styles.host,
|
|
52
52
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeGestureHandler.GestureHandlerRootView, {
|
|
53
|
+
pointerEvents: "box-none",
|
|
53
54
|
style: _reactNative.StyleSheet.absoluteFill,
|
|
54
55
|
children: children
|
|
55
56
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["React","_interopRequireWildcard","require","_reactNative","_reactNativeGestureHandler","_index","_indexWeb","_webViewStyle","_zIndex","_jsxRuntime","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","ToastHost","children","ToasterOverlayWrapper","content","jsx","OverlayRoot","style","styles","host","GestureHandlerRootView","StyleSheet","absoluteFill","Portal","displayName","create","position","WEB_POSITION_FIXED","top","left","right","bottom","zIndex","Z_INDEX","toast"],"sourceRoot":"../../../src","sources":["toast/ToastHost.tsx"],"mappings":";;;;;;AAwBA,IAAAA,KAAA,GAAAC,uBAAA,CAAAC,OAAA;AACA,IAAAC,YAAA,GAAAD,OAAA;AACA,IAAAE,0BAAA,GAAAF,OAAA;AAEA,IAAAG,MAAA,GAAAH,OAAA;AACA,IAAAI,SAAA,GAAAJ,OAAA;AACA,IAAAK,aAAA,GAAAL,OAAA;AACA,IAAAM,OAAA,GAAAN,OAAA;AAA4C,IAAAO,WAAA,GAAAP,OAAA;AAAA,SAAAD,wBAAAS,CAAA,EAAAC,CAAA,6BAAAC,OAAA,MAAAC,CAAA,OAAAD,OAAA,IAAAE,CAAA,OAAAF,OAAA,YAAAX,uBAAA,YAAAA,CAAAS,CAAA,EAAAC,CAAA,SAAAA,CAAA,IAAAD,CAAA,IAAAA,CAAA,CAAAK,UAAA,SAAAL,CAAA,MAAAM,CAAA,EAAAC,CAAA,EAAAC,CAAA,KAAAC,SAAA,QAAAC,OAAA,EAAAV,CAAA,iBAAAA,CAAA,uBAAAA,CAAA,yBAAAA,CAAA,SAAAQ,CAAA,MAAAF,CAAA,GAAAL,CAAA,GAAAG,CAAA,GAAAD,CAAA,QAAAG,CAAA,CAAAK,GAAA,CAAAX,CAAA,UAAAM,CAAA,CAAAM,GAAA,CAAAZ,CAAA,GAAAM,CAAA,CAAAO,GAAA,CAAAb,CAAA,EAAAQ,CAAA,gBAAAP,CAAA,IAAAD,CAAA,gBAAAC,CAAA,OAAAa,cAAA,CAAAC,IAAA,CAAAf,CAAA,EAAAC,CAAA,OAAAM,CAAA,IAAAD,CAAA,GAAAU,MAAA,CAAAC,cAAA,KAAAD,MAAA,CAAAE,wBAAA,CAAAlB,CAAA,EAAAC,CAAA,OAAAM,CAAA,CAAAK,GAAA,IAAAL,CAAA,CAAAM,GAAA,IAAAP,CAAA,CAAAE,CAAA,EAAAP,CAAA,EAAAM,CAAA,IAAAC,CAAA,CAAAP,CAAA,IAAAD,CAAA,CAAAC,CAAA,WAAAO,CAAA,KAAAR,CAAA,EAAAC,CAAA;AA/B5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAWO,SAASkB,SAASA,CAAC;EAAEC,QAAQ;EAAEC;AAAsC,CAAC,EAAE;EAC7E,MAAMC,OAAO;EAAA;EACX;EACA;EACA;EACA,IAAAvB,WAAA,CAAAwB,GAAA,EAAC5B,MAAA,CAAA6B,WAAW;IAACC,KAAK,EAAEC,MAAM,CAACC,IAAK;IAAAP,QAAA,
|
|
1
|
+
{"version":3,"names":["React","_interopRequireWildcard","require","_reactNative","_reactNativeGestureHandler","_index","_indexWeb","_webViewStyle","_zIndex","_jsxRuntime","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","ToastHost","children","ToasterOverlayWrapper","content","jsx","OverlayRoot","style","styles","host","GestureHandlerRootView","pointerEvents","StyleSheet","absoluteFill","Portal","displayName","create","position","WEB_POSITION_FIXED","top","left","right","bottom","zIndex","Z_INDEX","toast"],"sourceRoot":"../../../src","sources":["toast/ToastHost.tsx"],"mappings":";;;;;;AAwBA,IAAAA,KAAA,GAAAC,uBAAA,CAAAC,OAAA;AACA,IAAAC,YAAA,GAAAD,OAAA;AACA,IAAAE,0BAAA,GAAAF,OAAA;AAEA,IAAAG,MAAA,GAAAH,OAAA;AACA,IAAAI,SAAA,GAAAJ,OAAA;AACA,IAAAK,aAAA,GAAAL,OAAA;AACA,IAAAM,OAAA,GAAAN,OAAA;AAA4C,IAAAO,WAAA,GAAAP,OAAA;AAAA,SAAAD,wBAAAS,CAAA,EAAAC,CAAA,6BAAAC,OAAA,MAAAC,CAAA,OAAAD,OAAA,IAAAE,CAAA,OAAAF,OAAA,YAAAX,uBAAA,YAAAA,CAAAS,CAAA,EAAAC,CAAA,SAAAA,CAAA,IAAAD,CAAA,IAAAA,CAAA,CAAAK,UAAA,SAAAL,CAAA,MAAAM,CAAA,EAAAC,CAAA,EAAAC,CAAA,KAAAC,SAAA,QAAAC,OAAA,EAAAV,CAAA,iBAAAA,CAAA,uBAAAA,CAAA,yBAAAA,CAAA,SAAAQ,CAAA,MAAAF,CAAA,GAAAL,CAAA,GAAAG,CAAA,GAAAD,CAAA,QAAAG,CAAA,CAAAK,GAAA,CAAAX,CAAA,UAAAM,CAAA,CAAAM,GAAA,CAAAZ,CAAA,GAAAM,CAAA,CAAAO,GAAA,CAAAb,CAAA,EAAAQ,CAAA,gBAAAP,CAAA,IAAAD,CAAA,gBAAAC,CAAA,OAAAa,cAAA,CAAAC,IAAA,CAAAf,CAAA,EAAAC,CAAA,OAAAM,CAAA,IAAAD,CAAA,GAAAU,MAAA,CAAAC,cAAA,KAAAD,MAAA,CAAAE,wBAAA,CAAAlB,CAAA,EAAAC,CAAA,OAAAM,CAAA,CAAAK,GAAA,IAAAL,CAAA,CAAAM,GAAA,IAAAP,CAAA,CAAAE,CAAA,EAAAP,CAAA,EAAAM,CAAA,IAAAC,CAAA,CAAAP,CAAA,IAAAD,CAAA,CAAAC,CAAA,WAAAO,CAAA,KAAAR,CAAA,EAAAC,CAAA;AA/B5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAWO,SAASkB,SAASA,CAAC;EAAEC,QAAQ;EAAEC;AAAsC,CAAC,EAAE;EAC7E,MAAMC,OAAO;EAAA;EACX;EACA;EACA;EACA,IAAAvB,WAAA,CAAAwB,GAAA,EAAC5B,MAAA,CAAA6B,WAAW;IAACC,KAAK,EAAEC,MAAM,CAACC,IAAK;IAAAP,QAAA,eAI9B,IAAArB,WAAA,CAAAwB,GAAA,EAAC7B,0BAAA,CAAAkC,sBAAsB;MAACC,aAAa,EAAC,UAAU;MAACJ,KAAK,EAAEK,uBAAU,CAACC,YAAa;MAAAX,QAAA,EAC7EA;IAAQ,CACa;EAAC,CACd,CACd;EAED,oBACE,IAAArB,WAAA,CAAAwB,GAAA,EAAC3B,SAAA,CAAAoC,MAAM;IAAAZ,QAAA,EACJC,qBAAqB,gBACpB,IAAAtB,WAAA,CAAAwB,GAAA,EAACF,qBAAqB;MAAAD,QAAA,EAAEE;IAAO,CAAwB,CAAC,GAExDA;EACD,CACK,CAAC;AAEb;AAEAH,SAAS,CAACc,WAAW,GAAG,WAAW;AAEnC,MAAMP,MAAM,GAAGI,uBAAU,CAACI,MAAM,CAAC;EAC/BP,IAAI,EAAE;IACJ;IACAQ,QAAQ,EAAEC,gCAAkB;IAC5BC,GAAG,EAAE,CAAC;IACNC,IAAI,EAAE,CAAC;IACPC,KAAK,EAAE,CAAC;IACRC,MAAM,EAAE,CAAC;IACT;IACA;IACAC,MAAM,EAAEC,eAAO,CAACC;EAClB;AACF,CAAC,CAAC","ignoreList":[]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Connection loss as a toast, not a layout-shifting banner.
|
|
5
|
+
*
|
|
6
|
+
* Every Oxy app needs this and none of them should hand-roll it: a coloured bar
|
|
7
|
+
* sliding in above the header pushes the whole screen down, has to be themed
|
|
8
|
+
* again in each app, and competes with the app's own chrome. The toast host is
|
|
9
|
+
* already mounted, already themed, already stacks.
|
|
10
|
+
*
|
|
11
|
+
* NATIVE variant: `@react-native-community/netinfo` (an Expo-managed dependency
|
|
12
|
+
* every Oxy app already ships) reports both link state and reachability. The
|
|
13
|
+
* web fork uses `navigator.onLine` instead so Vite/SPA consumers need no extra
|
|
14
|
+
* dependency at all.
|
|
15
|
+
*/
|
|
16
|
+
import { useEffect, useState } from 'react';
|
|
17
|
+
import NetInfo from '@react-native-community/netinfo';
|
|
18
|
+
import { useConnectionStatusToasts } from "./shared.js";
|
|
19
|
+
/** Mount once at the app root. Renders nothing. */
|
|
20
|
+
export function ConnectionStatusToasts(props = {}) {
|
|
21
|
+
const [isOnline, setIsOnline] = useState(null);
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
// `isInternetReachable` is null until the first probe resolves; treat that
|
|
24
|
+
// as "connected as far as we know" so a captive-portal check never flashes
|
|
25
|
+
// an outage on launch.
|
|
26
|
+
return NetInfo.addEventListener(state => {
|
|
27
|
+
setIsOnline(Boolean(state.isConnected && state.isInternetReachable !== false));
|
|
28
|
+
});
|
|
29
|
+
}, []);
|
|
30
|
+
useConnectionStatusToasts(isOnline, props);
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
ConnectionStatusToasts.displayName = 'ConnectionStatusToasts';
|
|
34
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["useEffect","useState","NetInfo","useConnectionStatusToasts","ConnectionStatusToasts","props","isOnline","setIsOnline","addEventListener","state","Boolean","isConnected","isInternetReachable","displayName"],"sourceRoot":"../../../src","sources":["connection-status/index.tsx"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASA,SAAS,EAAEC,QAAQ,QAAQ,OAAO;AAC3C,OAAOC,OAAO,MAAM,iCAAiC;AAErD,SAASC,yBAAyB,QAA0C,aAAU;AAItF;AACA,OAAO,SAASC,sBAAsBA,CAACC,KAAkC,GAAG,CAAC,CAAC,EAAQ;EACpF,MAAM,CAACC,QAAQ,EAAEC,WAAW,CAAC,GAAGN,QAAQ,CAAiB,IAAI,CAAC;EAE9DD,SAAS,CAAC,MAAM;IACd;IACA;IACA;IACA,OAAOE,OAAO,CAACM,gBAAgB,CAAEC,KAAK,IAAK;MACzCF,WAAW,CAACG,OAAO,CAACD,KAAK,CAACE,WAAW,IAAIF,KAAK,CAACG,mBAAmB,KAAK,KAAK,CAAC,CAAC;IAChF,CAAC,CAAC;EACJ,CAAC,EAAE,EAAE,CAAC;EAENT,yBAAyB,CAACG,QAAQ,EAAED,KAAK,CAAC;EAC1C,OAAO,IAAI;AACb;AAEAD,sBAAsB,CAACS,WAAW,GAAG,wBAAwB","ignoreList":[]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Web variant — see `./index.tsx` for what this is and why it is a toast.
|
|
5
|
+
*
|
|
6
|
+
* `navigator.onLine` + the `online`/`offline` events are the browser's own
|
|
7
|
+
* signal, so a Vite/SPA consumer needs no native dependency to get the same
|
|
8
|
+
* behaviour. (The browser only knows about the link, not reachability; that is
|
|
9
|
+
* the honest limit of what a web app can detect without polling.)
|
|
10
|
+
*/
|
|
11
|
+
import { useEffect, useState } from 'react';
|
|
12
|
+
import { useConnectionStatusToasts } from "./shared.js";
|
|
13
|
+
/** Mount once at the app root. Renders nothing. */
|
|
14
|
+
export function ConnectionStatusToasts(props = {}) {
|
|
15
|
+
const [isOnline, setIsOnline] = useState(null);
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (typeof navigator === 'undefined' || typeof window === 'undefined') return undefined;
|
|
18
|
+
setIsOnline(navigator.onLine);
|
|
19
|
+
const update = () => setIsOnline(navigator.onLine);
|
|
20
|
+
window.addEventListener('online', update);
|
|
21
|
+
window.addEventListener('offline', update);
|
|
22
|
+
return () => {
|
|
23
|
+
window.removeEventListener('online', update);
|
|
24
|
+
window.removeEventListener('offline', update);
|
|
25
|
+
};
|
|
26
|
+
}, []);
|
|
27
|
+
useConnectionStatusToasts(isOnline, props);
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
ConnectionStatusToasts.displayName = 'ConnectionStatusToasts';
|
|
31
|
+
//# sourceMappingURL=index.web.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["useEffect","useState","useConnectionStatusToasts","ConnectionStatusToasts","props","isOnline","setIsOnline","navigator","window","undefined","onLine","update","addEventListener","removeEventListener","displayName"],"sourceRoot":"../../../src","sources":["connection-status/index.web.tsx"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASA,SAAS,EAAEC,QAAQ,QAAQ,OAAO;AAE3C,SAASC,yBAAyB,QAA0C,aAAU;AAItF;AACA,OAAO,SAASC,sBAAsBA,CAACC,KAAkC,GAAG,CAAC,CAAC,EAAQ;EACpF,MAAM,CAACC,QAAQ,EAAEC,WAAW,CAAC,GAAGL,QAAQ,CAAiB,IAAI,CAAC;EAE9DD,SAAS,CAAC,MAAM;IACd,IAAI,OAAOO,SAAS,KAAK,WAAW,IAAI,OAAOC,MAAM,KAAK,WAAW,EAAE,OAAOC,SAAS;IAEvFH,WAAW,CAACC,SAAS,CAACG,MAAM,CAAC;IAC7B,MAAMC,MAAM,GAAGA,CAAA,KAAML,WAAW,CAACC,SAAS,CAACG,MAAM,CAAC;IAClDF,MAAM,CAACI,gBAAgB,CAAC,QAAQ,EAAED,MAAM,CAAC;IACzCH,MAAM,CAACI,gBAAgB,CAAC,SAAS,EAAED,MAAM,CAAC;IAC1C,OAAO,MAAM;MACXH,MAAM,CAACK,mBAAmB,CAAC,QAAQ,EAAEF,MAAM,CAAC;MAC5CH,MAAM,CAACK,mBAAmB,CAAC,SAAS,EAAEF,MAAM,CAAC;IAC/C,CAAC;EACH,CAAC,EAAE,EAAE,CAAC;EAENT,yBAAyB,CAACG,QAAQ,EAAED,KAAK,CAAC;EAC1C,OAAO,IAAI;AACb;AAEAD,sBAAsB,CAACW,WAAW,GAAG,wBAAwB","ignoreList":[]}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef } from 'react';
|
|
4
|
+
import { toast } from "../toast/index.js";
|
|
5
|
+
|
|
6
|
+
/** One id for the whole lifecycle, so the toast updates in place. */
|
|
7
|
+
const TOAST_ID = 'bloom-connection-status';
|
|
8
|
+
const DEFAULTS = {
|
|
9
|
+
offlineMessage: 'No internet connection',
|
|
10
|
+
reconnectingMessage: 'Reconnecting…',
|
|
11
|
+
restoredMessage: 'Back online',
|
|
12
|
+
reconnectingDelayMs: 3000,
|
|
13
|
+
restoredDurationMs: 2500
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Drive the connection toast from an `isOnline` signal.
|
|
18
|
+
*
|
|
19
|
+
* Platform-agnostic half of `ConnectionStatusToasts`: the forks differ only in
|
|
20
|
+
* where the signal comes from (NetInfo on native, `navigator.onLine` on web),
|
|
21
|
+
* never in what the user sees.
|
|
22
|
+
*
|
|
23
|
+
* `isOnline === null` means "not determined yet" and shows nothing — a first
|
|
24
|
+
* paint must not accuse the user of being offline.
|
|
25
|
+
*/
|
|
26
|
+
export function useConnectionStatusToasts(isOnline, {
|
|
27
|
+
offlineMessage = DEFAULTS.offlineMessage,
|
|
28
|
+
reconnectingMessage = DEFAULTS.reconnectingMessage,
|
|
29
|
+
restoredMessage = DEFAULTS.restoredMessage,
|
|
30
|
+
reconnectingDelayMs = DEFAULTS.reconnectingDelayMs,
|
|
31
|
+
restoredDurationMs = DEFAULTS.restoredDurationMs
|
|
32
|
+
} = {}) {
|
|
33
|
+
// Whether an outage toast is currently up, so coming back online only
|
|
34
|
+
// announces itself to someone who saw the outage.
|
|
35
|
+
const wasOffline = useRef(false);
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (isOnline === null) return undefined;
|
|
38
|
+
if (!isOnline) {
|
|
39
|
+
wasOffline.current = true;
|
|
40
|
+
// Persistent: an outage ends when the network says so, not on a timer.
|
|
41
|
+
toast.error(offlineMessage, {
|
|
42
|
+
id: TOAST_ID,
|
|
43
|
+
duration: Infinity,
|
|
44
|
+
dismissible: false
|
|
45
|
+
});
|
|
46
|
+
const escalate = setTimeout(() => {
|
|
47
|
+
toast.warning(reconnectingMessage, {
|
|
48
|
+
id: TOAST_ID,
|
|
49
|
+
duration: Infinity,
|
|
50
|
+
dismissible: false
|
|
51
|
+
});
|
|
52
|
+
}, reconnectingDelayMs);
|
|
53
|
+
return () => clearTimeout(escalate);
|
|
54
|
+
}
|
|
55
|
+
if (wasOffline.current) {
|
|
56
|
+
wasOffline.current = false;
|
|
57
|
+
toast.success(restoredMessage, {
|
|
58
|
+
id: TOAST_ID,
|
|
59
|
+
duration: restoredDurationMs
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return undefined;
|
|
63
|
+
}, [isOnline, offlineMessage, reconnectingMessage, restoredMessage, reconnectingDelayMs, restoredDurationMs]);
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=shared.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["useEffect","useRef","toast","TOAST_ID","DEFAULTS","offlineMessage","reconnectingMessage","restoredMessage","reconnectingDelayMs","restoredDurationMs","useConnectionStatusToasts","isOnline","wasOffline","undefined","current","error","id","duration","Infinity","dismissible","escalate","setTimeout","warning","clearTimeout","success"],"sourceRoot":"../../../src","sources":["connection-status/shared.ts"],"mappings":";;AAAA,SAASA,SAAS,EAAEC,MAAM,QAAQ,OAAO;AAEzC,SAASC,KAAK,QAAQ,mBAAU;;AAEhC;AACA,MAAMC,QAAQ,GAAG,yBAAyB;AAe1C,MAAMC,QAAQ,GAAG;EACfC,cAAc,EAAE,wBAAwB;EACxCC,mBAAmB,EAAE,eAAe;EACpCC,eAAe,EAAE,aAAa;EAC9BC,mBAAmB,EAAE,IAAI;EACzBC,kBAAkB,EAAE;AACtB,CAAU;;AAEV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,yBAAyBA,CACvCC,QAAwB,EACxB;EACEN,cAAc,GAAGD,QAAQ,CAACC,cAAc;EACxCC,mBAAmB,GAAGF,QAAQ,CAACE,mBAAmB;EAClDC,eAAe,GAAGH,QAAQ,CAACG,eAAe;EAC1CC,mBAAmB,GAAGJ,QAAQ,CAACI,mBAAmB;EAClDC,kBAAkB,GAAGL,QAAQ,CAACK;AACH,CAAC,GAAG,CAAC,CAAC,EAC7B;EACN;EACA;EACA,MAAMG,UAAU,GAAGX,MAAM,CAAC,KAAK,CAAC;EAEhCD,SAAS,CAAC,MAAM;IACd,IAAIW,QAAQ,KAAK,IAAI,EAAE,OAAOE,SAAS;IAEvC,IAAI,CAACF,QAAQ,EAAE;MACbC,UAAU,CAACE,OAAO,GAAG,IAAI;MACzB;MACAZ,KAAK,CAACa,KAAK,CAACV,cAAc,EAAE;QAAEW,EAAE,EAAEb,QAAQ;QAAEc,QAAQ,EAAEC,QAAQ;QAAEC,WAAW,EAAE;MAAM,CAAC,CAAC;MACrF,MAAMC,QAAQ,GAAGC,UAAU,CAAC,MAAM;QAChCnB,KAAK,CAACoB,OAAO,CAAChB,mBAAmB,EAAE;UACjCU,EAAE,EAAEb,QAAQ;UACZc,QAAQ,EAAEC,QAAQ;UAClBC,WAAW,EAAE;QACf,CAAC,CAAC;MACJ,CAAC,EAAEX,mBAAmB,CAAC;MACvB,OAAO,MAAMe,YAAY,CAACH,QAAQ,CAAC;IACrC;IAEA,IAAIR,UAAU,CAACE,OAAO,EAAE;MACtBF,UAAU,CAACE,OAAO,GAAG,KAAK;MAC1BZ,KAAK,CAACsB,OAAO,CAACjB,eAAe,EAAE;QAAES,EAAE,EAAEb,QAAQ;QAAEc,QAAQ,EAAER;MAAmB,CAAC,CAAC;IAChF;IACA,OAAOI,SAAS;EAClB,CAAC,EAAE,CACDF,QAAQ,EACRN,cAAc,EACdC,mBAAmB,EACnBC,eAAe,EACfC,mBAAmB,EACnBC,kBAAkB,CACnB,CAAC;AACJ","ignoreList":[]}
|
|
@@ -14,8 +14,12 @@
|
|
|
14
14
|
* universal: `ToastHost` supplies a viewport-sized containing block on web, so
|
|
15
15
|
* there is no `.web` fork and no `position: 'fixed'` here (W7).
|
|
16
16
|
*
|
|
17
|
-
* `pointerEvents` goes
|
|
18
|
-
*
|
|
17
|
+
* `pointerEvents` goes on the PROP, never in `style`: react-native-web resolves
|
|
18
|
+
* the RN-only `box-none` value only from the prop path, so as a style entry it
|
|
19
|
+
* is silently dropped and this full-host container inherits `auto` — a
|
|
20
|
+
* transparent sheet over the whole app that swallows every tap for as long as a
|
|
21
|
+
* toast is up. RNW logs a deprecation notice for the prop once per session;
|
|
22
|
+
* that is the cheaper of the two.
|
|
19
23
|
*/
|
|
20
24
|
import * as React from 'react';
|
|
21
25
|
import { Platform, Pressable, View } from 'react-native';
|
|
@@ -93,15 +97,14 @@ export const Positioner = ({
|
|
|
93
97
|
style: [getContainerStyle(), androidElevationStyle,
|
|
94
98
|
// Overrides ONE edge of the four `getContainerStyle` pins, so the box
|
|
95
99
|
// keeps a real height and the rows inside it stay in bounds.
|
|
96
|
-
insetValues,
|
|
100
|
+
insetValues, style]
|
|
97
101
|
// This container spans the whole host, so it must never swallow a touch.
|
|
98
102
|
// `box-none` passes presses through to the app while still reaching the
|
|
99
103
|
// rows; an EMPTY one on Android drops to `none` outright, because the
|
|
100
104
|
// outlet's configured position always renders a container even with no
|
|
101
105
|
// rows in it (see `Toaster`'s grouping).
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}, style],
|
|
106
|
+
,
|
|
107
|
+
pointerEvents: Platform.OS === 'android' && !hasChildren ? 'none' : 'box-none',
|
|
105
108
|
children: children
|
|
106
109
|
})]
|
|
107
110
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["React","Platform","Pressable","View","SafeAreaInsetsContext","initialWindowMetrics","useDynamicToastContext","useToastContext","calculateOutsidePressableArea","getContainerStyle","getInsetValues","jsx","_jsx","Fragment","_Fragment","jsxs","_jsxs","FALLBACK_INSETS","insets","top","bottom","left","right","ANDROID_ELEVATION","androidElevationStyle","OS","elevation","Positioner","children","position","style","offset","gap","visibleToasts","isExpanded","collapse","toastHeights","useContext","resolvedPosition","insetValues","safeAreaInsets","handleOutsidePress","useCallback","shouldAllowCollapse","hasChildren","Children","count","onPress","pointerEvents","displayName"],"sourceRoot":"../../../src","sources":["toast/Positioner.tsx"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,KAAKA,KAAK,MAAM,OAAO;AAC9B,SAASC,QAAQ,EAAEC,SAAS,EAAEC,IAAI,QAAQ,cAAc;AACxD,SACEC,qBAAqB,EACrBC,oBAAoB,QACf,gCAAgC;AAEvC,SAASC,sBAAsB,EAAEC,eAAe,QAAQ,cAAW;AACnE,SACEC,6BAA6B,EAC7BC,iBAAiB,EACjBC,cAAc,QACT,uBAAoB;AAAC,SAAAC,GAAA,IAAAC,IAAA,EAAAC,QAAA,IAAAC,SAAA,EAAAC,IAAA,IAAAC,KAAA;AAG5B;AACA;AACA;AACA;AACA;AACA,MAAMC,eAAe,GAAGZ,oBAAoB,EAAEa,MAAM,IAAI;EACtDC,GAAG,EAAE,CAAC;EACNC,MAAM,EAAE,CAAC;EACTC,IAAI,EAAE,CAAC;EACPC,KAAK,EAAE;AACT,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA,MAAMC,iBAAiB,GAAG,IAAI;AAC9B,MAAMC,qBAAqB,GACzBvB,QAAQ,CAACwB,EAAE,KAAK,SAAS,GAAG;EAAEC,SAAS,EAAEH;AAAkB,CAAC,GAAG,IAAI;AAErE,OAAO,MAAMI,UAEZ,GAAGA,CAAC;EAAEC,QAAQ;EAAEC,QAAQ;EAAEC;AAAM,CAAC,KAAK;EACrC,MAAM;IAAEC,MAAM;IAAEC,GAAG;IAAEC;EAAc,CAAC,GAAG1B,eAAe,CAAC,CAAC;EACxD,MAAM;IAAE2B,UAAU;IAAEC,QAAQ;IAAEC;EAAa,CAAC,GAAG9B,sBAAsB,CAAC,CAAC;EACvE,MAAMY,MAAM,GAAGlB,KAAK,CAACqC,UAAU,CAACjC,qBAAqB,CAAC,IAAIa,eAAe;EAEzE,MAAMqB,gBAAgB,GAAGT,QAAQ,IAAI,eAAe;EACpD,MAAMU,WAAW,GAAG7B,cAAc,CAAC;IACjCmB,QAAQ,EAAES,gBAAgB;IAC1BP,MAAM;IACNS,cAAc,EAAE;MAAErB,GAAG,EAAED,MAAM,CAACC,GAAG;MAAEC,MAAM,EAAEF,MAAM,CAACE;IAAO;EAC3D,CAAC,CAAC;EAEF,MAAMqB,kBAAkB,GAAGzC,KAAK,CAAC0C,WAAW,CAAC,MAAM;IACjD,IAAIR,UAAU,EAAE;MACdC,QAAQ,CAAC,CAAC;IACZ;EACF,CAAC,EAAE,CAACD,UAAU,EAAEC,QAAQ,CAAC,CAAC;;EAE1B;EACA,MAAMQ,mBAAmB,GAAGL,gBAAgB,KAAK,QAAQ,IAAIJ,UAAU;EACvE,MAAMU,WAAW,GAAG5C,KAAK,CAAC6C,QAAQ,CAACC,KAAK,CAAClB,QAAQ,CAAC,GAAG,CAAC;EAEtD,oBACEZ,KAAA,CAAAF,SAAA;IAAAc,QAAA,GACGe,mBAAmB,gBAClB/B,IAAA,CAACV,SAAS;MACR4B,KAAK,EAAE,CACLtB,6BAA6B,CAAC;QAC5BqB,QAAQ,EAAES,gBAAgB;QAC1BF,YAAY;QACZJ,GAAG;QACHC,aAAa;QACbM;MACF,CAAC,CAAC,EACFf,qBAAqB,CACrB;MACFuB,OAAO,EAAEN;IAAmB,CAC7B,CAAC,GACA,IAAI,eACR7B,IAAA,CAACT,IAAI;MACH2B,KAAK,EAAE,CACLrB,iBAAiB,CAAC,CAAC,EACnBe,qBAAqB;MACrB;MACA;MACAe,WAAW;
|
|
1
|
+
{"version":3,"names":["React","Platform","Pressable","View","SafeAreaInsetsContext","initialWindowMetrics","useDynamicToastContext","useToastContext","calculateOutsidePressableArea","getContainerStyle","getInsetValues","jsx","_jsx","Fragment","_Fragment","jsxs","_jsxs","FALLBACK_INSETS","insets","top","bottom","left","right","ANDROID_ELEVATION","androidElevationStyle","OS","elevation","Positioner","children","position","style","offset","gap","visibleToasts","isExpanded","collapse","toastHeights","useContext","resolvedPosition","insetValues","safeAreaInsets","handleOutsidePress","useCallback","shouldAllowCollapse","hasChildren","Children","count","onPress","pointerEvents","displayName"],"sourceRoot":"../../../src","sources":["toast/Positioner.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["React","StyleSheet","GestureHandlerRootView","OverlayRoot","Portal","WEB_POSITION_FIXED","Z_INDEX","jsx","_jsx","ToastHost","children","ToasterOverlayWrapper","content","style","styles","host","absoluteFill","displayName","create","position","top","left","right","bottom","zIndex","toast"],"sourceRoot":"../../../src","sources":["toast/ToastHost.tsx"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,KAAKA,KAAK,MAAM,OAAO;AAC9B,SAASC,UAAU,QAAQ,cAAc;AACzC,SAASC,sBAAsB,QAAQ,8BAA8B;AAErE,SAASC,WAAW,QAAQ,qBAAY;AACxC,SAASC,MAAM,QAAQ,wBAAqB;AAC5C,SAASC,kBAAkB,QAAQ,6BAA0B;AAC7D,SAASC,OAAO,QAAQ,sBAAmB;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAG5C,OAAO,SAASC,SAASA,CAAC;EAAEC,QAAQ;EAAEC;AAAsC,CAAC,EAAE;EAC7E,MAAMC,OAAO;EAAA;EACX;EACA;EACA;EACAJ,IAAA,CAACL,WAAW;IAACU,KAAK,EAAEC,MAAM,CAACC,IAAK;IAAAL,QAAA,
|
|
1
|
+
{"version":3,"names":["React","StyleSheet","GestureHandlerRootView","OverlayRoot","Portal","WEB_POSITION_FIXED","Z_INDEX","jsx","_jsx","ToastHost","children","ToasterOverlayWrapper","content","style","styles","host","pointerEvents","absoluteFill","displayName","create","position","top","left","right","bottom","zIndex","toast"],"sourceRoot":"../../../src","sources":["toast/ToastHost.tsx"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,KAAKA,KAAK,MAAM,OAAO;AAC9B,SAASC,UAAU,QAAQ,cAAc;AACzC,SAASC,sBAAsB,QAAQ,8BAA8B;AAErE,SAASC,WAAW,QAAQ,qBAAY;AACxC,SAASC,MAAM,QAAQ,wBAAqB;AAC5C,SAASC,kBAAkB,QAAQ,6BAA0B;AAC7D,SAASC,OAAO,QAAQ,sBAAmB;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAG5C,OAAO,SAASC,SAASA,CAAC;EAAEC,QAAQ;EAAEC;AAAsC,CAAC,EAAE;EAC7E,MAAMC,OAAO;EAAA;EACX;EACA;EACA;EACAJ,IAAA,CAACL,WAAW;IAACU,KAAK,EAAEC,MAAM,CAACC,IAAK;IAAAL,QAAA,eAI9BF,IAAA,CAACN,sBAAsB;MAACc,aAAa,EAAC,UAAU;MAACH,KAAK,EAAEZ,UAAU,CAACgB,YAAa;MAAAP,QAAA,EAC7EA;IAAQ,CACa;EAAC,CACd,CACd;EAED,oBACEF,IAAA,CAACJ,MAAM;IAAAM,QAAA,EACJC,qBAAqB,gBACpBH,IAAA,CAACG,qBAAqB;MAAAD,QAAA,EAAEE;IAAO,CAAwB,CAAC,GAExDA;EACD,CACK,CAAC;AAEb;AAEAH,SAAS,CAACS,WAAW,GAAG,WAAW;AAEnC,MAAMJ,MAAM,GAAGb,UAAU,CAACkB,MAAM,CAAC;EAC/BJ,IAAI,EAAE;IACJ;IACAK,QAAQ,EAAEf,kBAAkB;IAC5BgB,GAAG,EAAE,CAAC;IACNC,IAAI,EAAE,CAAC;IACPC,KAAK,EAAE,CAAC;IACRC,MAAM,EAAE,CAAC;IACT;IACA;IACAC,MAAM,EAAEnB,OAAO,CAACoB;EAClB;AACF,CAAC,CAAC","ignoreList":[]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ConnectionStatusToastsProps } from './shared';
|
|
2
|
+
export type { ConnectionStatusToastsProps } from './shared';
|
|
3
|
+
/** Mount once at the app root. Renders nothing. */
|
|
4
|
+
export declare function ConnectionStatusToasts(props?: ConnectionStatusToastsProps): null;
|
|
5
|
+
export declare namespace ConnectionStatusToasts {
|
|
6
|
+
var displayName: string;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/connection-status/index.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAA6B,KAAK,2BAA2B,EAAE,MAAM,UAAU,CAAC;AAEvF,YAAY,EAAE,2BAA2B,EAAE,MAAM,UAAU,CAAC;AAE5D,mDAAmD;AACnD,wBAAgB,sBAAsB,CAAC,KAAK,GAAE,2BAAgC,GAAG,IAAI,CAcpF;yBAde,sBAAsB"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ConnectionStatusToastsProps } from './shared';
|
|
2
|
+
export type { ConnectionStatusToastsProps } from './shared';
|
|
3
|
+
/** Mount once at the app root. Renders nothing. */
|
|
4
|
+
export declare function ConnectionStatusToasts(props?: ConnectionStatusToastsProps): null;
|
|
5
|
+
export declare namespace ConnectionStatusToasts {
|
|
6
|
+
var displayName: string;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=index.web.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.web.d.ts","sourceRoot":"","sources":["../../../../src/connection-status/index.web.tsx"],"names":[],"mappings":"AAUA,OAAO,EAA6B,KAAK,2BAA2B,EAAE,MAAM,UAAU,CAAC;AAEvF,YAAY,EAAE,2BAA2B,EAAE,MAAM,UAAU,CAAC;AAE5D,mDAAmD;AACnD,wBAAgB,sBAAsB,CAAC,KAAK,GAAE,2BAAgC,GAAG,IAAI,CAkBpF;yBAlBe,sBAAsB"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface ConnectionStatusToastsProps {
|
|
2
|
+
/** Shown as soon as the device reports no connection. */
|
|
3
|
+
offlineMessage?: string;
|
|
4
|
+
/** Replaces `offlineMessage` once the outage has lasted `reconnectingDelayMs`. */
|
|
5
|
+
reconnectingMessage?: string;
|
|
6
|
+
/** Shown briefly when the connection comes back. */
|
|
7
|
+
restoredMessage?: string;
|
|
8
|
+
/** How long an outage must last before it reads as "reconnecting". */
|
|
9
|
+
reconnectingDelayMs?: number;
|
|
10
|
+
/** How long the restored toast stays up. */
|
|
11
|
+
restoredDurationMs?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Drive the connection toast from an `isOnline` signal.
|
|
15
|
+
*
|
|
16
|
+
* Platform-agnostic half of `ConnectionStatusToasts`: the forks differ only in
|
|
17
|
+
* where the signal comes from (NetInfo on native, `navigator.onLine` on web),
|
|
18
|
+
* never in what the user sees.
|
|
19
|
+
*
|
|
20
|
+
* `isOnline === null` means "not determined yet" and shows nothing — a first
|
|
21
|
+
* paint must not accuse the user of being offline.
|
|
22
|
+
*/
|
|
23
|
+
export declare function useConnectionStatusToasts(isOnline: boolean | null, { offlineMessage, reconnectingMessage, restoredMessage, reconnectingDelayMs, restoredDurationMs, }?: ConnectionStatusToastsProps): void;
|
|
24
|
+
//# sourceMappingURL=shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../../src/connection-status/shared.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,2BAA2B;IAC1C,yDAAyD;IACzD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kFAAkF;IAClF,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,oDAAoD;IACpD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sEAAsE;IACtE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,4CAA4C;IAC5C,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAUD;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,OAAO,GAAG,IAAI,EACxB,EACE,cAAwC,EACxC,mBAAkD,EAClD,eAA0C,EAC1C,mBAAkD,EAClD,kBAAgD,GACjD,GAAE,2BAAgC,GAClC,IAAI,CAmCN"}
|
|
@@ -12,8 +12,12 @@
|
|
|
12
12
|
* universal: `ToastHost` supplies a viewport-sized containing block on web, so
|
|
13
13
|
* there is no `.web` fork and no `position: 'fixed'` here (W7).
|
|
14
14
|
*
|
|
15
|
-
* `pointerEvents` goes
|
|
16
|
-
*
|
|
15
|
+
* `pointerEvents` goes on the PROP, never in `style`: react-native-web resolves
|
|
16
|
+
* the RN-only `box-none` value only from the prop path, so as a style entry it
|
|
17
|
+
* is silently dropped and this full-host container inherits `auto` — a
|
|
18
|
+
* transparent sheet over the whole app that swallows every tap for as long as a
|
|
19
|
+
* toast is up. RNW logs a deprecation notice for the prop once per session;
|
|
20
|
+
* that is the cheaper of the two.
|
|
17
21
|
*/
|
|
18
22
|
import * as React from 'react';
|
|
19
23
|
import type { ToasterProps } from './types';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Positioner.d.ts","sourceRoot":"","sources":["../../../../src/toast/Positioner.tsx"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"Positioner.d.ts","sourceRoot":"","sources":["../../../../src/toast/Positioner.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAa/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAwB5C,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAC/B,KAAK,CAAC,iBAAiB,CAAC,IAAI,CAAC,YAAY,EAAE,UAAU,GAAG,OAAO,CAAC,CAAC,CA4DlE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ToastHost.d.ts","sourceRoot":"","sources":["../../../../src/toast/ToastHost.tsx"],"names":[],"mappings":"AAgCA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,wBAAgB,SAAS,CAAC,EAAE,QAAQ,EAAE,qBAAqB,EAAE,EAAE,cAAc,
|
|
1
|
+
{"version":3,"file":"ToastHost.d.ts","sourceRoot":"","sources":["../../../../src/toast/ToastHost.tsx"],"names":[],"mappings":"AAgCA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,wBAAgB,SAAS,CAAC,EAAE,QAAQ,EAAE,qBAAqB,EAAE,EAAE,cAAc,2CAwB5E;yBAxBe,SAAS"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ConnectionStatusToastsProps } from './shared';
|
|
2
|
+
export type { ConnectionStatusToastsProps } from './shared';
|
|
3
|
+
/** Mount once at the app root. Renders nothing. */
|
|
4
|
+
export declare function ConnectionStatusToasts(props?: ConnectionStatusToastsProps): null;
|
|
5
|
+
export declare namespace ConnectionStatusToasts {
|
|
6
|
+
var displayName: string;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/connection-status/index.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAA6B,KAAK,2BAA2B,EAAE,MAAM,UAAU,CAAC;AAEvF,YAAY,EAAE,2BAA2B,EAAE,MAAM,UAAU,CAAC;AAE5D,mDAAmD;AACnD,wBAAgB,sBAAsB,CAAC,KAAK,GAAE,2BAAgC,GAAG,IAAI,CAcpF;yBAde,sBAAsB"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ConnectionStatusToastsProps } from './shared';
|
|
2
|
+
export type { ConnectionStatusToastsProps } from './shared';
|
|
3
|
+
/** Mount once at the app root. Renders nothing. */
|
|
4
|
+
export declare function ConnectionStatusToasts(props?: ConnectionStatusToastsProps): null;
|
|
5
|
+
export declare namespace ConnectionStatusToasts {
|
|
6
|
+
var displayName: string;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=index.web.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.web.d.ts","sourceRoot":"","sources":["../../../../src/connection-status/index.web.tsx"],"names":[],"mappings":"AAUA,OAAO,EAA6B,KAAK,2BAA2B,EAAE,MAAM,UAAU,CAAC;AAEvF,YAAY,EAAE,2BAA2B,EAAE,MAAM,UAAU,CAAC;AAE5D,mDAAmD;AACnD,wBAAgB,sBAAsB,CAAC,KAAK,GAAE,2BAAgC,GAAG,IAAI,CAkBpF;yBAlBe,sBAAsB"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface ConnectionStatusToastsProps {
|
|
2
|
+
/** Shown as soon as the device reports no connection. */
|
|
3
|
+
offlineMessage?: string;
|
|
4
|
+
/** Replaces `offlineMessage` once the outage has lasted `reconnectingDelayMs`. */
|
|
5
|
+
reconnectingMessage?: string;
|
|
6
|
+
/** Shown briefly when the connection comes back. */
|
|
7
|
+
restoredMessage?: string;
|
|
8
|
+
/** How long an outage must last before it reads as "reconnecting". */
|
|
9
|
+
reconnectingDelayMs?: number;
|
|
10
|
+
/** How long the restored toast stays up. */
|
|
11
|
+
restoredDurationMs?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Drive the connection toast from an `isOnline` signal.
|
|
15
|
+
*
|
|
16
|
+
* Platform-agnostic half of `ConnectionStatusToasts`: the forks differ only in
|
|
17
|
+
* where the signal comes from (NetInfo on native, `navigator.onLine` on web),
|
|
18
|
+
* never in what the user sees.
|
|
19
|
+
*
|
|
20
|
+
* `isOnline === null` means "not determined yet" and shows nothing — a first
|
|
21
|
+
* paint must not accuse the user of being offline.
|
|
22
|
+
*/
|
|
23
|
+
export declare function useConnectionStatusToasts(isOnline: boolean | null, { offlineMessage, reconnectingMessage, restoredMessage, reconnectingDelayMs, restoredDurationMs, }?: ConnectionStatusToastsProps): void;
|
|
24
|
+
//# sourceMappingURL=shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../../src/connection-status/shared.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,2BAA2B;IAC1C,yDAAyD;IACzD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kFAAkF;IAClF,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,oDAAoD;IACpD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sEAAsE;IACtE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,4CAA4C;IAC5C,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAUD;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,OAAO,GAAG,IAAI,EACxB,EACE,cAAwC,EACxC,mBAAkD,EAClD,eAA0C,EAC1C,mBAAkD,EAClD,kBAAgD,GACjD,GAAE,2BAAgC,GAClC,IAAI,CAmCN"}
|
|
@@ -12,8 +12,12 @@
|
|
|
12
12
|
* universal: `ToastHost` supplies a viewport-sized containing block on web, so
|
|
13
13
|
* there is no `.web` fork and no `position: 'fixed'` here (W7).
|
|
14
14
|
*
|
|
15
|
-
* `pointerEvents` goes
|
|
16
|
-
*
|
|
15
|
+
* `pointerEvents` goes on the PROP, never in `style`: react-native-web resolves
|
|
16
|
+
* the RN-only `box-none` value only from the prop path, so as a style entry it
|
|
17
|
+
* is silently dropped and this full-host container inherits `auto` — a
|
|
18
|
+
* transparent sheet over the whole app that swallows every tap for as long as a
|
|
19
|
+
* toast is up. RNW logs a deprecation notice for the prop once per session;
|
|
20
|
+
* that is the cheaper of the two.
|
|
17
21
|
*/
|
|
18
22
|
import * as React from 'react';
|
|
19
23
|
import type { ToasterProps } from './types';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Positioner.d.ts","sourceRoot":"","sources":["../../../../src/toast/Positioner.tsx"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"Positioner.d.ts","sourceRoot":"","sources":["../../../../src/toast/Positioner.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAa/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAwB5C,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAC/B,KAAK,CAAC,iBAAiB,CAAC,IAAI,CAAC,YAAY,EAAE,UAAU,GAAG,OAAO,CAAC,CAAC,CA4DlE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ToastHost.d.ts","sourceRoot":"","sources":["../../../../src/toast/ToastHost.tsx"],"names":[],"mappings":"AAgCA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,wBAAgB,SAAS,CAAC,EAAE,QAAQ,EAAE,qBAAqB,EAAE,EAAE,cAAc,
|
|
1
|
+
{"version":3,"file":"ToastHost.d.ts","sourceRoot":"","sources":["../../../../src/toast/ToastHost.tsx"],"names":[],"mappings":"AAgCA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,wBAAgB,SAAS,CAAC,EAAE,QAAQ,EAAE,qBAAqB,EAAE,EAAE,cAAc,2CAwB5E;yBAxBe,SAAS"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxyhq/bloom",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.66.0",
|
|
4
4
|
"description": "Bloom UI — Oxy ecosystem component library for React Native + Expo + Web",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
|
@@ -148,6 +148,22 @@
|
|
|
148
148
|
"default": "./lib/commonjs/portal/index.js"
|
|
149
149
|
}
|
|
150
150
|
},
|
|
151
|
+
"./connection-status": {
|
|
152
|
+
"react-native": "./src/connection-status/index.tsx",
|
|
153
|
+
"browser": {
|
|
154
|
+
"types": "./lib/typescript/module/connection-status/index.web.d.ts",
|
|
155
|
+
"import": "./lib/module/connection-status/index.web.js",
|
|
156
|
+
"require": "./lib/commonjs/connection-status/index.web.js"
|
|
157
|
+
},
|
|
158
|
+
"import": {
|
|
159
|
+
"types": "./lib/typescript/module/connection-status/index.d.ts",
|
|
160
|
+
"default": "./lib/module/connection-status/index.js"
|
|
161
|
+
},
|
|
162
|
+
"require": {
|
|
163
|
+
"types": "./lib/typescript/commonjs/connection-status/index.d.ts",
|
|
164
|
+
"default": "./lib/commonjs/connection-status/index.js"
|
|
165
|
+
}
|
|
166
|
+
},
|
|
151
167
|
"./overlay": {
|
|
152
168
|
"react-native": "./src/overlay/index.tsx",
|
|
153
169
|
"import": {
|
|
@@ -1210,7 +1226,8 @@
|
|
|
1210
1226
|
"storybook": "^10",
|
|
1211
1227
|
"ts-jest": "^29.4.6",
|
|
1212
1228
|
"typescript": "~5.9.2",
|
|
1213
|
-
"vite": "^7"
|
|
1229
|
+
"vite": "^7",
|
|
1230
|
+
"@react-native-community/netinfo": "^12.0.1"
|
|
1214
1231
|
},
|
|
1215
1232
|
"peerDependencies": {
|
|
1216
1233
|
"expo": "*",
|
|
@@ -1229,7 +1246,8 @@
|
|
|
1229
1246
|
"react-native-safe-area-context": ">=5.0.0",
|
|
1230
1247
|
"react-native-screens": ">=3.16.0",
|
|
1231
1248
|
"react-native-svg": ">=13.0.0",
|
|
1232
|
-
"nativewind": ">=5.0.0"
|
|
1249
|
+
"nativewind": ">=5.0.0",
|
|
1250
|
+
"@react-native-community/netinfo": ""
|
|
1233
1251
|
},
|
|
1234
1252
|
"peerDependenciesMeta": {
|
|
1235
1253
|
"expo": {
|
|
@@ -1249,6 +1267,9 @@
|
|
|
1249
1267
|
},
|
|
1250
1268
|
"nativewind": {
|
|
1251
1269
|
"optional": true
|
|
1270
|
+
},
|
|
1271
|
+
"@react-native-community/netinfo": {
|
|
1272
|
+
"optional": true
|
|
1252
1273
|
}
|
|
1253
1274
|
},
|
|
1254
1275
|
"react-native-builder-bob": {
|
|
@@ -116,10 +116,16 @@ describe('ToastHost platform split', () => {
|
|
|
116
116
|
expect(driver).not.toMatch(/useDerivedValue/);
|
|
117
117
|
});
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
// This assertion used to demand the exact opposite, and it is what kept the
|
|
120
|
+
// bug alive: `box-none` is an RN-only value that react-native-web resolves
|
|
121
|
+
// ONLY from the prop (`createDOMProps` maps it to a class pair). As a style
|
|
122
|
+
// entry it is dropped, so this full-host container inherited `auto` and sat
|
|
123
|
+
// over the whole app as an invisible click-eater for as long as a toast was
|
|
124
|
+
// up. The prop form logs an RNW deprecation notice once; that is the price.
|
|
125
|
+
it('Positioner passes the RN-only pointerEvents value as a PROP, not a style', () => {
|
|
120
126
|
const positioner = code('toast/Positioner.tsx');
|
|
121
|
-
expect(positioner).toMatch(/pointerEvents
|
|
122
|
-
expect(positioner).not.toMatch(/pointerEvents
|
|
127
|
+
expect(positioner).toMatch(/pointerEvents=\{/);
|
|
128
|
+
expect(positioner).not.toMatch(/pointerEvents:\s*(?:[^,;\n]*\n?){0,6}?['"]box-none['"]/);
|
|
123
129
|
});
|
|
124
130
|
|
|
125
131
|
it('package.json exports ./toast WITHOUT a browser condition', () => {
|
|
@@ -23,7 +23,10 @@ import { createRoot, type Root } from 'react-dom/client';
|
|
|
23
23
|
// emits a class and would make every assertion below vacuous.
|
|
24
24
|
jest.mock('react-native', () => jest.requireActual('react-native-web'));
|
|
25
25
|
|
|
26
|
+
import { View } from 'react-native';
|
|
27
|
+
|
|
26
28
|
import { Backdrop, OverlayRoot } from '../overlay';
|
|
29
|
+
import { ToastHost } from '../toast/ToastHost';
|
|
27
30
|
|
|
28
31
|
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT =
|
|
29
32
|
true;
|
|
@@ -175,6 +178,48 @@ describe('overlay pointer-events contract (web)', () => {
|
|
|
175
178
|
container.remove();
|
|
176
179
|
});
|
|
177
180
|
|
|
181
|
+
// The toast host is the widest surface Bloom mounts: a viewport-sized stack of
|
|
182
|
+
// containers that is present for as long as ANY toast is up. Every layer in it
|
|
183
|
+
// must be click-through, or a toast turns the whole app read-only — which is
|
|
184
|
+
// what shipped, because the positioner passed `box-none` as a style entry.
|
|
185
|
+
it('every layer of the toast host stays click-through', () => {
|
|
186
|
+
const { root } = render(
|
|
187
|
+
createElement(ToastHost, {
|
|
188
|
+
children: createElement(View, { testID: 'row' }),
|
|
189
|
+
}),
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
const row = document.querySelector('[data-testid="row"]') as HTMLElement | null;
|
|
193
|
+
expect(row).not.toBeNull();
|
|
194
|
+
|
|
195
|
+
const layers: HTMLElement[] = [];
|
|
196
|
+
for (let el = row?.parentElement; el && el !== document.body; el = el.parentElement) {
|
|
197
|
+
layers.push(el);
|
|
198
|
+
}
|
|
199
|
+
// Vacuity floor: the host view and the portal root. (The gesture root is
|
|
200
|
+
// the third layer in a browser; this suite's `react-native-gesture-handler`
|
|
201
|
+
// mock renders it away, so its own `box-none` is pinned by the source gate
|
|
202
|
+
// in `pointer-events-style-form.test.ts` instead.) A portal that failed to
|
|
203
|
+
// mount would leave an empty chain and pass silently.
|
|
204
|
+
expect(layers.length).toBeGreaterThanOrEqual(2);
|
|
205
|
+
expect(layers.some((el) => el.id === 'bloom-portal-root')).toBe(true);
|
|
206
|
+
|
|
207
|
+
for (const el of layers) {
|
|
208
|
+
const inline = el.style.pointerEvents;
|
|
209
|
+
const inert =
|
|
210
|
+
inline === 'none' ||
|
|
211
|
+
rulesFor(el.className).some(
|
|
212
|
+
(r) => /pointer-events:\s*none/.test(r) && !/>\s*\*/.test(r),
|
|
213
|
+
);
|
|
214
|
+
expect({ layer: el.className || el.id, inert }).toEqual({
|
|
215
|
+
layer: el.className || el.id,
|
|
216
|
+
inert: true,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
act(() => root.unmount());
|
|
221
|
+
});
|
|
222
|
+
|
|
178
223
|
it('a disabled Backdrop dims without dismissing', () => {
|
|
179
224
|
const onPress = jest.fn();
|
|
180
225
|
const { root, container } = render(
|
|
@@ -68,14 +68,26 @@ describe('pointerEvents style-object form', () => {
|
|
|
68
68
|
});
|
|
69
69
|
|
|
70
70
|
it('never uses the RN-only values as styles — pass them as the prop', () => {
|
|
71
|
-
const offenders =
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
)
|
|
78
|
-
|
|
71
|
+
const offenders: string[] = [];
|
|
72
|
+
|
|
73
|
+
for (const file of files) {
|
|
74
|
+
const source = readFileSync(file, 'utf8');
|
|
75
|
+
// Scan the whole SOURCE, not line by line: the toast positioner hid one
|
|
76
|
+
// of these behind a multi-line ternary (`pointerEvents:\n cond ? 'none'
|
|
77
|
+
// : 'box-none'`) and a per-line regex sailed straight past it, which is
|
|
78
|
+
// how a full-screen layer went on swallowing every tap under a toast.
|
|
79
|
+
// Comments first: this file and several components DESCRIBE the banned
|
|
80
|
+
// form in prose, and a scanner that flags its own documentation is a
|
|
81
|
+
// scanner nobody keeps.
|
|
82
|
+
const code = source.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/[^\n]*/g, '');
|
|
83
|
+
// The value may sit behind a ternary on the next line, so allow a bounded
|
|
84
|
+
// gap — but never one that crosses into a JSX prop (`pointerEvents=`) or
|
|
85
|
+
// the next element (`<`), which is what turns this into a false positive.
|
|
86
|
+
for (const match of code.matchAll(/pointerEvents:(?:(?!pointerEvents=|<)[\s\S]){0,160}?['"](box-none|box-only)['"]/g)) {
|
|
87
|
+
const line = code.slice(0, match.index).split('\n').length;
|
|
88
|
+
offenders.push(`${file.slice(SRC.length + 1)}:~${line} ${match[0].replace(/\s+/g, ' ')}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
79
91
|
|
|
80
92
|
expect(offenders).toEqual([]);
|
|
81
93
|
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connection loss as a toast, not a layout-shifting banner.
|
|
3
|
+
*
|
|
4
|
+
* Every Oxy app needs this and none of them should hand-roll it: a coloured bar
|
|
5
|
+
* sliding in above the header pushes the whole screen down, has to be themed
|
|
6
|
+
* again in each app, and competes with the app's own chrome. The toast host is
|
|
7
|
+
* already mounted, already themed, already stacks.
|
|
8
|
+
*
|
|
9
|
+
* NATIVE variant: `@react-native-community/netinfo` (an Expo-managed dependency
|
|
10
|
+
* every Oxy app already ships) reports both link state and reachability. The
|
|
11
|
+
* web fork uses `navigator.onLine` instead so Vite/SPA consumers need no extra
|
|
12
|
+
* dependency at all.
|
|
13
|
+
*/
|
|
14
|
+
import { useEffect, useState } from 'react';
|
|
15
|
+
import NetInfo from '@react-native-community/netinfo';
|
|
16
|
+
|
|
17
|
+
import { useConnectionStatusToasts, type ConnectionStatusToastsProps } from './shared';
|
|
18
|
+
|
|
19
|
+
export type { ConnectionStatusToastsProps } from './shared';
|
|
20
|
+
|
|
21
|
+
/** Mount once at the app root. Renders nothing. */
|
|
22
|
+
export function ConnectionStatusToasts(props: ConnectionStatusToastsProps = {}): null {
|
|
23
|
+
const [isOnline, setIsOnline] = useState<boolean | null>(null);
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
// `isInternetReachable` is null until the first probe resolves; treat that
|
|
27
|
+
// as "connected as far as we know" so a captive-portal check never flashes
|
|
28
|
+
// an outage on launch.
|
|
29
|
+
return NetInfo.addEventListener((state) => {
|
|
30
|
+
setIsOnline(Boolean(state.isConnected && state.isInternetReachable !== false));
|
|
31
|
+
});
|
|
32
|
+
}, []);
|
|
33
|
+
|
|
34
|
+
useConnectionStatusToasts(isOnline, props);
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
ConnectionStatusToasts.displayName = 'ConnectionStatusToasts';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web variant — see `./index.tsx` for what this is and why it is a toast.
|
|
3
|
+
*
|
|
4
|
+
* `navigator.onLine` + the `online`/`offline` events are the browser's own
|
|
5
|
+
* signal, so a Vite/SPA consumer needs no native dependency to get the same
|
|
6
|
+
* behaviour. (The browser only knows about the link, not reachability; that is
|
|
7
|
+
* the honest limit of what a web app can detect without polling.)
|
|
8
|
+
*/
|
|
9
|
+
import { useEffect, useState } from 'react';
|
|
10
|
+
|
|
11
|
+
import { useConnectionStatusToasts, type ConnectionStatusToastsProps } from './shared';
|
|
12
|
+
|
|
13
|
+
export type { ConnectionStatusToastsProps } from './shared';
|
|
14
|
+
|
|
15
|
+
/** Mount once at the app root. Renders nothing. */
|
|
16
|
+
export function ConnectionStatusToasts(props: ConnectionStatusToastsProps = {}): null {
|
|
17
|
+
const [isOnline, setIsOnline] = useState<boolean | null>(null);
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (typeof navigator === 'undefined' || typeof window === 'undefined') return undefined;
|
|
21
|
+
|
|
22
|
+
setIsOnline(navigator.onLine);
|
|
23
|
+
const update = () => setIsOnline(navigator.onLine);
|
|
24
|
+
window.addEventListener('online', update);
|
|
25
|
+
window.addEventListener('offline', update);
|
|
26
|
+
return () => {
|
|
27
|
+
window.removeEventListener('online', update);
|
|
28
|
+
window.removeEventListener('offline', update);
|
|
29
|
+
};
|
|
30
|
+
}, []);
|
|
31
|
+
|
|
32
|
+
useConnectionStatusToasts(isOnline, props);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
ConnectionStatusToasts.displayName = 'ConnectionStatusToasts';
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
import { toast } from '../toast';
|
|
4
|
+
|
|
5
|
+
/** One id for the whole lifecycle, so the toast updates in place. */
|
|
6
|
+
const TOAST_ID = 'bloom-connection-status';
|
|
7
|
+
|
|
8
|
+
export interface ConnectionStatusToastsProps {
|
|
9
|
+
/** Shown as soon as the device reports no connection. */
|
|
10
|
+
offlineMessage?: string;
|
|
11
|
+
/** Replaces `offlineMessage` once the outage has lasted `reconnectingDelayMs`. */
|
|
12
|
+
reconnectingMessage?: string;
|
|
13
|
+
/** Shown briefly when the connection comes back. */
|
|
14
|
+
restoredMessage?: string;
|
|
15
|
+
/** How long an outage must last before it reads as "reconnecting". */
|
|
16
|
+
reconnectingDelayMs?: number;
|
|
17
|
+
/** How long the restored toast stays up. */
|
|
18
|
+
restoredDurationMs?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const DEFAULTS = {
|
|
22
|
+
offlineMessage: 'No internet connection',
|
|
23
|
+
reconnectingMessage: 'Reconnecting…',
|
|
24
|
+
restoredMessage: 'Back online',
|
|
25
|
+
reconnectingDelayMs: 3000,
|
|
26
|
+
restoredDurationMs: 2500,
|
|
27
|
+
} as const;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Drive the connection toast from an `isOnline` signal.
|
|
31
|
+
*
|
|
32
|
+
* Platform-agnostic half of `ConnectionStatusToasts`: the forks differ only in
|
|
33
|
+
* where the signal comes from (NetInfo on native, `navigator.onLine` on web),
|
|
34
|
+
* never in what the user sees.
|
|
35
|
+
*
|
|
36
|
+
* `isOnline === null` means "not determined yet" and shows nothing — a first
|
|
37
|
+
* paint must not accuse the user of being offline.
|
|
38
|
+
*/
|
|
39
|
+
export function useConnectionStatusToasts(
|
|
40
|
+
isOnline: boolean | null,
|
|
41
|
+
{
|
|
42
|
+
offlineMessage = DEFAULTS.offlineMessage,
|
|
43
|
+
reconnectingMessage = DEFAULTS.reconnectingMessage,
|
|
44
|
+
restoredMessage = DEFAULTS.restoredMessage,
|
|
45
|
+
reconnectingDelayMs = DEFAULTS.reconnectingDelayMs,
|
|
46
|
+
restoredDurationMs = DEFAULTS.restoredDurationMs,
|
|
47
|
+
}: ConnectionStatusToastsProps = {},
|
|
48
|
+
): void {
|
|
49
|
+
// Whether an outage toast is currently up, so coming back online only
|
|
50
|
+
// announces itself to someone who saw the outage.
|
|
51
|
+
const wasOffline = useRef(false);
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (isOnline === null) return undefined;
|
|
55
|
+
|
|
56
|
+
if (!isOnline) {
|
|
57
|
+
wasOffline.current = true;
|
|
58
|
+
// Persistent: an outage ends when the network says so, not on a timer.
|
|
59
|
+
toast.error(offlineMessage, { id: TOAST_ID, duration: Infinity, dismissible: false });
|
|
60
|
+
const escalate = setTimeout(() => {
|
|
61
|
+
toast.warning(reconnectingMessage, {
|
|
62
|
+
id: TOAST_ID,
|
|
63
|
+
duration: Infinity,
|
|
64
|
+
dismissible: false,
|
|
65
|
+
});
|
|
66
|
+
}, reconnectingDelayMs);
|
|
67
|
+
return () => clearTimeout(escalate);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (wasOffline.current) {
|
|
71
|
+
wasOffline.current = false;
|
|
72
|
+
toast.success(restoredMessage, { id: TOAST_ID, duration: restoredDurationMs });
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
}, [
|
|
76
|
+
isOnline,
|
|
77
|
+
offlineMessage,
|
|
78
|
+
reconnectingMessage,
|
|
79
|
+
restoredMessage,
|
|
80
|
+
reconnectingDelayMs,
|
|
81
|
+
restoredDurationMs,
|
|
82
|
+
]);
|
|
83
|
+
}
|
package/src/toast/Positioner.tsx
CHANGED
|
@@ -12,8 +12,12 @@
|
|
|
12
12
|
* universal: `ToastHost` supplies a viewport-sized containing block on web, so
|
|
13
13
|
* there is no `.web` fork and no `position: 'fixed'` here (W7).
|
|
14
14
|
*
|
|
15
|
-
* `pointerEvents` goes
|
|
16
|
-
*
|
|
15
|
+
* `pointerEvents` goes on the PROP, never in `style`: react-native-web resolves
|
|
16
|
+
* the RN-only `box-none` value only from the prop path, so as a style entry it
|
|
17
|
+
* is silently dropped and this full-host container inherits `auto` — a
|
|
18
|
+
* transparent sheet over the whole app that swallows every tap for as long as a
|
|
19
|
+
* toast is up. RNW logs a deprecation notice for the prop once per session;
|
|
20
|
+
* that is the cheaper of the two.
|
|
17
21
|
*/
|
|
18
22
|
import * as React from 'react';
|
|
19
23
|
import { Platform, Pressable, View } from 'react-native';
|
|
@@ -100,17 +104,14 @@ export const Positioner: React.FC<
|
|
|
100
104
|
// Overrides ONE edge of the four `getContainerStyle` pins, so the box
|
|
101
105
|
// keeps a real height and the rows inside it stay in bounds.
|
|
102
106
|
insetValues,
|
|
103
|
-
// This container spans the whole host, so it must never swallow a touch.
|
|
104
|
-
// `box-none` passes presses through to the app while still reaching the
|
|
105
|
-
// rows; an EMPTY one on Android drops to `none` outright, because the
|
|
106
|
-
// outlet's configured position always renders a container even with no
|
|
107
|
-
// rows in it (see `Toaster`'s grouping).
|
|
108
|
-
{
|
|
109
|
-
pointerEvents:
|
|
110
|
-
Platform.OS === 'android' && !hasChildren ? 'none' : 'box-none',
|
|
111
|
-
},
|
|
112
107
|
style,
|
|
113
108
|
]}
|
|
109
|
+
// This container spans the whole host, so it must never swallow a touch.
|
|
110
|
+
// `box-none` passes presses through to the app while still reaching the
|
|
111
|
+
// rows; an EMPTY one on Android drops to `none` outright, because the
|
|
112
|
+
// outlet's configured position always renders a container even with no
|
|
113
|
+
// rows in it (see `Toaster`'s grouping).
|
|
114
|
+
pointerEvents={Platform.OS === 'android' && !hasChildren ? 'none' : 'box-none'}
|
|
114
115
|
>
|
|
115
116
|
{children}
|
|
116
117
|
</View>
|
package/src/toast/ToastHost.tsx
CHANGED
|
@@ -38,7 +38,10 @@ export function ToastHost({ children, ToasterOverlayWrapper }: ToastHostProps) {
|
|
|
38
38
|
// entry it never reached the DOM, so the rows inherited the portal root's
|
|
39
39
|
// `pointer-events: none` and no toast could be pressed or swiped away.
|
|
40
40
|
<OverlayRoot style={styles.host}>
|
|
41
|
-
|
|
41
|
+
{/* Also `box-none`: as the direct child of a box-none root it would
|
|
42
|
+
otherwise be handed `pointer-events: auto` and, spanning the whole
|
|
43
|
+
viewport, block the app underneath while a toast is up. */}
|
|
44
|
+
<GestureHandlerRootView pointerEvents="box-none" style={StyleSheet.absoluteFill}>
|
|
42
45
|
{children}
|
|
43
46
|
</GestureHandlerRootView>
|
|
44
47
|
</OverlayRoot>
|