@oxyhq/bloom 0.55.0 → 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/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/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/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/package.json +12 -1
- package/src/__tests__/BloomProvider.web.test.tsx +114 -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/README.md
CHANGED
|
@@ -24,9 +24,40 @@ Also required:
|
|
|
24
24
|
|
|
25
25
|
## Usage
|
|
26
26
|
|
|
27
|
+
### App root
|
|
28
|
+
|
|
29
|
+
Mount `BloomProvider` once, at the very top of the app. It composes every piece of
|
|
30
|
+
app-wide Bloom state — theme, haptics, image resolution, scroll restoration and the
|
|
31
|
+
tab-bar minimize progress — so none of them can end up at the wrong depth. It takes
|
|
32
|
+
all of `BloomThemeProvider`'s props plus `imageResolver` and `haptics`.
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import { BloomProvider } from '@oxyhq/bloom/provider';
|
|
36
|
+
|
|
37
|
+
<BloomProvider
|
|
38
|
+
defaultMode="system"
|
|
39
|
+
defaultColorPreset="blue"
|
|
40
|
+
persistKey="app.theme"
|
|
41
|
+
storage={storage}
|
|
42
|
+
imageResolver={(id, variant) => oxyServices.getFileDownloadUrl(id, variant)}
|
|
43
|
+
>
|
|
44
|
+
<App />
|
|
45
|
+
</BloomProvider>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Everything scrollable must be **under** it: on web `useScrollRestoration()` throws
|
|
49
|
+
outside its provider, so a list rendered beside the root (a right rail, an overlay)
|
|
50
|
+
crashes the screen.
|
|
51
|
+
|
|
52
|
+
Outlets are **not** included — their position in the tree is a real app decision, and
|
|
53
|
+
a second mount duplicates every surface they render. Mount these yourself, under
|
|
54
|
+
`BloomProvider`: `<ToastOutlet>`, `<Portal.Provider>`/`<Portal.Outlet>`,
|
|
55
|
+
`<SurfaceHost>`, `<BloomDialogProvider>`, `<AlertDialogHost>`.
|
|
56
|
+
|
|
27
57
|
### Theme
|
|
28
58
|
|
|
29
|
-
|
|
59
|
+
`BloomProvider` already mounts `BloomThemeProvider`; mount it directly only when you
|
|
60
|
+
need a nested/scoped theme. It accepts controlled `mode` and `colorPreset` props — persist them however you like (AsyncStorage, Zustand, etc.).
|
|
30
61
|
|
|
31
62
|
```tsx
|
|
32
63
|
import { BloomThemeProvider } from '@oxyhq/bloom/theme';
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.BloomProvider = BloomProvider;
|
|
7
|
+
var _index = require("../image-resolver/index.js");
|
|
8
|
+
var _useHaptics = require("../hooks/useHaptics.js");
|
|
9
|
+
var _minimizeContext = require("../tab-bar/minimize-context.js");
|
|
10
|
+
var _index2 = require("../theme/index.js");
|
|
11
|
+
var _scrollProvider = require("./scroll-provider");
|
|
12
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
13
|
+
/**
|
|
14
|
+
* `BloomProvider` — the ONE Bloom root an app mounts.
|
|
15
|
+
*
|
|
16
|
+
* Bloom's app-wide state used to be a handful of separate providers that every
|
|
17
|
+
* consumer wired by hand (theme, haptics, image resolution, scroll restoration,
|
|
18
|
+
* tab-bar minimize progress). Mounting them separately means each one can end
|
|
19
|
+
* up at a different depth, and a provider mounted too low fails in ways that
|
|
20
|
+
* are hard to trace:
|
|
21
|
+
*
|
|
22
|
+
* - `useScrollRestoration()` THROWS on web outside `ScrollRestorationProvider`,
|
|
23
|
+
* so any scrollable rendered beside the provider (a right rail, an overlay)
|
|
24
|
+
* crashes the screen.
|
|
25
|
+
* - `useMinimizeState()` silently hands each caller a private fallback, so a
|
|
26
|
+
* tab bar below the provider just never minimizes — no error anywhere.
|
|
27
|
+
*
|
|
28
|
+
* Mounting this single provider at the app root makes both classes of mistake
|
|
29
|
+
* impossible: everything Bloom renders is under all of them, at the same depth.
|
|
30
|
+
* Nesting extra contexts costs nothing at runtime — the win is that scope is no
|
|
31
|
+
* longer a per-app decision.
|
|
32
|
+
*
|
|
33
|
+
* NOT included, on purpose — these are OUTLETS, not state, and their placement
|
|
34
|
+
* in the tree is a real app decision (z-order, safe areas, and mounting a
|
|
35
|
+
* second one duplicates every surface it renders):
|
|
36
|
+
* `<ToastOutlet>`, `<Portal.Provider>`/`<Portal.Outlet>`, `<SurfaceHost>`,
|
|
37
|
+
* `<BloomDialogProvider>`, `<AlertDialogHost>`.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
function BloomProvider({
|
|
41
|
+
children,
|
|
42
|
+
imageResolver,
|
|
43
|
+
haptics = true,
|
|
44
|
+
...themeProps
|
|
45
|
+
}) {
|
|
46
|
+
return (
|
|
47
|
+
/*#__PURE__*/
|
|
48
|
+
// `value` is passed unconditionally (null when unset) so toggling a resolver
|
|
49
|
+
// never changes the tree shape and remounts everything below it.
|
|
50
|
+
(0, _jsxRuntime.jsx)(_index.ImageResolverProvider, {
|
|
51
|
+
value: imageResolver ?? null,
|
|
52
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_index2.BloomThemeProvider, {
|
|
53
|
+
...themeProps,
|
|
54
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_scrollProvider.ScrollRestorationProvider, {
|
|
55
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_useHaptics.BloomHapticsProvider, {
|
|
56
|
+
enabled: haptics,
|
|
57
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_minimizeContext.TabBarMinimizeProvider, {
|
|
58
|
+
children: children
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
BloomProvider.displayName = 'BloomProvider';
|
|
67
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_index","require","_useHaptics","_minimizeContext","_index2","_scrollProvider","_jsxRuntime","BloomProvider","children","imageResolver","haptics","themeProps","jsx","ImageResolverProvider","value","BloomThemeProvider","ScrollRestorationProvider","BloomHapticsProvider","enabled","TabBarMinimizeProvider","displayName"],"sourceRoot":"../../../src","sources":["provider/index.tsx"],"mappings":";;;;;;AA4BA,IAAAA,MAAA,GAAAC,OAAA;AACA,IAAAC,WAAA,GAAAD,OAAA;AACA,IAAAE,gBAAA,GAAAF,OAAA;AACA,IAAAG,OAAA,GAAAH,OAAA;AACA,IAAAI,eAAA,GAAAJ,OAAA;AAA8D,IAAAK,WAAA,GAAAL,OAAA;AAhC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAqBO,SAASM,aAAaA,CAAC;EAC5BC,QAAQ;EACRC,aAAa;EACbC,OAAO,GAAG,IAAI;EACd,GAAGC;AACe,CAAC,EAAE;EACrB;IAAA;IACE;IACA;IACA,IAAAL,WAAA,CAAAM,GAAA,EAACZ,MAAA,CAAAa,qBAAqB;MAACC,KAAK,EAAEL,aAAa,IAAI,IAAK;MAAAD,QAAA,eAClD,IAAAF,WAAA,CAAAM,GAAA,EAACR,OAAA,CAAAW,kBAAkB;QAAA,GAAKJ,UAAU;QAAAH,QAAA,eAChC,IAAAF,WAAA,CAAAM,GAAA,EAACP,eAAA,CAAAW,yBAAyB;UAAAR,QAAA,eACxB,IAAAF,WAAA,CAAAM,GAAA,EAACV,WAAA,CAAAe,oBAAoB;YAACC,OAAO,EAAER,OAAQ;YAAAF,QAAA,eACrC,IAAAF,WAAA,CAAAM,GAAA,EAACT,gBAAA,CAAAgB,sBAAsB;cAAAX,QAAA,EAAEA;YAAQ,CAAyB;UAAC,CACvC;QAAC,CACE;MAAC,CACV;IAAC,CACA;EAAC;AAE5B;AAEAD,aAAa,CAACa,WAAW,GAAG,eAAe","ignoreList":[]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
Object.defineProperty(exports, "ScrollRestorationProvider", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function () {
|
|
9
|
+
return _index.ScrollRestorationProvider;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
var _index = require("../scroll/index.js");
|
|
13
|
+
//# sourceMappingURL=scroll-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_index","require"],"sourceRoot":"../../../src","sources":["provider/scroll-provider.ts"],"mappings":";;;;;;;;;;;AAYA,IAAAA,MAAA,GAAAC,OAAA","ignoreList":[]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
Object.defineProperty(exports, "ScrollRestorationProvider", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function () {
|
|
9
|
+
return _indexWeb.ScrollRestorationProvider;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
var _indexWeb = require("../scroll/index.web.js");
|
|
13
|
+
//# sourceMappingURL=scroll-provider.web.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_indexWeb","require"],"sourceRoot":"../../../src","sources":["provider/scroll-provider.web.ts"],"mappings":";;;;;;;;;;;AACA,IAAAA,SAAA,GAAAC,OAAA","ignoreList":[]}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `BloomProvider` — the ONE Bloom root an app mounts.
|
|
5
|
+
*
|
|
6
|
+
* Bloom's app-wide state used to be a handful of separate providers that every
|
|
7
|
+
* consumer wired by hand (theme, haptics, image resolution, scroll restoration,
|
|
8
|
+
* tab-bar minimize progress). Mounting them separately means each one can end
|
|
9
|
+
* up at a different depth, and a provider mounted too low fails in ways that
|
|
10
|
+
* are hard to trace:
|
|
11
|
+
*
|
|
12
|
+
* - `useScrollRestoration()` THROWS on web outside `ScrollRestorationProvider`,
|
|
13
|
+
* so any scrollable rendered beside the provider (a right rail, an overlay)
|
|
14
|
+
* crashes the screen.
|
|
15
|
+
* - `useMinimizeState()` silently hands each caller a private fallback, so a
|
|
16
|
+
* tab bar below the provider just never minimizes — no error anywhere.
|
|
17
|
+
*
|
|
18
|
+
* Mounting this single provider at the app root makes both classes of mistake
|
|
19
|
+
* impossible: everything Bloom renders is under all of them, at the same depth.
|
|
20
|
+
* Nesting extra contexts costs nothing at runtime — the win is that scope is no
|
|
21
|
+
* longer a per-app decision.
|
|
22
|
+
*
|
|
23
|
+
* NOT included, on purpose — these are OUTLETS, not state, and their placement
|
|
24
|
+
* in the tree is a real app decision (z-order, safe areas, and mounting a
|
|
25
|
+
* second one duplicates every surface it renders):
|
|
26
|
+
* `<ToastOutlet>`, `<Portal.Provider>`/`<Portal.Outlet>`, `<SurfaceHost>`,
|
|
27
|
+
* `<BloomDialogProvider>`, `<AlertDialogHost>`.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { ImageResolverProvider } from "../image-resolver/index.js";
|
|
31
|
+
import { BloomHapticsProvider } from "../hooks/useHaptics.js";
|
|
32
|
+
import { TabBarMinimizeProvider } from "../tab-bar/minimize-context.js";
|
|
33
|
+
import { BloomThemeProvider } from "../theme/index.js";
|
|
34
|
+
import { ScrollRestorationProvider } from './scroll-provider';
|
|
35
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
36
|
+
export function BloomProvider({
|
|
37
|
+
children,
|
|
38
|
+
imageResolver,
|
|
39
|
+
haptics = true,
|
|
40
|
+
...themeProps
|
|
41
|
+
}) {
|
|
42
|
+
return (
|
|
43
|
+
/*#__PURE__*/
|
|
44
|
+
// `value` is passed unconditionally (null when unset) so toggling a resolver
|
|
45
|
+
// never changes the tree shape and remounts everything below it.
|
|
46
|
+
_jsx(ImageResolverProvider, {
|
|
47
|
+
value: imageResolver ?? null,
|
|
48
|
+
children: /*#__PURE__*/_jsx(BloomThemeProvider, {
|
|
49
|
+
...themeProps,
|
|
50
|
+
children: /*#__PURE__*/_jsx(ScrollRestorationProvider, {
|
|
51
|
+
children: /*#__PURE__*/_jsx(BloomHapticsProvider, {
|
|
52
|
+
enabled: haptics,
|
|
53
|
+
children: /*#__PURE__*/_jsx(TabBarMinimizeProvider, {
|
|
54
|
+
children: children
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
BloomProvider.displayName = 'BloomProvider';
|
|
63
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["ImageResolverProvider","BloomHapticsProvider","TabBarMinimizeProvider","BloomThemeProvider","ScrollRestorationProvider","jsx","_jsx","BloomProvider","children","imageResolver","haptics","themeProps","value","enabled","displayName"],"sourceRoot":"../../../src","sources":["provider/index.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;AACA;;AAGA,SAASA,qBAAqB,QAA4B,4BAAmB;AAC7E,SAASC,oBAAoB,QAAQ,wBAAqB;AAC1D,SAASC,sBAAsB,QAAQ,gCAA6B;AACpE,SAASC,kBAAkB,QAAsC,mBAAU;AAC3E,SAASC,yBAAyB,QAAQ,mBAAmB;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAc9D,OAAO,SAASC,aAAaA,CAAC;EAC5BC,QAAQ;EACRC,aAAa;EACbC,OAAO,GAAG,IAAI;EACd,GAAGC;AACe,CAAC,EAAE;EACrB;IAAA;IACE;IACA;IACAL,IAAA,CAACN,qBAAqB;MAACY,KAAK,EAAEH,aAAa,IAAI,IAAK;MAAAD,QAAA,eAClDF,IAAA,CAACH,kBAAkB;QAAA,GAAKQ,UAAU;QAAAH,QAAA,eAChCF,IAAA,CAACF,yBAAyB;UAAAI,QAAA,eACxBF,IAAA,CAACL,oBAAoB;YAACY,OAAO,EAAEH,OAAQ;YAAAF,QAAA,eACrCF,IAAA,CAACJ,sBAAsB;cAAAM,QAAA,EAAEA;YAAQ,CAAyB;UAAC,CACvC;QAAC,CACE;MAAC,CACV;IAAC,CACA;EAAC;AAE5B;AAEAD,aAAa,CAACO,WAAW,GAAG,eAAe","ignoreList":[]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Native/default binding for the scroll-restoration provider used by
|
|
5
|
+
* {@link BloomProvider}.
|
|
6
|
+
*
|
|
7
|
+
* `@oxyhq/bloom/scroll` is web-forked, so its `browser` export condition hands
|
|
8
|
+
* web consumers the real implementation while native gets the no-op. A compiled
|
|
9
|
+
* `lib/module/provider/index.js` cannot benefit from that condition (it imports
|
|
10
|
+
* a relative path, not the package subpath), so the platform choice is made
|
|
11
|
+
* here by FILENAME instead: Metro picks `scroll-provider.web.ts` on web, and
|
|
12
|
+
* every web bundler that resolves `.web.js` picks the compiled sibling. Same
|
|
13
|
+
* mechanism the toast engine uses for `ToastHost.native.tsx`.
|
|
14
|
+
*/
|
|
15
|
+
export { ScrollRestorationProvider } from "../scroll/index.js";
|
|
16
|
+
//# sourceMappingURL=scroll-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["ScrollRestorationProvider"],"sourceRoot":"../../../src","sources":["provider/scroll-provider.ts"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASA,yBAAyB,QAAQ,oBAAW","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["ScrollRestorationProvider"],"sourceRoot":"../../../src","sources":["provider/scroll-provider.web.ts"],"mappings":";;AAAA;AACA,SAASA,yBAAyB,QAAQ,wBAAqB","ignoreList":[]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `BloomProvider` — the ONE Bloom root an app mounts.
|
|
3
|
+
*
|
|
4
|
+
* Bloom's app-wide state used to be a handful of separate providers that every
|
|
5
|
+
* consumer wired by hand (theme, haptics, image resolution, scroll restoration,
|
|
6
|
+
* tab-bar minimize progress). Mounting them separately means each one can end
|
|
7
|
+
* up at a different depth, and a provider mounted too low fails in ways that
|
|
8
|
+
* are hard to trace:
|
|
9
|
+
*
|
|
10
|
+
* - `useScrollRestoration()` THROWS on web outside `ScrollRestorationProvider`,
|
|
11
|
+
* so any scrollable rendered beside the provider (a right rail, an overlay)
|
|
12
|
+
* crashes the screen.
|
|
13
|
+
* - `useMinimizeState()` silently hands each caller a private fallback, so a
|
|
14
|
+
* tab bar below the provider just never minimizes — no error anywhere.
|
|
15
|
+
*
|
|
16
|
+
* Mounting this single provider at the app root makes both classes of mistake
|
|
17
|
+
* impossible: everything Bloom renders is under all of them, at the same depth.
|
|
18
|
+
* Nesting extra contexts costs nothing at runtime — the win is that scope is no
|
|
19
|
+
* longer a per-app decision.
|
|
20
|
+
*
|
|
21
|
+
* NOT included, on purpose — these are OUTLETS, not state, and their placement
|
|
22
|
+
* in the tree is a real app decision (z-order, safe areas, and mounting a
|
|
23
|
+
* second one duplicates every surface it renders):
|
|
24
|
+
* `<ToastOutlet>`, `<Portal.Provider>`/`<Portal.Outlet>`, `<SurfaceHost>`,
|
|
25
|
+
* `<BloomDialogProvider>`, `<AlertDialogHost>`.
|
|
26
|
+
*/
|
|
27
|
+
import type { ReactNode } from 'react';
|
|
28
|
+
import { type ImageResolver } from '../image-resolver';
|
|
29
|
+
import { type BloomThemeProviderProps } from '../theme';
|
|
30
|
+
export interface BloomProviderProps extends Omit<BloomThemeProviderProps, 'children'> {
|
|
31
|
+
children: ReactNode;
|
|
32
|
+
/**
|
|
33
|
+
* Resolves bare media identifiers (Oxy file ids) to loadable URLs for every
|
|
34
|
+
* Bloom surface that takes a `source` — `<Avatar>`, image galleries, cards.
|
|
35
|
+
* Typically `(id, variant) => oxyServices.getFileDownloadUrl(id, variant)`.
|
|
36
|
+
*/
|
|
37
|
+
imageResolver?: ImageResolver;
|
|
38
|
+
/** `false` disables haptic feedback app-wide (honored by every `useHaptics()` call). */
|
|
39
|
+
haptics?: boolean;
|
|
40
|
+
}
|
|
41
|
+
export declare function BloomProvider({ children, imageResolver, haptics, ...themeProps }: BloomProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
42
|
+
export declare namespace BloomProvider {
|
|
43
|
+
var displayName: string;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/provider/index.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,EAAyB,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAG9E,OAAO,EAAsB,KAAK,uBAAuB,EAAE,MAAM,UAAU,CAAC;AAG5E,MAAM,WAAW,kBAAmB,SAAQ,IAAI,CAAC,uBAAuB,EAAE,UAAU,CAAC;IACnF,QAAQ,EAAE,SAAS,CAAC;IACpB;;;;OAIG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,wFAAwF;IACxF,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,aAAa,EACb,OAAc,EACd,GAAG,UAAU,EACd,EAAE,kBAAkB,2CAcpB;yBAnBe,aAAa"}
|
|
@@ -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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `BloomProvider` — the ONE Bloom root an app mounts.
|
|
3
|
+
*
|
|
4
|
+
* Bloom's app-wide state used to be a handful of separate providers that every
|
|
5
|
+
* consumer wired by hand (theme, haptics, image resolution, scroll restoration,
|
|
6
|
+
* tab-bar minimize progress). Mounting them separately means each one can end
|
|
7
|
+
* up at a different depth, and a provider mounted too low fails in ways that
|
|
8
|
+
* are hard to trace:
|
|
9
|
+
*
|
|
10
|
+
* - `useScrollRestoration()` THROWS on web outside `ScrollRestorationProvider`,
|
|
11
|
+
* so any scrollable rendered beside the provider (a right rail, an overlay)
|
|
12
|
+
* crashes the screen.
|
|
13
|
+
* - `useMinimizeState()` silently hands each caller a private fallback, so a
|
|
14
|
+
* tab bar below the provider just never minimizes — no error anywhere.
|
|
15
|
+
*
|
|
16
|
+
* Mounting this single provider at the app root makes both classes of mistake
|
|
17
|
+
* impossible: everything Bloom renders is under all of them, at the same depth.
|
|
18
|
+
* Nesting extra contexts costs nothing at runtime — the win is that scope is no
|
|
19
|
+
* longer a per-app decision.
|
|
20
|
+
*
|
|
21
|
+
* NOT included, on purpose — these are OUTLETS, not state, and their placement
|
|
22
|
+
* in the tree is a real app decision (z-order, safe areas, and mounting a
|
|
23
|
+
* second one duplicates every surface it renders):
|
|
24
|
+
* `<ToastOutlet>`, `<Portal.Provider>`/`<Portal.Outlet>`, `<SurfaceHost>`,
|
|
25
|
+
* `<BloomDialogProvider>`, `<AlertDialogHost>`.
|
|
26
|
+
*/
|
|
27
|
+
import type { ReactNode } from 'react';
|
|
28
|
+
import { type ImageResolver } from '../image-resolver';
|
|
29
|
+
import { type BloomThemeProviderProps } from '../theme';
|
|
30
|
+
export interface BloomProviderProps extends Omit<BloomThemeProviderProps, 'children'> {
|
|
31
|
+
children: ReactNode;
|
|
32
|
+
/**
|
|
33
|
+
* Resolves bare media identifiers (Oxy file ids) to loadable URLs for every
|
|
34
|
+
* Bloom surface that takes a `source` — `<Avatar>`, image galleries, cards.
|
|
35
|
+
* Typically `(id, variant) => oxyServices.getFileDownloadUrl(id, variant)`.
|
|
36
|
+
*/
|
|
37
|
+
imageResolver?: ImageResolver;
|
|
38
|
+
/** `false` disables haptic feedback app-wide (honored by every `useHaptics()` call). */
|
|
39
|
+
haptics?: boolean;
|
|
40
|
+
}
|
|
41
|
+
export declare function BloomProvider({ children, imageResolver, haptics, ...themeProps }: BloomProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
42
|
+
export declare namespace BloomProvider {
|
|
43
|
+
var displayName: string;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/provider/index.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,EAAyB,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAG9E,OAAO,EAAsB,KAAK,uBAAuB,EAAE,MAAM,UAAU,CAAC;AAG5E,MAAM,WAAW,kBAAmB,SAAQ,IAAI,CAAC,uBAAuB,EAAE,UAAU,CAAC;IACnF,QAAQ,EAAE,SAAS,CAAC;IACpB;;;;OAIG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,wFAAwF;IACxF,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,aAAa,EACb,OAAc,EACd,GAAG,UAAU,EACd,EAAE,kBAAkB,2CAcpB;yBAnBe,aAAa"}
|
|
@@ -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"}
|
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
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `BloomProvider` — the ONE Bloom root an app mounts.
|
|
3
|
+
*
|
|
4
|
+
* Bloom's app-wide state used to be a handful of separate providers that every
|
|
5
|
+
* consumer wired by hand (theme, haptics, image resolution, scroll restoration,
|
|
6
|
+
* tab-bar minimize progress). Mounting them separately means each one can end
|
|
7
|
+
* up at a different depth, and a provider mounted too low fails in ways that
|
|
8
|
+
* are hard to trace:
|
|
9
|
+
*
|
|
10
|
+
* - `useScrollRestoration()` THROWS on web outside `ScrollRestorationProvider`,
|
|
11
|
+
* so any scrollable rendered beside the provider (a right rail, an overlay)
|
|
12
|
+
* crashes the screen.
|
|
13
|
+
* - `useMinimizeState()` silently hands each caller a private fallback, so a
|
|
14
|
+
* tab bar below the provider just never minimizes — no error anywhere.
|
|
15
|
+
*
|
|
16
|
+
* Mounting this single provider at the app root makes both classes of mistake
|
|
17
|
+
* impossible: everything Bloom renders is under all of them, at the same depth.
|
|
18
|
+
* Nesting extra contexts costs nothing at runtime — the win is that scope is no
|
|
19
|
+
* longer a per-app decision.
|
|
20
|
+
*
|
|
21
|
+
* NOT included, on purpose — these are OUTLETS, not state, and their placement
|
|
22
|
+
* in the tree is a real app decision (z-order, safe areas, and mounting a
|
|
23
|
+
* second one duplicates every surface it renders):
|
|
24
|
+
* `<ToastOutlet>`, `<Portal.Provider>`/`<Portal.Outlet>`, `<SurfaceHost>`,
|
|
25
|
+
* `<BloomDialogProvider>`, `<AlertDialogHost>`.
|
|
26
|
+
*/
|
|
27
|
+
import type { ReactNode } from 'react';
|
|
28
|
+
|
|
29
|
+
import { ImageResolverProvider, type ImageResolver } from '../image-resolver';
|
|
30
|
+
import { BloomHapticsProvider } from '../hooks/useHaptics';
|
|
31
|
+
import { TabBarMinimizeProvider } from '../tab-bar/minimize-context';
|
|
32
|
+
import { BloomThemeProvider, type BloomThemeProviderProps } from '../theme';
|
|
33
|
+
import { ScrollRestorationProvider } from './scroll-provider';
|
|
34
|
+
|
|
35
|
+
export interface BloomProviderProps extends Omit<BloomThemeProviderProps, 'children'> {
|
|
36
|
+
children: ReactNode;
|
|
37
|
+
/**
|
|
38
|
+
* Resolves bare media identifiers (Oxy file ids) to loadable URLs for every
|
|
39
|
+
* Bloom surface that takes a `source` — `<Avatar>`, image galleries, cards.
|
|
40
|
+
* Typically `(id, variant) => oxyServices.getFileDownloadUrl(id, variant)`.
|
|
41
|
+
*/
|
|
42
|
+
imageResolver?: ImageResolver;
|
|
43
|
+
/** `false` disables haptic feedback app-wide (honored by every `useHaptics()` call). */
|
|
44
|
+
haptics?: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function BloomProvider({
|
|
48
|
+
children,
|
|
49
|
+
imageResolver,
|
|
50
|
+
haptics = true,
|
|
51
|
+
...themeProps
|
|
52
|
+
}: BloomProviderProps) {
|
|
53
|
+
return (
|
|
54
|
+
// `value` is passed unconditionally (null when unset) so toggling a resolver
|
|
55
|
+
// never changes the tree shape and remounts everything below it.
|
|
56
|
+
<ImageResolverProvider value={imageResolver ?? null}>
|
|
57
|
+
<BloomThemeProvider {...themeProps}>
|
|
58
|
+
<ScrollRestorationProvider>
|
|
59
|
+
<BloomHapticsProvider enabled={haptics}>
|
|
60
|
+
<TabBarMinimizeProvider>{children}</TabBarMinimizeProvider>
|
|
61
|
+
</BloomHapticsProvider>
|
|
62
|
+
</ScrollRestorationProvider>
|
|
63
|
+
</BloomThemeProvider>
|
|
64
|
+
</ImageResolverProvider>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
BloomProvider.displayName = 'BloomProvider';
|
|
@@ -0,0 +1,13 @@
|
|
|
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';
|