@oxyhq/bloom 0.54.1 → 0.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -1
- package/lib/commonjs/provider/index.js +67 -0
- package/lib/commonjs/provider/index.js.map +1 -0
- package/lib/commonjs/provider/scroll-provider.js +13 -0
- package/lib/commonjs/provider/scroll-provider.js.map +1 -0
- package/lib/commonjs/provider/scroll-provider.web.js +13 -0
- package/lib/commonjs/provider/scroll-provider.web.js.map +1 -0
- package/lib/commonjs/toast/ToastRow.js +35 -10
- package/lib/commonjs/toast/ToastRow.js.map +1 -1
- package/lib/commonjs/toast/ToastSwipeHandler.js +15 -5
- package/lib/commonjs/toast/ToastSwipeHandler.js.map +1 -1
- package/lib/commonjs/toast/constants.js +18 -1
- package/lib/commonjs/toast/constants.js.map +1 -1
- package/lib/commonjs/toast/toast-store.js +39 -1
- package/lib/commonjs/toast/toast-store.js.map +1 -1
- package/lib/commonjs/toast/use-stack-hover.js +175 -0
- package/lib/commonjs/toast/use-stack-hover.js.map +1 -0
- package/lib/commonjs/toast/use-stack-hover.native.js +42 -0
- package/lib/commonjs/toast/use-stack-hover.native.js.map +1 -0
- package/lib/module/provider/index.js +63 -0
- package/lib/module/provider/index.js.map +1 -0
- package/lib/module/provider/scroll-provider.js +16 -0
- package/lib/module/provider/scroll-provider.js.map +1 -0
- package/lib/module/provider/scroll-provider.web.js +5 -0
- package/lib/module/provider/scroll-provider.web.js.map +1 -0
- package/lib/module/toast/ToastRow.js +35 -10
- package/lib/module/toast/ToastRow.js.map +1 -1
- package/lib/module/toast/ToastSwipeHandler.js +16 -4
- package/lib/module/toast/ToastSwipeHandler.js.map +1 -1
- package/lib/module/toast/constants.js +18 -1
- package/lib/module/toast/constants.js.map +1 -1
- package/lib/module/toast/toast-store.js +39 -1
- package/lib/module/toast/toast-store.js.map +1 -1
- package/lib/module/toast/use-stack-hover.js +168 -0
- package/lib/module/toast/use-stack-hover.js.map +1 -0
- package/lib/module/toast/use-stack-hover.native.js +35 -0
- package/lib/module/toast/use-stack-hover.native.js.map +1 -0
- package/lib/typescript/commonjs/provider/index.d.ts +45 -0
- package/lib/typescript/commonjs/provider/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/provider/scroll-provider.d.ts +14 -0
- package/lib/typescript/commonjs/provider/scroll-provider.d.ts.map +1 -0
- package/lib/typescript/commonjs/provider/scroll-provider.web.d.ts +3 -0
- package/lib/typescript/commonjs/provider/scroll-provider.web.d.ts.map +1 -0
- package/lib/typescript/commonjs/toast/ToastRow.d.ts.map +1 -1
- package/lib/typescript/commonjs/toast/ToastSwipeHandler.d.ts +4 -0
- package/lib/typescript/commonjs/toast/ToastSwipeHandler.d.ts.map +1 -1
- package/lib/typescript/commonjs/toast/constants.d.ts.map +1 -1
- package/lib/typescript/commonjs/toast/toast-store.d.ts +15 -0
- package/lib/typescript/commonjs/toast/toast-store.d.ts.map +1 -1
- package/lib/typescript/commonjs/toast/use-stack-hover.d.ts +55 -0
- package/lib/typescript/commonjs/toast/use-stack-hover.d.ts.map +1 -0
- package/lib/typescript/commonjs/toast/use-stack-hover.native.d.ts +26 -0
- package/lib/typescript/commonjs/toast/use-stack-hover.native.d.ts.map +1 -0
- package/lib/typescript/module/provider/index.d.ts +45 -0
- package/lib/typescript/module/provider/index.d.ts.map +1 -0
- package/lib/typescript/module/provider/scroll-provider.d.ts +14 -0
- package/lib/typescript/module/provider/scroll-provider.d.ts.map +1 -0
- package/lib/typescript/module/provider/scroll-provider.web.d.ts +3 -0
- package/lib/typescript/module/provider/scroll-provider.web.d.ts.map +1 -0
- package/lib/typescript/module/toast/ToastRow.d.ts.map +1 -1
- package/lib/typescript/module/toast/ToastSwipeHandler.d.ts +4 -0
- package/lib/typescript/module/toast/ToastSwipeHandler.d.ts.map +1 -1
- package/lib/typescript/module/toast/constants.d.ts.map +1 -1
- package/lib/typescript/module/toast/toast-store.d.ts +15 -0
- package/lib/typescript/module/toast/toast-store.d.ts.map +1 -1
- package/lib/typescript/module/toast/use-stack-hover.d.ts +55 -0
- package/lib/typescript/module/toast/use-stack-hover.d.ts.map +1 -0
- package/lib/typescript/module/toast/use-stack-hover.native.d.ts +26 -0
- package/lib/typescript/module/toast/use-stack-hover.native.d.ts.map +1 -0
- package/package.json +12 -1
- package/src/__tests__/BloomProvider.web.test.tsx +114 -0
- package/src/__tests__/Toaster.test.tsx +554 -0
- package/src/__tests__/toast-position-utils.test.ts +28 -0
- package/src/__tests__/toast-stack-hover.test.ts +92 -0
- package/src/__tests__/toast-store.test.ts +72 -0
- package/src/provider/index.tsx +68 -0
- package/src/provider/scroll-provider.ts +13 -0
- package/src/provider/scroll-provider.web.ts +2 -0
- package/src/toast/Toast.stories.tsx +10 -0
- package/src/toast/ToastRow.tsx +33 -10
- package/src/toast/ToastSwipeHandler.tsx +14 -2
- package/src/toast/constants.ts +18 -1
- package/src/toast/toast-store.ts +39 -1
- package/src/toast/use-stack-hover.native.ts +34 -0
- package/src/toast/use-stack-hover.ts +182 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native/default binding for the scroll-restoration provider used by
|
|
3
|
+
* {@link BloomProvider}.
|
|
4
|
+
*
|
|
5
|
+
* `@oxyhq/bloom/scroll` is web-forked, so its `browser` export condition hands
|
|
6
|
+
* web consumers the real implementation while native gets the no-op. A compiled
|
|
7
|
+
* `lib/module/provider/index.js` cannot benefit from that condition (it imports
|
|
8
|
+
* a relative path, not the package subpath), so the platform choice is made
|
|
9
|
+
* here by FILENAME instead: Metro picks `scroll-provider.web.ts` on web, and
|
|
10
|
+
* every web bundler that resolves `.web.js` picks the compiled sibling. Same
|
|
11
|
+
* mechanism the toast engine uses for `ToastHost.native.tsx`.
|
|
12
|
+
*/
|
|
13
|
+
export { ScrollRestorationProvider } from '../scroll';
|
|
14
|
+
//# sourceMappingURL=scroll-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scroll-provider.d.ts","sourceRoot":"","sources":["../../../../src/provider/scroll-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scroll-provider.web.d.ts","sourceRoot":"","sources":["../../../../src/provider/scroll-provider.web.ts"],"names":[],"mappings":"AAAA,wGAAwG;AACxG,OAAO,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ToastRow.d.ts","sourceRoot":"","sources":["../../../../src/toast/ToastRow.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"ToastRow.d.ts","sourceRoot":"","sources":["../../../../src/toast/ToastRow.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AA6B/B,OAAO,KAAK,EAEV,aAAa,EAEb,QAAQ,EACR,WAAW,EACZ,MAAM,SAAS,CAAC;AAwDjB,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eAHR,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,KAAK,IAAI;kCAuWzC,CAAC"}
|
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
* covers `toast.custom` JSX and `unstyled` rows too, which never reach that
|
|
15
15
|
* card. Every distance the gesture measures is a fraction of the capped
|
|
16
16
|
* `rowWidth`, not of the window — see the constant.
|
|
17
|
+
*
|
|
18
|
+
* W13 — being THE row box also makes it the hover target: `useStackHover`'s
|
|
19
|
+
* handlers go here so a pointer over ANY row counts as a pointer over the stack.
|
|
20
|
+
* The handlers are empty on native.
|
|
17
21
|
*/
|
|
18
22
|
import * as React from 'react';
|
|
19
23
|
import { type ViewStyle } from 'react-native';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ToastSwipeHandler.d.ts","sourceRoot":"","sources":["../../../../src/toast/ToastSwipeHandler.tsx"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"ToastSwipeHandler.d.ts","sourceRoot":"","sources":["../../../../src/toast/ToastSwipeHandler.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAetB,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAYzD,KAAK,sBAAsB,GAAG,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,GAAG;IAC5D,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,GAAG,CAAC,SAAS,GAAG,SAAS,CAAC,EAAE,CAAC;IAC9C,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,OAAO,EAAE,CAAC,IAAI,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACnD,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CACtC,KAAK,CAAC,iBAAiB,CAAC,sBAAsB,CAAC,CA8JhD,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../src/toast/constants.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,UAAU,EACV,aAAa,EACb,mBAAmB,EACnB,UAAU,EACV,YAAY,EACb,MAAM,SAAS,CAAC;AAEjB,2DAA2D;AAC3D,eAAO,MAAM,sBAAsB,KAAK,CAAC;AACzC,eAAO,MAAM,qBAAqB,KAAK,CAAC;AACxC,eAAO,MAAM,qBAAqB,KAAK,CAAC;AACxC,6EAA6E;AAC7E,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAErC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,mBAAmB,QAAqB,CAAC;AAEtD,qGAAqG;AACrG,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAC/C,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAE/C,eAAO,MAAM,aAAa,EAAE;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,aAAa,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,uBAAuB,EAAE,mBAAmB,CAAC;IAC7C,OAAO,EAAE,YAAY,GAAG,SAAS,CAAC;IAClC,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,qBAAqB,EAAE,OAAO,CAAC;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,UAAU,CAAC;IAClB,kBAAkB,EAAE,UAAU,CAAC;IAC/B,UAAU,EAAE,OAAO,CAAC;IACpB,cAAc,EAAE,OAAO,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../src/toast/constants.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,UAAU,EACV,aAAa,EACb,mBAAmB,EACnB,UAAU,EACV,YAAY,EACb,MAAM,SAAS,CAAC;AAEjB,2DAA2D;AAC3D,eAAO,MAAM,sBAAsB,KAAK,CAAC;AACzC,eAAO,MAAM,qBAAqB,KAAK,CAAC;AACxC,eAAO,MAAM,qBAAqB,KAAK,CAAC;AACxC,6EAA6E;AAC7E,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAErC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,mBAAmB,QAAqB,CAAC;AAEtD,qGAAqG;AACrG,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAC/C,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAE/C,eAAO,MAAM,aAAa,EAAE;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,aAAa,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,uBAAuB,EAAE,mBAAmB,CAAC;IAC7C,OAAO,EAAE,YAAY,GAAG,SAAS,CAAC;IAClC,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,qBAAqB,EAAE,OAAO,CAAC;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,UAAU,CAAC;IAClB,kBAAkB,EAAE,UAAU,CAAC;IAC/B,UAAU,EAAE,OAAO,CAAC;IACpB,cAAc,EAAE,OAAO,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,OAAO,CAAC;CA+C3B,CAAC"}
|
|
@@ -20,10 +20,25 @@ declare class ToastStore {
|
|
|
20
20
|
private pendingPromises;
|
|
21
21
|
private collapseCooldown;
|
|
22
22
|
private collapseCooldownTimeout;
|
|
23
|
+
private expansionHold;
|
|
23
24
|
subscribe: (callback: Subscriber) => () => void;
|
|
24
25
|
/** Stable while nothing has changed — safe as a `useSyncExternalStore` snapshot. */
|
|
25
26
|
getSnapshot: () => ToastStoreState;
|
|
26
27
|
setConfig: (config: ToastStoreConfig) => void;
|
|
28
|
+
/**
|
|
29
|
+
* Something outside the store can HOLD an expanded stack open — on web, a pointer
|
|
30
|
+
* resting on it. Registered by the platform trigger (`use-stack-hover`) so this
|
|
31
|
+
* module stays pure JS with no DOM and no platform branch; native registers
|
|
32
|
+
* nothing and the predicate stays `null`.
|
|
33
|
+
*
|
|
34
|
+
* It is asked at DECISION time rather than tracked as state on purpose: a row can
|
|
35
|
+
* stop being under the pointer with no event at all — rows unmount under the
|
|
36
|
+
* cursor and move under a stationary one, and neither fires `pointerleave` —
|
|
37
|
+
* so any cached boolean goes stale. Nothing renders from this, so it is
|
|
38
|
+
* deliberately not part of the snapshot.
|
|
39
|
+
*/
|
|
40
|
+
setExpansionHold: (hold: (() => boolean) | null) => void;
|
|
41
|
+
private isHeldOpen;
|
|
27
42
|
private notify;
|
|
28
43
|
private cloneIndex;
|
|
29
44
|
private resolveDuration;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toast-store.d.ts","sourceRoot":"","sources":["../../../../src/toast/toast-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,OAAO,KAAK,EACV,UAAU,EACV,QAAQ,EACR,gBAAgB,EAChB,eAAe,EAChB,MAAM,SAAS,CAAC;AAEjB,KAAK,UAAU,GAAG,MAAM,IAAI,CAAC;AAK7B,cAAM,UAAU;IACd,OAAO,CAAC,KAAK,CAQX;IAEF,OAAO,CAAC,WAAW,CAAyB;IAC5C,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,kBAAkB,CAA8C;IACxE,OAAO,CAAC,eAAe,CAA8B;IACrD,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,uBAAuB,CAA8C;
|
|
1
|
+
{"version":3,"file":"toast-store.d.ts","sourceRoot":"","sources":["../../../../src/toast/toast-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,OAAO,KAAK,EACV,UAAU,EACV,QAAQ,EACR,gBAAgB,EAChB,eAAe,EAChB,MAAM,SAAS,CAAC;AAEjB,KAAK,UAAU,GAAG,MAAM,IAAI,CAAC;AAK7B,cAAM,UAAU;IACd,OAAO,CAAC,KAAK,CAQX;IAEF,OAAO,CAAC,WAAW,CAAyB;IAC5C,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,kBAAkB,CAA8C;IACxE,OAAO,CAAC,eAAe,CAA8B;IACrD,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,uBAAuB,CAA8C;IAC7E,OAAO,CAAC,aAAa,CAAgC;IAErD,SAAS,GAAI,UAAU,UAAU,gBAK/B;IAEF,oFAAoF;IACpF,WAAW,QAAO,eAAe,CAE/B;IAEF,SAAS,GAAI,QAAQ,gBAAgB,UAEnC;IAEF;;;;;;;;;;;OAWG;IACH,gBAAgB,GAAI,MAAM,CAAC,MAAM,OAAO,CAAC,GAAG,IAAI,UAE9C;IAEF,OAAO,CAAC,UAAU,CAAkD;IAEpE,OAAO,CAAC,MAAM,CAEZ;IAEF,OAAO,CAAC,UAAU,CAEhB;IAEF,OAAO,CAAC,eAAe,CAErB;IAEF,OAAO,CAAC,iBAAiB,CAQvB;IAEF,UAAU,GAAI,IAAI,MAAM,GAAG,MAAM,UAE/B;IAEF,WAAW,GAAI,IAAI,MAAM,GAAG,MAAM,UAEhC;IAEF,cAAc,aAEZ;IAEF,eAAe,aAEb;IAEF,OAAO,CAAC,aAAa,CAwCnB;IAEF,QAAQ,GACN,SAAS,IAAI,CACX,UAAU,EACV,IAAI,GAAG,gBAAgB,GAAG,OAAO,GAAG,iBAAiB,CACtD,GAAG;QACF,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;KACtB,KACA,MAAM,GAAG,MAAM,CAkHhB;IAEF,wCAAwC;IACxC,YAAY,GACV,IAAI,MAAM,GAAG,MAAM,GAAG,SAAS,EAC/B,SAAS,WAAW,GAAG,aAAa,KACnC,MAAM,GAAG,MAAM,GAAG,SAAS,CAyF5B;IAEF,4EAA4E;IAC5E,OAAO,CAAC,mBAAmB,CAazB;IAEF,WAAW,GAAI,IAAI,MAAM,GAAG,MAAM,UAahC;IAEF,WAAW,GACT,IAAI,MAAM,GAAG,MAAM,KAClB,KAAK,CAAC,SAAS,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,SAAS,CAE7C;IAEF,cAAc,GAAI,IAAI,MAAM,GAAG,MAAM,EAAE,QAAQ,MAAM,UAWnD;IAEF,MAAM,aAOJ;IAEF,QAAQ,aAiBN;IAEF,YAAY,aAUV;CACH;AAED,eAAO,MAAM,UAAU,YAAmB,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How long a leave waits for a neighbouring row to claim the pointer. One frame is
|
|
3
|
+
* enough for the same-tick leave/enter pair a row crossing produces; this is a
|
|
4
|
+
* little more so a slow frame cannot flash the stack shut. Exported so the suite
|
|
5
|
+
* asserts against the real value rather than a copy of it.
|
|
6
|
+
*/
|
|
7
|
+
export declare const HOVER_LEAVE_GRACE = 80;
|
|
8
|
+
type HoverEvent = {
|
|
9
|
+
nativeEvent: {
|
|
10
|
+
pointerType?: string;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Whether the pointer is over the stack RIGHT NOW.
|
|
15
|
+
*
|
|
16
|
+
* Two sources, and both are load-bearing:
|
|
17
|
+
*
|
|
18
|
+
* - the `hovered` latch answers "a mouse entered and has not left", which is the
|
|
19
|
+
* only thing available where there is no DOM (jest runs on `testEnvironment:
|
|
20
|
+
* 'node'`), and is a cheap early-out.
|
|
21
|
+
* - the live `:hover` query answers "a row is under the pointer", which the latch
|
|
22
|
+
* CANNOT: a row can stop being hovered with no event whatsoever. Measured — a row
|
|
23
|
+
* culled by `visibleToasts` under a stationary cursor fires no `pointerleave`,
|
|
24
|
+
* `pointerout` or `mouseleave` at all, and the restack then slides the remaining
|
|
25
|
+
* rows out from under the pointer. A latch alone therefore sticks TRUE, which
|
|
26
|
+
* would disable the press toggle for the rest of the session and suppress every
|
|
27
|
+
* later auto-collapse.
|
|
28
|
+
*
|
|
29
|
+
* Requiring both means a stale latch is corrected by the DOM and a stray DOM match
|
|
30
|
+
* (a pen, a touch) is rejected by the latch.
|
|
31
|
+
*
|
|
32
|
+
* COVERAGE, stated plainly: jest cannot see the DOM half at all
|
|
33
|
+
* (`testEnvironment: 'node'`), and a browser cannot isolate it either — its two
|
|
34
|
+
* consequences are a press toggle standing down (unreachable with a mouse, which
|
|
35
|
+
* always hovers what it presses) and a suppressed auto-collapse (which the deferred
|
|
36
|
+
* leave below independently corrects the moment any boundary event fires). Both
|
|
37
|
+
* halves are kept because each is measurably more accurate than the other alone, not
|
|
38
|
+
* because a test proves the composite.
|
|
39
|
+
*
|
|
40
|
+
* `ToastRow` asks so a press does not TOGGLE what hover already opened, and
|
|
41
|
+
* `toastStore` asks before auto-collapsing a stack down to its last row. The press
|
|
42
|
+
* still runs the toast's own `onPress`; only the expansion toggle steps aside.
|
|
43
|
+
*/
|
|
44
|
+
export declare function isStackHovered(): boolean;
|
|
45
|
+
export type StackHoverProps = {
|
|
46
|
+
onPointerEnter: (event: HoverEvent) => void;
|
|
47
|
+
onPointerLeave: (event: HoverEvent) => void;
|
|
48
|
+
};
|
|
49
|
+
export declare function useStackHover({ enableStacking, }: {
|
|
50
|
+
enableStacking: boolean;
|
|
51
|
+
}): StackHoverProps;
|
|
52
|
+
/** Test-only reset: the module state outlives a render tree, so a suite must clear it. */
|
|
53
|
+
export declare function resetStackHoverForTests(): void;
|
|
54
|
+
export {};
|
|
55
|
+
//# sourceMappingURL=use-stack-hover.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-stack-hover.d.ts","sourceRoot":"","sources":["../../../../src/toast/use-stack-hover.ts"],"names":[],"mappings":"AAwCA;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAKpC,KAAK,UAAU,GAAG;IAAE,WAAW,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AAc5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAExC;AAwBD,MAAM,MAAM,eAAe,GAAG;IAC5B,cAAc,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IAC5C,cAAc,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;CAC7C,CAAC;AAEF,wBAAgB,aAAa,CAAC,EAC5B,cAAc,GACf,EAAE;IACD,cAAc,EAAE,OAAO,CAAC;CACzB,GAAG,eAAe,CA4ClB;AAED,0FAA0F;AAC1F,wBAAgB,uBAAuB,IAAI,IAAI,CAG9C"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bloom-original — NATIVE. Metro resolves this over `use-stack-hover.ts` on iOS and
|
|
3
|
+
* Android, the same filename split `ToastHost.native.tsx` uses.
|
|
4
|
+
*
|
|
5
|
+
* Native keeps PRESS as the only way to expand a stack, so this hands back no
|
|
6
|
+
* handlers at all. Two reasons it is a fork rather than a runtime `Platform` check:
|
|
7
|
+
* the web file's module-level hover state and its `setTimeout` have no business
|
|
8
|
+
* existing in a native bundle, and `onPointerEnter`/`onPointerLeave` DO fire on
|
|
9
|
+
* native for touch (RN's pointer events unify touch and mouse), which would make
|
|
10
|
+
* every tap expand-then-collapse while the press toggle fired as well.
|
|
11
|
+
*
|
|
12
|
+
* Keep the shape identical to the web file — `ToastSwipeHandler` spreads whatever
|
|
13
|
+
* this returns onto the row box, and `ToastRow` calls `isStackHovered()`
|
|
14
|
+
* unconditionally.
|
|
15
|
+
*/
|
|
16
|
+
export type StackHoverProps = Record<string, never>;
|
|
17
|
+
/** Parity with the web file's export; nothing here waits for anything. */
|
|
18
|
+
export declare const HOVER_LEAVE_GRACE = 0;
|
|
19
|
+
/** Nothing hovers on a touch screen. */
|
|
20
|
+
export declare function isStackHovered(): boolean;
|
|
21
|
+
export declare function useStackHover(_options: {
|
|
22
|
+
enableStacking: boolean;
|
|
23
|
+
}): StackHoverProps;
|
|
24
|
+
/** Parity with the web file so the shared suite can call it on either platform. */
|
|
25
|
+
export declare function resetStackHoverForTests(): void;
|
|
26
|
+
//# sourceMappingURL=use-stack-hover.native.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-stack-hover.native.d.ts","sourceRoot":"","sources":["../../../../src/toast/use-stack-hover.native.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AAEpD,0EAA0E;AAC1E,eAAO,MAAM,iBAAiB,IAAI,CAAC;AAEnC,wCAAwC;AACxC,wBAAgB,cAAc,IAAI,OAAO,CAExC;AAKD,wBAAgB,aAAa,CAAC,QAAQ,EAAE;IAAE,cAAc,EAAE,OAAO,CAAA;CAAE,GAAG,eAAe,CAEpF;AAED,mFAAmF;AACnF,wBAAgB,uBAAuB,IAAI,IAAI,CAAG"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxyhq/bloom",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.56.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",
|
|
@@ -28,6 +28,17 @@
|
|
|
28
28
|
"default": "./lib/commonjs/index.js"
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
|
+
"./provider": {
|
|
32
|
+
"react-native": "./src/provider/index.tsx",
|
|
33
|
+
"import": {
|
|
34
|
+
"types": "./lib/typescript/module/provider/index.d.ts",
|
|
35
|
+
"default": "./lib/module/provider/index.js"
|
|
36
|
+
},
|
|
37
|
+
"require": {
|
|
38
|
+
"types": "./lib/typescript/commonjs/provider/index.d.ts",
|
|
39
|
+
"default": "./lib/commonjs/provider/index.js"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
31
42
|
"./surfaces": {
|
|
32
43
|
"react-native": "./src/surfaces/index.ts",
|
|
33
44
|
"browser": {
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// `BloomProvider` exists so an app mounts ONE Bloom root instead of five, and
|
|
6
|
+
// the concrete bug that motivated it is web-only: `useScrollRestoration()`
|
|
7
|
+
// THROWS outside `ScrollRestorationProvider`, so anything scrollable that an
|
|
8
|
+
// app renders beside — rather than under — that provider crashes the screen.
|
|
9
|
+
//
|
|
10
|
+
// These tests therefore assert the WEB binding: `provider/scroll-provider` is
|
|
11
|
+
// mapped to the web implementation the same way a web bundler resolves the
|
|
12
|
+
// `.web` sibling (jest has no platform-extension resolution of its own, so
|
|
13
|
+
// without this mapping the suite would silently exercise the native no-op and
|
|
14
|
+
// pass no matter what).
|
|
15
|
+
|
|
16
|
+
import { createElement, type ReactNode } from 'react';
|
|
17
|
+
import { act } from 'react';
|
|
18
|
+
import { createRoot, type Root } from 'react-dom/client';
|
|
19
|
+
|
|
20
|
+
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT =
|
|
21
|
+
true;
|
|
22
|
+
|
|
23
|
+
// Only the two navigation hooks `scroll/index.web` consumes; `virtual` keeps
|
|
24
|
+
// the real (native-heavy) expo-router off the resolver.
|
|
25
|
+
jest.mock(
|
|
26
|
+
'expo-router',
|
|
27
|
+
() => {
|
|
28
|
+
const react = jest.requireActual<typeof import('react')>('react');
|
|
29
|
+
return {
|
|
30
|
+
useFocusEffect: (effect: () => undefined | (() => void)) => {
|
|
31
|
+
react.useEffect(effect, [effect]);
|
|
32
|
+
},
|
|
33
|
+
useRoute: () => ({ key: 'route-test', name: 'Test', params: {} }),
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
{ virtual: true },
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
// What a web bundler does with `provider/scroll-provider.web.ts`.
|
|
40
|
+
jest.mock('../provider/scroll-provider', () => ({
|
|
41
|
+
ScrollRestorationProvider:
|
|
42
|
+
jest.requireActual<typeof import('../scroll/index.web')>('../scroll/index.web')
|
|
43
|
+
.ScrollRestorationProvider,
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
// Imported AFTER the mocks are registered.
|
|
47
|
+
import { BloomProvider } from '../provider';
|
|
48
|
+
import { useImageResolver } from '../image-resolver';
|
|
49
|
+
import { useScrollRestoration } from '../scroll/index.web';
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Stands in for any Bloom-consuming scrollable an app renders under the root —
|
|
53
|
+
* a feed, a right-rail replies list, an overlay.
|
|
54
|
+
*/
|
|
55
|
+
function Scrollable({ onResolve }: { onResolve?: (url: string | undefined) => void }): ReactNode {
|
|
56
|
+
useScrollRestoration('window');
|
|
57
|
+
const resolver = useImageResolver();
|
|
58
|
+
onResolve?.(resolver?.('file-id', 'avatar'));
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function render(node: ReactNode): { root: Root; container: HTMLElement } {
|
|
63
|
+
const container = document.createElement('div');
|
|
64
|
+
document.body.appendChild(container);
|
|
65
|
+
const root = createRoot(container);
|
|
66
|
+
act(() => {
|
|
67
|
+
root.render(node);
|
|
68
|
+
});
|
|
69
|
+
return { root, container };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function unmount({ root, container }: { root: Root; container: HTMLElement }): void {
|
|
73
|
+
act(() => {
|
|
74
|
+
root.unmount();
|
|
75
|
+
});
|
|
76
|
+
container.remove();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
describe('BloomProvider (web)', () => {
|
|
80
|
+
it('provides scroll restoration to everything below it', () => {
|
|
81
|
+
let resolved: string | undefined;
|
|
82
|
+
let mounted: { root: Root; container: HTMLElement } | undefined;
|
|
83
|
+
|
|
84
|
+
expect(() => {
|
|
85
|
+
mounted = render(
|
|
86
|
+
createElement(BloomProvider, {
|
|
87
|
+
fonts: false,
|
|
88
|
+
imageResolver: (id: string, variant?: string) => `${id}:${variant}`,
|
|
89
|
+
children: createElement(Scrollable, { onResolve: (url) => { resolved = url; } }),
|
|
90
|
+
}),
|
|
91
|
+
);
|
|
92
|
+
}).not.toThrow();
|
|
93
|
+
|
|
94
|
+
// The image resolver reaches the same subtree — one root, every context.
|
|
95
|
+
expect(resolved).toBe('file-id:avatar');
|
|
96
|
+
|
|
97
|
+
if (mounted) unmount(mounted);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Vacuity guard: proves the assertion above is actually load-bearing. If the
|
|
101
|
+
// provider ever stops mounting the WEB scroll provider (a bad platform
|
|
102
|
+
// binding, a dropped nesting level) this control case is what makes the test
|
|
103
|
+
// above meaningful — remove the provider and the very same child throws.
|
|
104
|
+
it('is what keeps the hook from throwing — the same child crashes without it', () => {
|
|
105
|
+
const consoleError = jest.spyOn(console, 'error').mockImplementation(() => undefined);
|
|
106
|
+
try {
|
|
107
|
+
expect(() => render(createElement(Scrollable, {}))).toThrow(
|
|
108
|
+
/useScrollRestoration must be used within a <ScrollRestorationProvider>/,
|
|
109
|
+
);
|
|
110
|
+
} finally {
|
|
111
|
+
consoleError.mockRestore();
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
});
|