@symbiote-native/splash-screen 0.1.1 → 0.1.3
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/build/angular/index.d.ts +3 -0
- package/build/angular/index.js +5 -0
- package/build/angular/services/hide-animation.service.d.ts +6 -0
- package/build/angular/services/hide-animation.service.js +74 -0
- package/build/core/compute-hide-animation-styles.d.ts +3 -0
- package/build/core/compute-hide-animation-styles.js +77 -0
- package/build/core/hide-animation-controller.d.ts +10 -0
- package/build/core/hide-animation-controller.js +50 -0
- package/build/core/hide.d.ts +1 -0
- package/build/core/hide.js +4 -0
- package/build/core/index.d.ts +5 -0
- package/build/core/index.js +4 -0
- package/build/core/native-module.d.ts +2 -0
- package/build/core/native-module.js +12 -0
- package/build/core/types.d.ts +51 -0
- package/build/core/types.js +1 -0
- package/build/react/hooks/use-hide-animation.d.ts +2 -0
- package/build/react/hooks/use-hide-animation.js +29 -0
- package/build/react/index.d.ts +3 -0
- package/build/react/index.js +7 -0
- package/build/vue/composables/use-hide-animation.d.ts +3 -0
- package/build/vue/composables/use-hide-animation.js +21 -0
- package/build/vue/index.d.ts +3 -0
- package/build/vue/index.js +7 -0
- package/codegen-specs/NativeRNBootSplash.ts +15 -0
- package/codegen-specs/NativeRNBootSplash.web.ts +47 -0
- package/package.json +11 -10
- package/symbiote-splash-screen.podspec +4 -1
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// hide()/isVisible() carry zero framework dependency — re-exported verbatim from core, same as
|
|
2
|
+
// every other adapter. HideAnimationService is the Angular-only lifecycle half; the state
|
|
3
|
+
// machine, style computation, and native-constants read all live in core, shared with React/Vue.
|
|
4
|
+
export { hide, isVisible } from '../core';
|
|
5
|
+
export { HideAnimationService } from './services/hide-animation.service';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type Signal } from '@angular/core';
|
|
2
|
+
import { type IHideAnimationConfig, type IHideAnimationResult } from '../../core';
|
|
3
|
+
export declare class HideAnimationService {
|
|
4
|
+
private readonly injector;
|
|
5
|
+
connect(getConfig: () => IHideAnimationConfig): Signal<IHideAnimationResult>;
|
|
6
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
2
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
3
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
4
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
5
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
6
|
+
var _, done = false;
|
|
7
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
8
|
+
var context = {};
|
|
9
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
10
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
11
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
12
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
13
|
+
if (kind === "accessor") {
|
|
14
|
+
if (result === void 0) continue;
|
|
15
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
16
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
17
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
18
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
19
|
+
}
|
|
20
|
+
else if (_ = accept(result)) {
|
|
21
|
+
if (kind === "field") initializers.unshift(_);
|
|
22
|
+
else descriptor[key] = _;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
26
|
+
done = true;
|
|
27
|
+
};
|
|
28
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
29
|
+
var useValue = arguments.length > 2;
|
|
30
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
31
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
32
|
+
}
|
|
33
|
+
return useValue ? value : void 0;
|
|
34
|
+
};
|
|
35
|
+
import { computed, effect, inject, Injectable, Injector } from '@angular/core';
|
|
36
|
+
import { computeHideAnimationStyles, getHideAnimationConstants, HideAnimationController, } from '../../core';
|
|
37
|
+
// Angular twin of React's `useHideAnimation` hook and Vue's `useHideAnimation` composable.
|
|
38
|
+
// Angular has no per-instance hook — state and lifecycle live in DI instead, so `connect()`
|
|
39
|
+
// stands in for the hook's role: call it ONCE (typically from a component's field initializer,
|
|
40
|
+
// inside an injection context) with a GETTER, so it can keep reading the caller's own signals —
|
|
41
|
+
// the same reason Vue's composable also takes a getter rather than a value: there is no
|
|
42
|
+
// re-render/re-setup cycle here to hang a fresh read off of.
|
|
43
|
+
//
|
|
44
|
+
// readonly hideAnimation = inject(HideAnimationService).connect(() => this.config());
|
|
45
|
+
// // template: [style]="hideAnimation().container.style", (layout)="..." etc.
|
|
46
|
+
let HideAnimationService = (() => {
|
|
47
|
+
let _classDecorators = [Injectable({ providedIn: 'root' })];
|
|
48
|
+
let _classDescriptor;
|
|
49
|
+
let _classExtraInitializers = [];
|
|
50
|
+
let _classThis;
|
|
51
|
+
var HideAnimationService = class {
|
|
52
|
+
static { _classThis = this; }
|
|
53
|
+
static {
|
|
54
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
55
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
56
|
+
HideAnimationService = _classThis = _classDescriptor.value;
|
|
57
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
58
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
59
|
+
}
|
|
60
|
+
// Captured in the constructor (itself always run inside an injection context by Angular's
|
|
61
|
+
// own DI) so `connect()` can create an `effect()` even when called from plain field-initializer
|
|
62
|
+
// code that is not, on its own, an active injection context — mirrors create-tunnel.ts's
|
|
63
|
+
// `TunnelOut`, which passes its own captured `Injector` to `effect()` for the same reason.
|
|
64
|
+
injector = inject(Injector);
|
|
65
|
+
connect(getConfig) {
|
|
66
|
+
const controller = new HideAnimationController(getConfig());
|
|
67
|
+
const constants = getHideAnimationConstants();
|
|
68
|
+
effect(() => controller.updateConfig(getConfig()), { injector: this.injector });
|
|
69
|
+
return computed(() => computeHideAnimationStyles(getConfig(), constants, controller));
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
return HideAnimationService = _classThis;
|
|
73
|
+
})();
|
|
74
|
+
export { HideAnimationService };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { HideAnimationController } from './hide-animation-controller';
|
|
2
|
+
import type { IHideAnimationConfig, IHideAnimationConstants, IHideAnimationResult } from './types';
|
|
3
|
+
export declare function computeHideAnimationStyles(config: IHideAnimationConfig, constants: IHideAnimationConstants, controller: HideAnimationController): IHideAnimationResult;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Platform } from '@symbiote-native/engine';
|
|
2
|
+
import { isEdgeToEdge } from 'react-native-is-edge-to-edge';
|
|
3
|
+
// react-native-bootsplash also branches on Platform.OS === 'web' here (react-native-web
|
|
4
|
+
// support); SymbioteNative ships iOS + Android only, so that branch is dropped rather than
|
|
5
|
+
// ported dead.
|
|
6
|
+
const IS_EDGE_TO_EDGE = isEdgeToEdge();
|
|
7
|
+
const SKIPPED_IMAGE_SOURCE = -1;
|
|
8
|
+
// Faithful port of react-native-bootsplash's useHideAnimation style computation (its
|
|
9
|
+
// src/index.ts useMemo body): container/logo/brand prop bags the app binds to its own
|
|
10
|
+
// View/Image, unchanged across React/Vue/Angular since none of this touches lifecycle.
|
|
11
|
+
export function computeHideAnimationStyles(config, constants, controller) {
|
|
12
|
+
const { manifest, statusBarTranslucent, navigationBarTranslucent } = config;
|
|
13
|
+
const { darkModeEnabled, logoSizeRatio = 1, navigationBarHeight = 0, statusBarHeight = 0, } = constants;
|
|
14
|
+
const skipLogo = config.logo == null;
|
|
15
|
+
const skipBrand = manifest.brand == null || config.brand == null;
|
|
16
|
+
const backgroundColor = darkModeEnabled && manifest.darkBackground != null
|
|
17
|
+
? manifest.darkBackground
|
|
18
|
+
: manifest.background;
|
|
19
|
+
const logoSource = skipLogo
|
|
20
|
+
? undefined
|
|
21
|
+
: darkModeEnabled && config.darkLogo != null
|
|
22
|
+
? config.darkLogo
|
|
23
|
+
: config.logo;
|
|
24
|
+
const brandSource = skipBrand
|
|
25
|
+
? undefined
|
|
26
|
+
: darkModeEnabled && config.darkBrand != null
|
|
27
|
+
? config.darkBrand
|
|
28
|
+
: config.brand;
|
|
29
|
+
const isAndroid = Platform.OS === 'android';
|
|
30
|
+
const container = {
|
|
31
|
+
style: {
|
|
32
|
+
alignItems: 'center',
|
|
33
|
+
backgroundColor,
|
|
34
|
+
justifyContent: 'center',
|
|
35
|
+
position: 'absolute',
|
|
36
|
+
left: 0,
|
|
37
|
+
right: 0,
|
|
38
|
+
top: 0,
|
|
39
|
+
bottom: 0,
|
|
40
|
+
marginTop: isAndroid && !IS_EDGE_TO_EDGE && !(statusBarTranslucent ?? false)
|
|
41
|
+
? -statusBarHeight
|
|
42
|
+
: undefined,
|
|
43
|
+
marginBottom: isAndroid && !IS_EDGE_TO_EDGE && !(navigationBarTranslucent ?? false)
|
|
44
|
+
? -navigationBarHeight
|
|
45
|
+
: undefined,
|
|
46
|
+
},
|
|
47
|
+
onLayout: controller.onContainerLayout,
|
|
48
|
+
};
|
|
49
|
+
const logoSizeMultiplier = isAndroid ? logoSizeRatio : 1;
|
|
50
|
+
const logo = logoSource == null
|
|
51
|
+
? { source: SKIPPED_IMAGE_SOURCE }
|
|
52
|
+
: {
|
|
53
|
+
source: logoSource,
|
|
54
|
+
fadeDuration: 0,
|
|
55
|
+
resizeMode: 'contain',
|
|
56
|
+
style: {
|
|
57
|
+
width: manifest.logo.width * logoSizeMultiplier,
|
|
58
|
+
height: manifest.logo.height * logoSizeMultiplier,
|
|
59
|
+
},
|
|
60
|
+
onLoadEnd: controller.onLogoLoadEnd,
|
|
61
|
+
};
|
|
62
|
+
const brand = brandSource == null || manifest.brand == null
|
|
63
|
+
? { source: SKIPPED_IMAGE_SOURCE }
|
|
64
|
+
: {
|
|
65
|
+
source: brandSource,
|
|
66
|
+
fadeDuration: 0,
|
|
67
|
+
resizeMode: 'contain',
|
|
68
|
+
style: {
|
|
69
|
+
position: 'absolute',
|
|
70
|
+
bottom: manifest.brand.bottom,
|
|
71
|
+
width: manifest.brand.width,
|
|
72
|
+
height: manifest.brand.height,
|
|
73
|
+
},
|
|
74
|
+
onLoadEnd: controller.onBrandLoadEnd,
|
|
75
|
+
};
|
|
76
|
+
return { container, logo, brand };
|
|
77
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { IHideAnimationConfig } from './types';
|
|
2
|
+
export declare class HideAnimationController {
|
|
3
|
+
private readonly readiness;
|
|
4
|
+
constructor(config: IHideAnimationConfig);
|
|
5
|
+
updateConfig(config: IHideAnimationConfig): void;
|
|
6
|
+
readonly onContainerLayout: () => void;
|
|
7
|
+
readonly onLogoLoadEnd: () => void;
|
|
8
|
+
readonly onBrandLoadEnd: () => void;
|
|
9
|
+
private maybeRunAnimate;
|
|
10
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { hide } from './hide';
|
|
2
|
+
// Faithful port of react-native-bootsplash's useHideAnimation readiness gate (its
|
|
3
|
+
// src/index.ts): hide() fires exactly once, after layout + both images (if requested) +
|
|
4
|
+
// the caller all report ready, then the caller's own fade-out `animate()` runs. logoReady/
|
|
5
|
+
// brandReady are captured ONCE at construction (mirrors the original's useRef factory,
|
|
6
|
+
// evaluated only on first render) — a config that later drops its logo/brand source does
|
|
7
|
+
// NOT retroactively flip readiness back on, only updateConfig's animate/userReady do.
|
|
8
|
+
export class HideAnimationController {
|
|
9
|
+
readiness;
|
|
10
|
+
constructor(config) {
|
|
11
|
+
this.readiness = {
|
|
12
|
+
layoutReady: false,
|
|
13
|
+
logoReady: config.logo == null,
|
|
14
|
+
brandReady: config.manifest.brand == null || config.brand == null,
|
|
15
|
+
userReady: config.ready ?? true,
|
|
16
|
+
animate: config.animate,
|
|
17
|
+
animateHasBeenCalled: false,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
updateConfig(config) {
|
|
21
|
+
this.readiness.animate = config.animate;
|
|
22
|
+
this.readiness.userReady = config.ready ?? true;
|
|
23
|
+
this.maybeRunAnimate();
|
|
24
|
+
}
|
|
25
|
+
onContainerLayout = () => {
|
|
26
|
+
this.readiness.layoutReady = true;
|
|
27
|
+
this.maybeRunAnimate();
|
|
28
|
+
};
|
|
29
|
+
onLogoLoadEnd = () => {
|
|
30
|
+
this.readiness.logoReady = true;
|
|
31
|
+
this.maybeRunAnimate();
|
|
32
|
+
};
|
|
33
|
+
onBrandLoadEnd = () => {
|
|
34
|
+
this.readiness.brandReady = true;
|
|
35
|
+
this.maybeRunAnimate();
|
|
36
|
+
};
|
|
37
|
+
maybeRunAnimate() {
|
|
38
|
+
const state = this.readiness;
|
|
39
|
+
if (state.layoutReady &&
|
|
40
|
+
state.logoReady &&
|
|
41
|
+
state.brandReady &&
|
|
42
|
+
state.userReady &&
|
|
43
|
+
!state.animateHasBeenCalled) {
|
|
44
|
+
state.animateHasBeenCalled = true;
|
|
45
|
+
hide({ fade: false })
|
|
46
|
+
.then(() => state.animate())
|
|
47
|
+
.catch(() => { });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { hide, isVisible } from 'react-native-bootsplash';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// hide()/isVisible() carry zero React dependency upstream (react-native-bootsplash's own
|
|
2
|
+
// src/index.ts calls straight into its TurboModule) — framework-agnostic as-is, so every
|
|
3
|
+
// adapter re-exports this verbatim rather than re-wrapping it.
|
|
4
|
+
export { hide, isVisible } from 'react-native-bootsplash';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { hide, isVisible } from './hide';
|
|
2
|
+
export { HideAnimationController } from './hide-animation-controller';
|
|
3
|
+
export { computeHideAnimationStyles } from './compute-hide-animation-styles';
|
|
4
|
+
export { getHideAnimationConstants } from './native-module';
|
|
5
|
+
export type { IHideConfig, IManifest, IHideAnimationConfig, IHideAnimationContainerProps, IHideAnimationImageProps, IHideAnimationResult, IHideAnimationConstants, } from './types';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// react-native-bootsplash's package.json ships an `exports` map that only opens `.`,
|
|
2
|
+
// `./expo`, `./package.json`, `./app.plugin.js` — its internal TurboModule spec
|
|
3
|
+
// (dist/commonjs/specs/NativeRNBootSplash) is NOT a published subpath, so a deep import
|
|
4
|
+
// would throw ERR_PACKAGE_PATH_NOT_EXPORTED. getEnforcingNativeModule reads the same
|
|
5
|
+
// global TurboModule proxy the spec itself reads (by name), sidestepping the exports map
|
|
6
|
+
// entirely — this is how we reach getConstants(), which the package's public JS API
|
|
7
|
+
// (hide/isVisible/useHideAnimation) never exposes directly.
|
|
8
|
+
import { getEnforcingNativeModule } from '@symbiote-native/engine';
|
|
9
|
+
const RN_BOOT_SPLASH_MODULE_NAME = 'RNBootSplash';
|
|
10
|
+
export function getHideAnimationConstants() {
|
|
11
|
+
return getEnforcingNativeModule(RN_BOOT_SPLASH_MODULE_NAME).getConstants();
|
|
12
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { IImageSourceProp, IResizeMode } from '@symbiote-native/components';
|
|
2
|
+
import type { IStyleProp, IViewStyle } from '@symbiote-native/engine';
|
|
3
|
+
export type IHideConfig = {
|
|
4
|
+
fade?: boolean;
|
|
5
|
+
};
|
|
6
|
+
export type IManifest = {
|
|
7
|
+
background: string;
|
|
8
|
+
darkBackground?: string;
|
|
9
|
+
logo: {
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
};
|
|
13
|
+
brand?: {
|
|
14
|
+
bottom: number;
|
|
15
|
+
width: number;
|
|
16
|
+
height: number;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
export type IHideAnimationConfig = {
|
|
20
|
+
manifest: IManifest;
|
|
21
|
+
ready?: boolean;
|
|
22
|
+
logo?: IImageSourceProp;
|
|
23
|
+
darkLogo?: IImageSourceProp;
|
|
24
|
+
brand?: IImageSourceProp;
|
|
25
|
+
darkBrand?: IImageSourceProp;
|
|
26
|
+
animate: () => void;
|
|
27
|
+
statusBarTranslucent?: boolean;
|
|
28
|
+
navigationBarTranslucent?: boolean;
|
|
29
|
+
};
|
|
30
|
+
export type IHideAnimationContainerProps = {
|
|
31
|
+
style: IStyleProp<IViewStyle>;
|
|
32
|
+
onLayout: () => void;
|
|
33
|
+
};
|
|
34
|
+
export type IHideAnimationImageProps = {
|
|
35
|
+
source: IImageSourceProp;
|
|
36
|
+
fadeDuration?: number;
|
|
37
|
+
resizeMode?: IResizeMode;
|
|
38
|
+
style?: IStyleProp<IViewStyle>;
|
|
39
|
+
onLoadEnd?: () => void;
|
|
40
|
+
};
|
|
41
|
+
export type IHideAnimationResult = {
|
|
42
|
+
container: IHideAnimationContainerProps;
|
|
43
|
+
logo: IHideAnimationImageProps;
|
|
44
|
+
brand: IHideAnimationImageProps;
|
|
45
|
+
};
|
|
46
|
+
export type IHideAnimationConstants = {
|
|
47
|
+
darkModeEnabled: boolean;
|
|
48
|
+
logoSizeRatio?: number;
|
|
49
|
+
navigationBarHeight?: number;
|
|
50
|
+
statusBarHeight?: number;
|
|
51
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// React lifecycle wiring over the framework-agnostic HideAnimationController + style
|
|
2
|
+
// computation (core/) — mirrors the lifecycle-bucket naming convention of
|
|
3
|
+
// adapters/react/src/hooks. Faithful port of react-native-bootsplash's own useHideAnimation:
|
|
4
|
+
// the controller is constructed lazily ONCE (a useRef factory, never reconstructed across
|
|
5
|
+
// re-renders), native constants are read once via useState's lazy initializer (they never
|
|
6
|
+
// change), and an effect with NO dependency array re-syncs the controller's config after
|
|
7
|
+
// EVERY render — intentional, since `ready` flipping true only gets picked up this way.
|
|
8
|
+
//
|
|
9
|
+
// No useMemo around the style computation: upstream lists ~15 individual primitive fields
|
|
10
|
+
// (manifest.logo.width, backgroundColor, ...) as its memo's deps specifically so Object.is
|
|
11
|
+
// compares by VALUE, not by the `config`/`manifest` object's reference — callers construct
|
|
12
|
+
// that object fresh on every render (the normal way to call this hook), so keying a memo off
|
|
13
|
+
// the whole object would recompute every render anyway while looking like it memoizes.
|
|
14
|
+
// Reproducing the fine-grained list isn't worth it for a splash screen shown for a couple of
|
|
15
|
+
// renders at boot; computing plainly is honest about the actual cost and simpler to read.
|
|
16
|
+
import { useEffect, useRef, useState } from 'react';
|
|
17
|
+
import { computeHideAnimationStyles, getHideAnimationConstants, HideAnimationController, } from '../../core';
|
|
18
|
+
export function useHideAnimation(config) {
|
|
19
|
+
const controllerRef = useRef(null);
|
|
20
|
+
if (controllerRef.current == null) {
|
|
21
|
+
controllerRef.current = new HideAnimationController(config);
|
|
22
|
+
}
|
|
23
|
+
const controller = controllerRef.current;
|
|
24
|
+
const [constants] = useState(() => getHideAnimationConstants());
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
controller.updateConfig(config);
|
|
27
|
+
});
|
|
28
|
+
return computeHideAnimationStyles(config, constants, controller);
|
|
29
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// @symbiote-native/splash-screen/react: the React entry over the framework-agnostic core.
|
|
2
|
+
// hide/isVisible carry zero lifecycle and are re-exported verbatim; useHideAnimation wraps
|
|
3
|
+
// HideAnimationController + computeHideAnimationStyles with React's own lifecycle
|
|
4
|
+
// (hooks/use-hide-animation.ts) — mirrors the lifecycle-bucket naming convention of
|
|
5
|
+
// adapters/react/src/hooks (never `composables`, that's Vue's term).
|
|
6
|
+
export { hide, isVisible } from '../core';
|
|
7
|
+
export { useHideAnimation } from './hooks/use-hide-animation';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Vue lifecycle wiring over the framework-agnostic HideAnimationController + style
|
|
2
|
+
// computation (core/). A Vue composable's setup body runs ONCE (unlike a React hook,
|
|
3
|
+
// which re-runs every render), so this takes a config GETTER, not a plain value — Vue's
|
|
4
|
+
// reactivity tracks whatever reactive refs the getter reads internally and re-runs the
|
|
5
|
+
// watchEffect/computed below on their change, mirroring how a React consumer would pass a
|
|
6
|
+
// fresh config object on every render.
|
|
7
|
+
//
|
|
8
|
+
// The controller is a plain local, not a Vue ref: nothing needs Vue to react to the
|
|
9
|
+
// controller reference itself (only its methods are called), so wrapping it would only
|
|
10
|
+
// add an unnecessary reactive Proxy — same identity discipline as
|
|
11
|
+
// `use-color-scheme.ts`'s subscription handle.
|
|
12
|
+
import { computed, watchEffect } from '@vue/runtime-core';
|
|
13
|
+
import { computeHideAnimationStyles, getHideAnimationConstants, HideAnimationController, } from '../../core';
|
|
14
|
+
export function useHideAnimation(getConfig) {
|
|
15
|
+
const controller = new HideAnimationController(getConfig());
|
|
16
|
+
const constants = getHideAnimationConstants();
|
|
17
|
+
watchEffect(() => {
|
|
18
|
+
controller.updateConfig(getConfig());
|
|
19
|
+
});
|
|
20
|
+
return computed(() => computeHideAnimationStyles(getConfig(), constants, controller));
|
|
21
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// @symbiote-native/splash-screen/vue: the Vue entry over the framework-agnostic core.
|
|
2
|
+
// hide/isVisible carry zero lifecycle and are re-exported verbatim; useHideAnimation
|
|
3
|
+
// wraps HideAnimationController + computeHideAnimationStyles with Vue's own reactivity
|
|
4
|
+
// (composables/use-hide-animation.ts) — mirrors the lifecycle-bucket naming convention
|
|
5
|
+
// of adapters/vue/src/composables (never `hooks/`, that's React's term).
|
|
6
|
+
export { hide, isVisible } from '../core';
|
|
7
|
+
export { useHideAnimation } from './composables/use-hide-animation';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { TurboModule } from "react-native";
|
|
2
|
+
import { TurboModuleRegistry } from "react-native";
|
|
3
|
+
|
|
4
|
+
export interface Spec extends TurboModule {
|
|
5
|
+
getConstants(): {
|
|
6
|
+
darkModeEnabled: boolean;
|
|
7
|
+
logoSizeRatio?: number;
|
|
8
|
+
navigationBarHeight?: number;
|
|
9
|
+
statusBarHeight?: number;
|
|
10
|
+
};
|
|
11
|
+
hide(fade: boolean): Promise<void>;
|
|
12
|
+
isVisible(): boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default TurboModuleRegistry.getEnforcing<Spec>("RNBootSplash");
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Spec } from "./NativeRNBootSplash";
|
|
2
|
+
|
|
3
|
+
function resolveAfter(delay: number) {
|
|
4
|
+
return new Promise((resolve) => setTimeout(resolve, delay));
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function removeNode(node: Node | null) {
|
|
8
|
+
const parent = node?.parentNode;
|
|
9
|
+
|
|
10
|
+
if (node != null && parent != null) {
|
|
11
|
+
parent.removeChild(node);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default {
|
|
16
|
+
getConstants: () => ({
|
|
17
|
+
darkModeEnabled:
|
|
18
|
+
typeof window !== "undefined" &&
|
|
19
|
+
"matchMedia" in window &&
|
|
20
|
+
window.matchMedia("(prefers-color-scheme: dark)").matches,
|
|
21
|
+
}),
|
|
22
|
+
|
|
23
|
+
hide: (fade) =>
|
|
24
|
+
document.fonts.ready.then(() => {
|
|
25
|
+
const container = document.getElementById("bootsplash");
|
|
26
|
+
const style = document.getElementById("bootsplash-style");
|
|
27
|
+
|
|
28
|
+
if (container == null || !fade) {
|
|
29
|
+
removeNode(container);
|
|
30
|
+
removeNode(style);
|
|
31
|
+
} else {
|
|
32
|
+
container.style.transitionProperty = "opacity";
|
|
33
|
+
container.style.transitionDuration = "250ms";
|
|
34
|
+
container.style.opacity = "0";
|
|
35
|
+
|
|
36
|
+
return resolveAfter(250).then(() => {
|
|
37
|
+
removeNode(container);
|
|
38
|
+
removeNode(style);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}),
|
|
42
|
+
|
|
43
|
+
isVisible: () => {
|
|
44
|
+
const container = document.getElementById("bootsplash");
|
|
45
|
+
return container != null;
|
|
46
|
+
},
|
|
47
|
+
} satisfies Spec;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbiote-native/splash-screen",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Third-party splash-screen native module wrapped for SymbioteNative — one framework-agnostic core, built once and reachable from the React, Vue, and Angular adapters.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"src",
|
|
43
43
|
"build",
|
|
44
44
|
"build-ngc",
|
|
45
|
+
"codegen-specs",
|
|
45
46
|
"cli.cjs",
|
|
46
47
|
"react-native.config.cjs",
|
|
47
48
|
"symbiote-splash-screen.podspec"
|
|
@@ -52,7 +53,7 @@
|
|
|
52
53
|
"codegenConfig": {
|
|
53
54
|
"name": "RNBootSplashSpec",
|
|
54
55
|
"type": "modules",
|
|
55
|
-
"jsSrcsDir": "
|
|
56
|
+
"jsSrcsDir": "codegen-specs",
|
|
56
57
|
"android": {
|
|
57
58
|
"javaPackageName": "com.zoontek.rnbootsplash"
|
|
58
59
|
}
|
|
@@ -60,8 +61,8 @@
|
|
|
60
61
|
"dependencies": {
|
|
61
62
|
"react-native-bootsplash": "^7.3.2",
|
|
62
63
|
"react-native-is-edge-to-edge": "^1.3.1",
|
|
63
|
-
"@symbiote-native/components": "0.2.
|
|
64
|
-
"@symbiote-native/engine": "0.1.
|
|
64
|
+
"@symbiote-native/components": "0.2.3",
|
|
65
|
+
"@symbiote-native/engine": "0.1.4"
|
|
65
66
|
},
|
|
66
67
|
"peerDependencies": {
|
|
67
68
|
"@angular/core": ">=20",
|
|
@@ -69,9 +70,9 @@
|
|
|
69
70
|
"react": ">=19.0.0",
|
|
70
71
|
"react-native": ">=0.86",
|
|
71
72
|
"vue": ">=3.5.0",
|
|
72
|
-
"@symbiote-native/angular": "0.3.
|
|
73
|
-
"@symbiote-native/
|
|
74
|
-
"@symbiote-native/
|
|
73
|
+
"@symbiote-native/angular": "0.3.3",
|
|
74
|
+
"@symbiote-native/react": "0.2.4",
|
|
75
|
+
"@symbiote-native/vue": "0.3.3"
|
|
75
76
|
},
|
|
76
77
|
"peerDependenciesMeta": {
|
|
77
78
|
"@symbiote-native/angular": {
|
|
@@ -105,10 +106,10 @@
|
|
|
105
106
|
"@vue/runtime-core": "^3.5.13",
|
|
106
107
|
"react": "19.2.3",
|
|
107
108
|
"typescript": "~6.0.0",
|
|
108
|
-
"@symbiote-native/
|
|
109
|
-
"@symbiote-native/
|
|
109
|
+
"@symbiote-native/angular": "0.3.3",
|
|
110
|
+
"@symbiote-native/react": "0.2.4",
|
|
110
111
|
"@symbiote-native/test-utils": "0.1.5",
|
|
111
|
-
"@symbiote-native/vue": "0.3.
|
|
112
|
+
"@symbiote-native/vue": "0.3.3"
|
|
112
113
|
},
|
|
113
114
|
"scripts": {
|
|
114
115
|
"typecheck": "tsc --build",
|
|
@@ -3,7 +3,10 @@ require 'fileutils'
|
|
|
3
3
|
|
|
4
4
|
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
# __dir__ is the pod's app-facing node_modules symlink under pnpm; react-native-bootsplash
|
|
7
|
+
# only sits as a flat sibling in the REAL .pnpm store directory, so resolution must walk up
|
|
8
|
+
# from the realpath, not the symlink (walking up from the symlink never reaches it).
|
|
9
|
+
native_splash_screen_package_json = `node --print "require.resolve('react-native-bootsplash/package.json', { paths: [process.argv[1]] })" "#{File.realpath(__dir__)}"`.strip
|
|
7
10
|
native_splash_screen_root = File.dirname(native_splash_screen_package_json)
|
|
8
11
|
|
|
9
12
|
# CocoaPods' file glob never crosses a symlink (Sandbox::PathList#read_file_system walks the
|