@symbiote-native/splash-screen 3.0.3 → 3.1.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/build/angular/index.d.ts +1 -1
- package/build/angular/services/hide-animation.service/index.js +11 -12
- package/build/core/hide-animation-controller.d.ts +3 -1
- package/build/core/hide-animation-controller.js +35 -2
- package/build/core/index.d.ts +1 -1
- package/build/core/native-module.js +18 -0
- package/build/core/types.d.ts +6 -0
- package/build/react/hooks/use-hide-animation/index.js +14 -18
- package/build/react/index.d.ts +1 -1
- package/build/svelte/index.d.ts +3 -0
- package/build/svelte/index.js +7 -0
- package/build/svelte/runes/use-hide-animation.svelte.d.ts +4 -0
- package/build/svelte/runes/use-hide-animation.svelte.js +24 -0
- package/build/vue/composables/use-hide-animation/index.js +11 -12
- package/build/vue/index.d.ts +1 -1
- package/build-ngc/angular/index.d.ts +1 -1
- package/build-ngc/angular/services/hide-animation.service/index.js +14 -15
- package/build-ngc/angular/services/hide-animation.service/index.js.map +1 -1
- package/build-ngc/core/hide-animation-controller.d.ts +3 -1
- package/build-ngc/core/hide-animation-controller.js +35 -2
- package/build-ngc/core/hide-animation-controller.js.map +1 -1
- package/build-ngc/core/index.d.ts +1 -1
- package/build-ngc/core/native-module.js +18 -0
- package/build-ngc/core/native-module.js.map +1 -1
- package/build-ngc/core/types.d.ts +6 -0
- package/package.json +27 -13
- package/src/angular/index.ts +8 -1
- package/src/angular/services/hide-animation.service/index.ts +12 -12
- package/src/core/hide-animation-controller.ts +39 -3
- package/src/core/index.ts +2 -0
- package/src/core/native-module.ts +18 -0
- package/src/core/types.ts +19 -0
- package/src/react/hooks/use-hide-animation/index.ts +13 -20
- package/src/react/index.ts +8 -1
- package/src/svelte/index.ts +16 -0
- package/src/svelte/runes/use-hide-animation.svelte.ts +37 -0
- package/src/svelte/svelte-ambient.d.ts +6 -0
- package/src/vue/composables/use-hide-animation/index.ts +10 -12
- package/src/vue/index.ts +8 -1
- package/symbiote-splash-screen.podspec +8 -0
package/build/angular/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { hide, isVisible } from '../core';
|
|
2
2
|
export { HideAnimationService } from './services/hide-animation.service';
|
|
3
|
-
export type { IHideAnimationConfig, IHideAnimationResult, IManifest, IHideConfig } from '../core';
|
|
3
|
+
export type { IHideAnimationConfig, IHideAnimationFailure, IHideAnimationFailureStage, IHideAnimationResult, IManifest, IHideConfig, } from '../core';
|
|
@@ -33,13 +33,11 @@ var __runInitializers = (this && this.__runInitializers) || function (thisArg, i
|
|
|
33
33
|
return useValue ? value : void 0;
|
|
34
34
|
};
|
|
35
35
|
import { computed, effect, inject, Injectable, Injector } from '@angular/core';
|
|
36
|
-
import { computeHideAnimationStyles,
|
|
36
|
+
import { computeHideAnimationStyles, HideAnimationController, } from '../../../core/index.js';
|
|
37
37
|
// Angular twin of React's `useHideAnimation` hook and Vue's `useHideAnimation` composable.
|
|
38
|
-
// Angular has no per-instance hook
|
|
39
|
-
//
|
|
40
|
-
//
|
|
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.
|
|
38
|
+
// Angular has no per-instance hook, so `connect()` stands in for it: call it ONCE (typically
|
|
39
|
+
// from a component's field initializer, inside an injection context) with a GETTER so it can
|
|
40
|
+
// keep reading the caller's own signals — same reason Vue's composable takes a getter too.
|
|
43
41
|
//
|
|
44
42
|
// readonly hideAnimation = inject(HideAnimationService).connect(() => this.config());
|
|
45
43
|
// // template: [style]="hideAnimation().container.style", (layout)="..." etc.
|
|
@@ -57,16 +55,17 @@ let HideAnimationService = (() => {
|
|
|
57
55
|
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
58
56
|
__runInitializers(_classThis, _classExtraInitializers);
|
|
59
57
|
}
|
|
60
|
-
// Captured in the constructor (
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
// `TunnelOut`, which passes its own captured `Injector` to `effect()` for the same reason.
|
|
58
|
+
// Captured in the constructor (always run inside an injection context by Angular's own DI)
|
|
59
|
+
// so `connect()` can create an `effect()` even when called from field-initializer code that
|
|
60
|
+
// isn't itself an active injection context — mirrors create-tunnel.ts's `TunnelOut`.
|
|
64
61
|
injector = inject(Injector);
|
|
62
|
+
// The controller reads the native constants in its constructor, so a missing RNBootSplash
|
|
63
|
+
// throws out of connect() and aborts the host component's construction — deliberately: that
|
|
64
|
+
// is a build error, and a splash quietly stuck in light mode would ship unnoticed.
|
|
65
65
|
connect(getConfig) {
|
|
66
66
|
const controller = new HideAnimationController(getConfig());
|
|
67
|
-
const constants = getHideAnimationConstants();
|
|
68
67
|
effect(() => controller.updateConfig(getConfig()), { injector: this.injector });
|
|
69
|
-
return computed(() => computeHideAnimationStyles(getConfig(), constants, controller));
|
|
68
|
+
return computed(() => computeHideAnimationStyles(getConfig(), controller.constants, controller));
|
|
70
69
|
}
|
|
71
70
|
};
|
|
72
71
|
return HideAnimationService = _classThis;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import type { IHideAnimationConfig } from './types';
|
|
1
|
+
import type { IHideAnimationConfig, IHideAnimationConstants } from './types';
|
|
2
2
|
export declare class HideAnimationController {
|
|
3
|
+
readonly constants: IHideAnimationConstants;
|
|
3
4
|
private readonly readiness;
|
|
5
|
+
private readonly reportFailure;
|
|
4
6
|
constructor(config: IHideAnimationConfig);
|
|
5
7
|
updateConfig(config: IHideAnimationConfig): void;
|
|
6
8
|
readonly onContainerLayout: () => void;
|
|
@@ -1,12 +1,29 @@
|
|
|
1
|
+
import { dlog } from '@symbiote-native/engine';
|
|
1
2
|
import { hide } from './hide.js';
|
|
3
|
+
import { getHideAnimationConstants } from './native-module.js';
|
|
2
4
|
// Faithful port of react-native-bootsplash's useHideAnimation readiness gate (its
|
|
3
5
|
// src/index.ts): hide() fires exactly once, after layout + both images (if requested) +
|
|
4
6
|
// the caller all report ready, then the caller's own fade-out `animate()` runs. logoReady/
|
|
5
7
|
// brandReady are captured ONCE at construction (mirrors the original's useRef factory,
|
|
6
8
|
// evaluated only on first render) — a config that later drops its logo/brand source does
|
|
7
9
|
// NOT retroactively flip readiness back on, only updateConfig's animate/userReady do.
|
|
10
|
+
//
|
|
11
|
+
// DELIBERATE DIVERGENCE from upstream, one place only: upstream swallows a rejected hide() with a
|
|
12
|
+
// bare `.catch(() => {})`, which strands the app under a splash it can no longer dismiss (see
|
|
13
|
+
// maybeRunAnimate). We fail open and report instead. Everything else, the unguarded constants read
|
|
14
|
+
// included, stays a faithful port.
|
|
8
15
|
export class HideAnimationController {
|
|
16
|
+
// Read once here rather than once per adapter: the values never change over a splash's lifetime.
|
|
17
|
+
// Unguarded on purpose — a missing RNBootSplash is a build error that must stay loud, see
|
|
18
|
+
// native-module.ts for the full reasoning before wrapping this in anything.
|
|
19
|
+
constants;
|
|
9
20
|
readiness;
|
|
21
|
+
// dlog is DEBUG-gated, so it is the developer's seam, not the report: config.onError is the
|
|
22
|
+
// channel the app actually hears about this on.
|
|
23
|
+
reportFailure = (failure) => {
|
|
24
|
+
dlog(`splash-screen: hide animation degraded at "${failure.stage}": ${String(failure.error)}`);
|
|
25
|
+
this.readiness.onError?.(failure);
|
|
26
|
+
};
|
|
10
27
|
constructor(config) {
|
|
11
28
|
this.readiness = {
|
|
12
29
|
layoutReady: false,
|
|
@@ -14,11 +31,14 @@ export class HideAnimationController {
|
|
|
14
31
|
brandReady: config.manifest.brand == null || config.brand == null,
|
|
15
32
|
userReady: config.ready ?? true,
|
|
16
33
|
animate: config.animate,
|
|
34
|
+
onError: config.onError,
|
|
17
35
|
animateHasBeenCalled: false,
|
|
18
36
|
};
|
|
37
|
+
this.constants = getHideAnimationConstants();
|
|
19
38
|
}
|
|
20
39
|
updateConfig(config) {
|
|
21
40
|
this.readiness.animate = config.animate;
|
|
41
|
+
this.readiness.onError = config.onError;
|
|
22
42
|
this.readiness.userReady = config.ready ?? true;
|
|
23
43
|
this.maybeRunAnimate();
|
|
24
44
|
}
|
|
@@ -42,9 +62,22 @@ export class HideAnimationController {
|
|
|
42
62
|
state.userReady &&
|
|
43
63
|
!state.animateHasBeenCalled) {
|
|
44
64
|
state.animateHasBeenCalled = true;
|
|
65
|
+
// Fail open. animateHasBeenCalled is already true by the time hide() settles, so the gate
|
|
66
|
+
// is shut for good: on a rejection there is no readiness callback left that could retry,
|
|
67
|
+
// and skipping animate() means the caller's splash overlay never fades out — the app sits
|
|
68
|
+
// under it forever. Running animate() anyway costs at worst a fade over a native splash
|
|
69
|
+
// that is still up; not running it costs the whole app.
|
|
45
70
|
hide({ fade: false })
|
|
46
|
-
.
|
|
47
|
-
.
|
|
71
|
+
.catch((error) => {
|
|
72
|
+
this.reportFailure({ stage: 'hide', error });
|
|
73
|
+
})
|
|
74
|
+
.then(() => {
|
|
75
|
+
state.animate();
|
|
76
|
+
})
|
|
77
|
+
.catch((error) => {
|
|
78
|
+
// The caller's own animation throwing must not become an unhandled rejection either.
|
|
79
|
+
this.reportFailure({ stage: 'animate', error });
|
|
80
|
+
});
|
|
48
81
|
}
|
|
49
82
|
}
|
|
50
83
|
}
|
package/build/core/index.d.ts
CHANGED
|
@@ -2,4 +2,4 @@ export { hide, isVisible } from './hide';
|
|
|
2
2
|
export { HideAnimationController } from './hide-animation-controller';
|
|
3
3
|
export { computeHideAnimationStyles } from './compute-hide-animation-styles';
|
|
4
4
|
export { getHideAnimationConstants } from './native-module';
|
|
5
|
-
export type { IHideConfig, IManifest, IHideAnimationConfig, IHideAnimationContainerProps, IHideAnimationImageProps, IHideAnimationResult, IHideAnimationConstants, } from './types';
|
|
5
|
+
export type { IHideConfig, IManifest, IHideAnimationConfig, IHideAnimationContainerProps, IHideAnimationFailure, IHideAnimationFailureStage, IHideAnimationImageProps, IHideAnimationResult, IHideAnimationConstants, } from './types';
|
|
@@ -7,6 +7,24 @@
|
|
|
7
7
|
// (hide/isVisible/useHideAnimation) never exposes directly.
|
|
8
8
|
import { getEnforcingNativeModule } from '@symbiote-native/engine';
|
|
9
9
|
const RN_BOOT_SPLASH_MODULE_NAME = 'RNBootSplash';
|
|
10
|
+
// DELIBERATELY UNGUARDED — do not wrap this in a try/catch or hand it a fallback.
|
|
11
|
+
//
|
|
12
|
+
// A missing RNBootSplash is a BUILD error, not a runtime condition: the module is either linked
|
|
13
|
+
// into the binary or it is not, deterministically, on the very first launch. Upstream treats it
|
|
14
|
+
// the same way and does not soften it either — react-native-bootsplash's whole spec file is
|
|
15
|
+
// `TurboModuleRegistry.getEnforcing<Spec>("RNBootSplash")` (src/specs/NativeRNBootSplash.ts),
|
|
16
|
+
// which throws at module-IMPORT time, and its useHideAnimation calls `NativeModule.getConstants()`
|
|
17
|
+
// with no guard at all. `getEnforcing` exists precisely to make this loud.
|
|
18
|
+
//
|
|
19
|
+
// This repo has a live footgun that depends on it staying loud: `npm install` deletes the
|
|
20
|
+
// `.rn-bootsplash/` folder this package's podspec vendors at pod-install time (see CLAUDE.md), so
|
|
21
|
+
// a skipped `pod install` yields exactly this missing module. Degrading to light-mode defaults
|
|
22
|
+
// would turn that into "the splash looks fine, just never dark" and it would ship.
|
|
23
|
+
//
|
|
24
|
+
// Note the failure is currently invisible under @symbiote-native/react specifically: mount()
|
|
25
|
+
// (adapters/react/src/render.ts) wires the reconciler's uncaught-error callbacks to `noop`, so the
|
|
26
|
+
// throw never reaches the app. That is a defect in THAT adapter, tracked separately — it is not a
|
|
27
|
+
// reason to stop throwing here.
|
|
10
28
|
export function getHideAnimationConstants() {
|
|
11
29
|
return getEnforcingNativeModule(RN_BOOT_SPLASH_MODULE_NAME).getConstants();
|
|
12
30
|
}
|
package/build/core/types.d.ts
CHANGED
|
@@ -16,6 +16,11 @@ export type IManifest = {
|
|
|
16
16
|
height: number;
|
|
17
17
|
};
|
|
18
18
|
};
|
|
19
|
+
export type IHideAnimationFailureStage = 'hide' | 'animate';
|
|
20
|
+
export type IHideAnimationFailure = {
|
|
21
|
+
stage: IHideAnimationFailureStage;
|
|
22
|
+
error: unknown;
|
|
23
|
+
};
|
|
19
24
|
export type IHideAnimationConfig = {
|
|
20
25
|
manifest: IManifest;
|
|
21
26
|
ready?: boolean;
|
|
@@ -24,6 +29,7 @@ export type IHideAnimationConfig = {
|
|
|
24
29
|
brand?: IImageSourceProp;
|
|
25
30
|
darkBrand?: IImageSourceProp;
|
|
26
31
|
animate: () => void;
|
|
32
|
+
onError?: (failure: IHideAnimationFailure) => void;
|
|
27
33
|
statusBarTranslucent?: boolean;
|
|
28
34
|
navigationBarTranslucent?: boolean;
|
|
29
35
|
};
|
|
@@ -1,29 +1,25 @@
|
|
|
1
|
-
//
|
|
2
|
-
// computation
|
|
3
|
-
//
|
|
4
|
-
// the controller
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// EVERY render — intentional, since `ready` flipping true only gets picked up this way.
|
|
1
|
+
// Port of react-native-bootsplash's own useHideAnimation over the framework-agnostic
|
|
2
|
+
// HideAnimationController + style computation in core/. The controller is built lazily ONCE
|
|
3
|
+
// via a useRef factory (never reconstructed across re-renders); the effect below has NO
|
|
4
|
+
// dependency array and re-syncs the controller's config after EVERY render on purpose, since
|
|
5
|
+
// `ready` flipping true is only picked up that way. The native constants ride along on that same
|
|
6
|
+
// once-built controller, so there is no separate useState to hold them.
|
|
8
7
|
//
|
|
9
|
-
// No useMemo around the style computation: upstream
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
import { useEffect, useRef, useState } from 'react';
|
|
17
|
-
import { computeHideAnimationStyles, getHideAnimationConstants, HideAnimationController, } from '../../../core/index.js';
|
|
8
|
+
// No useMemo around the style computation: upstream memoizes on ~15 individual primitive
|
|
9
|
+
// fields so Object.is compares by value rather than by the config/manifest object's
|
|
10
|
+
// reference (callers construct that object fresh every render, so keying off the whole
|
|
11
|
+
// object would recompute anyway). Not worth reproducing for a splash screen shown a couple
|
|
12
|
+
// of renders at boot — computing plainly is simpler and equally cheap.
|
|
13
|
+
import { useEffect, useRef } from 'react';
|
|
14
|
+
import { computeHideAnimationStyles, HideAnimationController, } from '../../../core/index.js';
|
|
18
15
|
export function useHideAnimation(config) {
|
|
19
16
|
const controllerRef = useRef(null);
|
|
20
17
|
if (controllerRef.current == null) {
|
|
21
18
|
controllerRef.current = new HideAnimationController(config);
|
|
22
19
|
}
|
|
23
20
|
const controller = controllerRef.current;
|
|
24
|
-
const [constants] = useState(() => getHideAnimationConstants());
|
|
25
21
|
useEffect(() => {
|
|
26
22
|
controller.updateConfig(config);
|
|
27
23
|
});
|
|
28
|
-
return computeHideAnimationStyles(config, constants, controller);
|
|
24
|
+
return computeHideAnimationStyles(config, controller.constants, controller);
|
|
29
25
|
}
|
package/build/react/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { hide, isVisible } from '../core';
|
|
2
2
|
export { useHideAnimation } from './hooks/use-hide-animation';
|
|
3
|
-
export type { IHideAnimationConfig, IHideAnimationResult, IManifest, IHideConfig } from '../core';
|
|
3
|
+
export type { IHideAnimationConfig, IHideAnimationFailure, IHideAnimationFailureStage, IHideAnimationResult, IManifest, IHideConfig, } from '../core';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// @symbiote-native/splash-screen/svelte: the Svelte entry over the framework-agnostic core.
|
|
2
|
+
// hide/isVisible carry zero lifecycle and are re-exported verbatim; useHideAnimation wraps
|
|
3
|
+
// HideAnimationController + computeHideAnimationStyles with Svelte's own runes
|
|
4
|
+
// (runes/use-hide-animation.svelte.ts) — mirrors the lifecycle-bucket naming convention of
|
|
5
|
+
// adapters/svelte/src/runes (never `hooks`/`composables`, those are React's/Vue's terms).
|
|
6
|
+
export { hide, isVisible } from '../core/index.js';
|
|
7
|
+
export { useHideAnimation } from './runes/use-hide-animation.svelte';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Svelte lifecycle wiring over the framework-agnostic HideAnimationController + style
|
|
2
|
+
// computation (core/). A Svelte component's `<script>` body runs ONCE (like Vue's setup,
|
|
3
|
+
// unlike a React hook re-running every render), so this takes a config GETTER, not a plain
|
|
4
|
+
// value — the boxed-getter convention from use-color-scheme.svelte.ts. The controller stays
|
|
5
|
+
// a plain local, not `$state`: only its methods are called, nothing needs Svelte to react to
|
|
6
|
+
// the reference itself. `$effect` re-syncs the controller's config on every reactive read
|
|
7
|
+
// inside `getConfig()`, mirroring Vue's `watchEffect`. The controller also carries the native
|
|
8
|
+
// constants, read once in its constructor — so a missing RNBootSplash throws straight out of the
|
|
9
|
+
// component's script body, on purpose.
|
|
10
|
+
import { computeHideAnimationStyles, HideAnimationController, } from '../../core/index.js';
|
|
11
|
+
export function useHideAnimation(getConfig) {
|
|
12
|
+
const controller = new HideAnimationController(getConfig());
|
|
13
|
+
let result = $state(computeHideAnimationStyles(getConfig(), controller.constants, controller));
|
|
14
|
+
$effect(() => {
|
|
15
|
+
const config = getConfig();
|
|
16
|
+
controller.updateConfig(config);
|
|
17
|
+
result = computeHideAnimationStyles(config, controller.constants, controller);
|
|
18
|
+
});
|
|
19
|
+
return {
|
|
20
|
+
get current() {
|
|
21
|
+
return result;
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
// Vue lifecycle wiring over the framework-agnostic HideAnimationController + style
|
|
2
|
-
// computation (core/). A
|
|
3
|
-
//
|
|
4
|
-
// reactivity tracks whatever reactive refs the getter reads
|
|
5
|
-
// watchEffect/computed below on their change, mirroring
|
|
6
|
-
//
|
|
2
|
+
// computation (core/). A composable's setup body runs ONCE (unlike a React hook, which
|
|
3
|
+
// 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 and re-runs the
|
|
5
|
+
// watchEffect/computed below on their change, mirroring a React consumer passing a fresh
|
|
6
|
+
// config object every render.
|
|
7
7
|
//
|
|
8
|
-
// The controller is a plain local, not a Vue ref:
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
8
|
+
// The controller is a plain local, not a Vue ref: only its methods are called, so wrapping
|
|
9
|
+
// it would only add an unnecessary reactive Proxy — same identity discipline as
|
|
10
|
+
// `use-color-scheme.ts`'s subscription handle. It also carries the native constants, read once in
|
|
11
|
+
// its constructor — so a missing RNBootSplash throws straight out of setup, on purpose.
|
|
12
12
|
import { computed, watchEffect } from '@vue/runtime-core';
|
|
13
|
-
import { computeHideAnimationStyles,
|
|
13
|
+
import { computeHideAnimationStyles, HideAnimationController, } from '../../../core/index.js';
|
|
14
14
|
export function useHideAnimation(getConfig) {
|
|
15
15
|
const controller = new HideAnimationController(getConfig());
|
|
16
|
-
const constants = getHideAnimationConstants();
|
|
17
16
|
watchEffect(() => {
|
|
18
17
|
controller.updateConfig(getConfig());
|
|
19
18
|
});
|
|
20
|
-
return computed(() => computeHideAnimationStyles(getConfig(), constants, controller));
|
|
19
|
+
return computed(() => computeHideAnimationStyles(getConfig(), controller.constants, controller));
|
|
21
20
|
}
|
package/build/vue/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { hide, isVisible } from '../core';
|
|
2
2
|
export { useHideAnimation } from './composables/use-hide-animation';
|
|
3
|
-
export type { IHideAnimationConfig, IHideAnimationResult, IManifest, IHideConfig } from '../core';
|
|
3
|
+
export type { IHideAnimationConfig, IHideAnimationFailure, IHideAnimationFailureStage, IHideAnimationResult, IManifest, IHideConfig, } from '../core';
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { hide, isVisible } from '../core';
|
|
2
2
|
export { HideAnimationService } from './services/hide-animation.service';
|
|
3
|
-
export type { IHideAnimationConfig, IHideAnimationResult, IManifest, IHideConfig } from '../core';
|
|
3
|
+
export type { IHideAnimationConfig, IHideAnimationFailure, IHideAnimationFailureStage, IHideAnimationResult, IManifest, IHideConfig, } from '../core';
|
|
@@ -1,31 +1,30 @@
|
|
|
1
1
|
import { computed, effect, inject, Injectable, Injector } from '@angular/core';
|
|
2
|
-
import { computeHideAnimationStyles,
|
|
2
|
+
import { computeHideAnimationStyles, HideAnimationController, } from '../../../core';
|
|
3
3
|
import * as i0 from "@angular/core";
|
|
4
4
|
// Angular twin of React's `useHideAnimation` hook and Vue's `useHideAnimation` composable.
|
|
5
|
-
// Angular has no per-instance hook
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// the same reason Vue's composable also takes a getter rather than a value: there is no
|
|
9
|
-
// re-render/re-setup cycle here to hang a fresh read off of.
|
|
5
|
+
// Angular has no per-instance hook, so `connect()` stands in for it: call it ONCE (typically
|
|
6
|
+
// from a component's field initializer, inside an injection context) with a GETTER so it can
|
|
7
|
+
// keep reading the caller's own signals — same reason Vue's composable takes a getter too.
|
|
10
8
|
//
|
|
11
9
|
// readonly hideAnimation = inject(HideAnimationService).connect(() => this.config());
|
|
12
10
|
// // template: [style]="hideAnimation().container.style", (layout)="..." etc.
|
|
13
11
|
export class HideAnimationService {
|
|
14
|
-
// Captured in the constructor (
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
// `TunnelOut`, which passes its own captured `Injector` to `effect()` for the same reason.
|
|
12
|
+
// Captured in the constructor (always run inside an injection context by Angular's own DI)
|
|
13
|
+
// so `connect()` can create an `effect()` even when called from field-initializer code that
|
|
14
|
+
// isn't itself an active injection context — mirrors create-tunnel.ts's `TunnelOut`.
|
|
18
15
|
injector = inject(Injector);
|
|
16
|
+
// The controller reads the native constants in its constructor, so a missing RNBootSplash
|
|
17
|
+
// throws out of connect() and aborts the host component's construction — deliberately: that
|
|
18
|
+
// is a build error, and a splash quietly stuck in light mode would ship unnoticed.
|
|
19
19
|
connect(getConfig) {
|
|
20
20
|
const controller = new HideAnimationController(getConfig());
|
|
21
|
-
const constants = getHideAnimationConstants();
|
|
22
21
|
effect(() => controller.updateConfig(getConfig()), { injector: this.injector });
|
|
23
|
-
return computed(() => computeHideAnimationStyles(getConfig(), constants, controller));
|
|
22
|
+
return computed(() => computeHideAnimationStyles(getConfig(), controller.constants, controller));
|
|
24
23
|
}
|
|
25
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
26
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.
|
|
24
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HideAnimationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
25
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HideAnimationService, providedIn: 'root' });
|
|
27
26
|
}
|
|
28
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
27
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HideAnimationService, decorators: [{
|
|
29
28
|
type: Injectable,
|
|
30
29
|
args: [{ providedIn: 'root' }]
|
|
31
30
|
}] });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/angular/services/hide-animation.service/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAe,MAAM,eAAe,CAAC;AAC5F,OAAO,EACL,0BAA0B,EAC1B,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/angular/services/hide-animation.service/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAe,MAAM,eAAe,CAAC;AAC5F,OAAO,EACL,0BAA0B,EAC1B,uBAAuB,GAGxB,MAAM,eAAe,CAAC;;AAEvB,2FAA2F;AAC3F,6FAA6F;AAC7F,6FAA6F;AAC7F,2FAA2F;AAC3F,EAAE;AACF,wFAAwF;AACxF,gFAAgF;AAEhF,MAAM,OAAO,oBAAoB;IAC/B,2FAA2F;IAC3F,4FAA4F;IAC5F,qFAAqF;IACpE,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IAE7C,0FAA0F;IAC1F,4FAA4F;IAC5F,mFAAmF;IACnF,OAAO,CAAC,SAAqC;QAC3C,MAAM,UAAU,GAAG,IAAI,uBAAuB,CAAC,SAAS,EAAE,CAAC,CAAC;QAE5D,MAAM,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,SAAS,EAAE,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;QAEhF,OAAO,QAAQ,CAAC,GAAG,EAAE,CACnB,0BAA0B,CAAC,SAAS,EAAE,EAAE,UAAU,CAAC,SAAS,EAAE,UAAU,CAAC,CAC1E,CAAC;IACJ,CAAC;uGAjBU,oBAAoB;2GAApB,oBAAoB,cADP,MAAM;;2FACnB,oBAAoB;kBADhC,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import type { IHideAnimationConfig } from './types';
|
|
1
|
+
import type { IHideAnimationConfig, IHideAnimationConstants } from './types';
|
|
2
2
|
export declare class HideAnimationController {
|
|
3
|
+
readonly constants: IHideAnimationConstants;
|
|
3
4
|
private readonly readiness;
|
|
5
|
+
private readonly reportFailure;
|
|
4
6
|
constructor(config: IHideAnimationConfig);
|
|
5
7
|
updateConfig(config: IHideAnimationConfig): void;
|
|
6
8
|
readonly onContainerLayout: () => void;
|
|
@@ -1,12 +1,29 @@
|
|
|
1
|
+
import { dlog } from '@symbiote-native/engine';
|
|
1
2
|
import { hide } from './hide';
|
|
3
|
+
import { getHideAnimationConstants } from './native-module';
|
|
2
4
|
// Faithful port of react-native-bootsplash's useHideAnimation readiness gate (its
|
|
3
5
|
// src/index.ts): hide() fires exactly once, after layout + both images (if requested) +
|
|
4
6
|
// the caller all report ready, then the caller's own fade-out `animate()` runs. logoReady/
|
|
5
7
|
// brandReady are captured ONCE at construction (mirrors the original's useRef factory,
|
|
6
8
|
// evaluated only on first render) — a config that later drops its logo/brand source does
|
|
7
9
|
// NOT retroactively flip readiness back on, only updateConfig's animate/userReady do.
|
|
10
|
+
//
|
|
11
|
+
// DELIBERATE DIVERGENCE from upstream, one place only: upstream swallows a rejected hide() with a
|
|
12
|
+
// bare `.catch(() => {})`, which strands the app under a splash it can no longer dismiss (see
|
|
13
|
+
// maybeRunAnimate). We fail open and report instead. Everything else, the unguarded constants read
|
|
14
|
+
// included, stays a faithful port.
|
|
8
15
|
export class HideAnimationController {
|
|
16
|
+
// Read once here rather than once per adapter: the values never change over a splash's lifetime.
|
|
17
|
+
// Unguarded on purpose — a missing RNBootSplash is a build error that must stay loud, see
|
|
18
|
+
// native-module.ts for the full reasoning before wrapping this in anything.
|
|
19
|
+
constants;
|
|
9
20
|
readiness;
|
|
21
|
+
// dlog is DEBUG-gated, so it is the developer's seam, not the report: config.onError is the
|
|
22
|
+
// channel the app actually hears about this on.
|
|
23
|
+
reportFailure = (failure) => {
|
|
24
|
+
dlog(`splash-screen: hide animation degraded at "${failure.stage}": ${String(failure.error)}`);
|
|
25
|
+
this.readiness.onError?.(failure);
|
|
26
|
+
};
|
|
10
27
|
constructor(config) {
|
|
11
28
|
this.readiness = {
|
|
12
29
|
layoutReady: false,
|
|
@@ -14,11 +31,14 @@ export class HideAnimationController {
|
|
|
14
31
|
brandReady: config.manifest.brand == null || config.brand == null,
|
|
15
32
|
userReady: config.ready ?? true,
|
|
16
33
|
animate: config.animate,
|
|
34
|
+
onError: config.onError,
|
|
17
35
|
animateHasBeenCalled: false,
|
|
18
36
|
};
|
|
37
|
+
this.constants = getHideAnimationConstants();
|
|
19
38
|
}
|
|
20
39
|
updateConfig(config) {
|
|
21
40
|
this.readiness.animate = config.animate;
|
|
41
|
+
this.readiness.onError = config.onError;
|
|
22
42
|
this.readiness.userReady = config.ready ?? true;
|
|
23
43
|
this.maybeRunAnimate();
|
|
24
44
|
}
|
|
@@ -42,9 +62,22 @@ export class HideAnimationController {
|
|
|
42
62
|
state.userReady &&
|
|
43
63
|
!state.animateHasBeenCalled) {
|
|
44
64
|
state.animateHasBeenCalled = true;
|
|
65
|
+
// Fail open. animateHasBeenCalled is already true by the time hide() settles, so the gate
|
|
66
|
+
// is shut for good: on a rejection there is no readiness callback left that could retry,
|
|
67
|
+
// and skipping animate() means the caller's splash overlay never fades out — the app sits
|
|
68
|
+
// under it forever. Running animate() anyway costs at worst a fade over a native splash
|
|
69
|
+
// that is still up; not running it costs the whole app.
|
|
45
70
|
hide({ fade: false })
|
|
46
|
-
.
|
|
47
|
-
.
|
|
71
|
+
.catch((error) => {
|
|
72
|
+
this.reportFailure({ stage: 'hide', error });
|
|
73
|
+
})
|
|
74
|
+
.then(() => {
|
|
75
|
+
state.animate();
|
|
76
|
+
})
|
|
77
|
+
.catch((error) => {
|
|
78
|
+
// The caller's own animation throwing must not become an unhandled rejection either.
|
|
79
|
+
this.reportFailure({ stage: 'animate', error });
|
|
80
|
+
});
|
|
48
81
|
}
|
|
49
82
|
}
|
|
50
83
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hide-animation-controller.js","sourceRoot":"","sources":["../../src/core/hide-animation-controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"hide-animation-controller.js","sourceRoot":"","sources":["../../src/core/hide-animation-controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAC/C,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAa5D,kFAAkF;AAClF,wFAAwF;AACxF,2FAA2F;AAC3F,uFAAuF;AACvF,yFAAyF;AACzF,sFAAsF;AACtF,EAAE;AACF,kGAAkG;AAClG,8FAA8F;AAC9F,mGAAmG;AACnG,mCAAmC;AACnC,MAAM,OAAO,uBAAuB;IAClC,iGAAiG;IACjG,0FAA0F;IAC1F,4EAA4E;IACnE,SAAS,CAA0B;IAE3B,SAAS,CAAkB;IAE5C,4FAA4F;IAC5F,gDAAgD;IAC/B,aAAa,GAAG,CAAC,OAA8B,EAAQ,EAAE;QACxE,IAAI,CAAC,8CAA8C,OAAO,CAAC,KAAK,MAAM,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC/F,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC,CAAC;IAEF,YAAY,MAA4B;QACtC,IAAI,CAAC,SAAS,GAAG;YACf,WAAW,EAAE,KAAK;YAClB,SAAS,EAAE,MAAM,CAAC,IAAI,IAAI,IAAI;YAC9B,UAAU,EAAE,MAAM,CAAC,QAAQ,CAAC,KAAK,IAAI,IAAI,IAAI,MAAM,CAAC,KAAK,IAAI,IAAI;YACjE,SAAS,EAAE,MAAM,CAAC,KAAK,IAAI,IAAI;YAC/B,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,oBAAoB,EAAE,KAAK;SAC5B,CAAC;QACF,IAAI,CAAC,SAAS,GAAG,yBAAyB,EAAE,CAAC;IAC/C,CAAC;IAED,YAAY,CAAC,MAA4B;QACvC,IAAI,CAAC,SAAS,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;QACxC,IAAI,CAAC,SAAS,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;QACxC,IAAI,CAAC,SAAS,CAAC,SAAS,GAAG,MAAM,CAAC,KAAK,IAAI,IAAI,CAAC;QAChD,IAAI,CAAC,eAAe,EAAE,CAAC;IACzB,CAAC;IAEQ,iBAAiB,GAAG,GAAS,EAAE;QACtC,IAAI,CAAC,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC;QAClC,IAAI,CAAC,eAAe,EAAE,CAAC;IACzB,CAAC,CAAC;IAEO,aAAa,GAAG,GAAS,EAAE;QAClC,IAAI,CAAC,SAAS,CAAC,SAAS,GAAG,IAAI,CAAC;QAChC,IAAI,CAAC,eAAe,EAAE,CAAC;IACzB,CAAC,CAAC;IAEO,cAAc,GAAG,GAAS,EAAE;QACnC,IAAI,CAAC,SAAS,CAAC,UAAU,GAAG,IAAI,CAAC;QACjC,IAAI,CAAC,eAAe,EAAE,CAAC;IACzB,CAAC,CAAC;IAEM,eAAe;QACrB,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC;QAE7B,IACE,KAAK,CAAC,WAAW;YACjB,KAAK,CAAC,SAAS;YACf,KAAK,CAAC,UAAU;YAChB,KAAK,CAAC,SAAS;YACf,CAAC,KAAK,CAAC,oBAAoB,EAC3B,CAAC;YACD,KAAK,CAAC,oBAAoB,GAAG,IAAI,CAAC;YAClC,0FAA0F;YAC1F,yFAAyF;YACzF,0FAA0F;YAC1F,wFAAwF;YACxF,wDAAwD;YACxD,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;iBAClB,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;gBACxB,IAAI,CAAC,aAAa,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;YAC/C,CAAC,CAAC;iBACD,IAAI,CAAC,GAAG,EAAE;gBACT,KAAK,CAAC,OAAO,EAAE,CAAC;YAClB,CAAC,CAAC;iBACD,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;gBACxB,qFAAqF;gBACrF,IAAI,CAAC,aAAa,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;YAClD,CAAC,CAAC,CAAC;QACP,CAAC;IACH,CAAC;CACF"}
|
|
@@ -2,4 +2,4 @@ export { hide, isVisible } from './hide';
|
|
|
2
2
|
export { HideAnimationController } from './hide-animation-controller';
|
|
3
3
|
export { computeHideAnimationStyles } from './compute-hide-animation-styles';
|
|
4
4
|
export { getHideAnimationConstants } from './native-module';
|
|
5
|
-
export type { IHideConfig, IManifest, IHideAnimationConfig, IHideAnimationContainerProps, IHideAnimationImageProps, IHideAnimationResult, IHideAnimationConstants, } from './types';
|
|
5
|
+
export type { IHideConfig, IManifest, IHideAnimationConfig, IHideAnimationContainerProps, IHideAnimationFailure, IHideAnimationFailureStage, IHideAnimationImageProps, IHideAnimationResult, IHideAnimationConstants, } from './types';
|
|
@@ -7,6 +7,24 @@
|
|
|
7
7
|
// (hide/isVisible/useHideAnimation) never exposes directly.
|
|
8
8
|
import { getEnforcingNativeModule } from '@symbiote-native/engine';
|
|
9
9
|
const RN_BOOT_SPLASH_MODULE_NAME = 'RNBootSplash';
|
|
10
|
+
// DELIBERATELY UNGUARDED — do not wrap this in a try/catch or hand it a fallback.
|
|
11
|
+
//
|
|
12
|
+
// A missing RNBootSplash is a BUILD error, not a runtime condition: the module is either linked
|
|
13
|
+
// into the binary or it is not, deterministically, on the very first launch. Upstream treats it
|
|
14
|
+
// the same way and does not soften it either — react-native-bootsplash's whole spec file is
|
|
15
|
+
// `TurboModuleRegistry.getEnforcing<Spec>("RNBootSplash")` (src/specs/NativeRNBootSplash.ts),
|
|
16
|
+
// which throws at module-IMPORT time, and its useHideAnimation calls `NativeModule.getConstants()`
|
|
17
|
+
// with no guard at all. `getEnforcing` exists precisely to make this loud.
|
|
18
|
+
//
|
|
19
|
+
// This repo has a live footgun that depends on it staying loud: `npm install` deletes the
|
|
20
|
+
// `.rn-bootsplash/` folder this package's podspec vendors at pod-install time (see CLAUDE.md), so
|
|
21
|
+
// a skipped `pod install` yields exactly this missing module. Degrading to light-mode defaults
|
|
22
|
+
// would turn that into "the splash looks fine, just never dark" and it would ship.
|
|
23
|
+
//
|
|
24
|
+
// Note the failure is currently invisible under @symbiote-native/react specifically: mount()
|
|
25
|
+
// (adapters/react/src/render.ts) wires the reconciler's uncaught-error callbacks to `noop`, so the
|
|
26
|
+
// throw never reaches the app. That is a defect in THAT adapter, tracked separately — it is not a
|
|
27
|
+
// reason to stop throwing here.
|
|
10
28
|
export function getHideAnimationConstants() {
|
|
11
29
|
return getEnforcingNativeModule(RN_BOOT_SPLASH_MODULE_NAME).getConstants();
|
|
12
30
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-module.js","sourceRoot":"","sources":["../../src/core/native-module.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,gFAAgF;AAChF,wFAAwF;AACxF,qFAAqF;AACrF,yFAAyF;AACzF,oFAAoF;AACpF,4DAA4D;AAC5D,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AAGnE,MAAM,0BAA0B,GAAG,cAAc,CAAC;AAMlD,MAAM,UAAU,yBAAyB;IACvC,OAAO,wBAAwB,CAAoB,0BAA0B,CAAC,CAAC,YAAY,EAAE,CAAC;AAChG,CAAC"}
|
|
1
|
+
{"version":3,"file":"native-module.js","sourceRoot":"","sources":["../../src/core/native-module.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,gFAAgF;AAChF,wFAAwF;AACxF,qFAAqF;AACrF,yFAAyF;AACzF,oFAAoF;AACpF,4DAA4D;AAC5D,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AAGnE,MAAM,0BAA0B,GAAG,cAAc,CAAC;AAMlD,kFAAkF;AAClF,EAAE;AACF,gGAAgG;AAChG,gGAAgG;AAChG,4FAA4F;AAC5F,8FAA8F;AAC9F,mGAAmG;AACnG,2EAA2E;AAC3E,EAAE;AACF,0FAA0F;AAC1F,kGAAkG;AAClG,+FAA+F;AAC/F,mFAAmF;AACnF,EAAE;AACF,6FAA6F;AAC7F,mGAAmG;AACnG,kGAAkG;AAClG,gCAAgC;AAChC,MAAM,UAAU,yBAAyB;IACvC,OAAO,wBAAwB,CAAoB,0BAA0B,CAAC,CAAC,YAAY,EAAE,CAAC;AAChG,CAAC"}
|
|
@@ -16,6 +16,11 @@ export type IManifest = {
|
|
|
16
16
|
height: number;
|
|
17
17
|
};
|
|
18
18
|
};
|
|
19
|
+
export type IHideAnimationFailureStage = 'hide' | 'animate';
|
|
20
|
+
export type IHideAnimationFailure = {
|
|
21
|
+
stage: IHideAnimationFailureStage;
|
|
22
|
+
error: unknown;
|
|
23
|
+
};
|
|
19
24
|
export type IHideAnimationConfig = {
|
|
20
25
|
manifest: IManifest;
|
|
21
26
|
ready?: boolean;
|
|
@@ -24,6 +29,7 @@ export type IHideAnimationConfig = {
|
|
|
24
29
|
brand?: IImageSourceProp;
|
|
25
30
|
darkBrand?: IImageSourceProp;
|
|
26
31
|
animate: () => void;
|
|
32
|
+
onError?: (failure: IHideAnimationFailure) => void;
|
|
27
33
|
statusBarTranslucent?: boolean;
|
|
28
34
|
navigationBarTranslucent?: boolean;
|
|
29
35
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbiote-native/splash-screen",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
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
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -33,6 +33,10 @@
|
|
|
33
33
|
"types": "./build/react/index.d.ts",
|
|
34
34
|
"default": "./build/react/index.js"
|
|
35
35
|
},
|
|
36
|
+
"./svelte": {
|
|
37
|
+
"types": "./build/svelte/index.d.ts",
|
|
38
|
+
"default": "./build/svelte/index.js"
|
|
39
|
+
},
|
|
36
40
|
"./angular": {
|
|
37
41
|
"types": "./build-ngc/angular/index.d.ts",
|
|
38
42
|
"react-native": "./build-ngc/angular/index.js",
|
|
@@ -65,18 +69,20 @@
|
|
|
65
69
|
"dependencies": {
|
|
66
70
|
"react-native-bootsplash": "7.3.2",
|
|
67
71
|
"react-native-is-edge-to-edge": "^1.3.1",
|
|
68
|
-
"@symbiote-native/components": "0.
|
|
72
|
+
"@symbiote-native/components": "0.4.0"
|
|
69
73
|
},
|
|
70
74
|
"peerDependencies": {
|
|
75
|
+
"@symbiote-native/angular": ">=0.6.2",
|
|
71
76
|
"@symbiote-native/engine": ">=0.1.7",
|
|
77
|
+
"@symbiote-native/react": ">=0.2.8",
|
|
78
|
+
"@symbiote-native/svelte": ">=0.1.0",
|
|
79
|
+
"@symbiote-native/vue": ">=0.3.8",
|
|
72
80
|
"@angular/core": ">=20",
|
|
73
81
|
"@vue/runtime-core": "^3.5.13",
|
|
74
82
|
"react": ">=19.0.0",
|
|
75
83
|
"react-native": ">=0.86",
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"@symbiote-native/react": "0.2.8",
|
|
79
|
-
"@symbiote-native/vue": "0.3.8"
|
|
84
|
+
"svelte": ">=5.0.0",
|
|
85
|
+
"vue": ">=3.5.0"
|
|
80
86
|
},
|
|
81
87
|
"peerDependenciesMeta": {
|
|
82
88
|
"@symbiote-native/angular": {
|
|
@@ -85,6 +91,9 @@
|
|
|
85
91
|
"@symbiote-native/react": {
|
|
86
92
|
"optional": true
|
|
87
93
|
},
|
|
94
|
+
"@symbiote-native/svelte": {
|
|
95
|
+
"optional": true
|
|
96
|
+
},
|
|
88
97
|
"@symbiote-native/vue": {
|
|
89
98
|
"optional": true
|
|
90
99
|
},
|
|
@@ -97,24 +106,29 @@
|
|
|
97
106
|
"react": {
|
|
98
107
|
"optional": true
|
|
99
108
|
},
|
|
109
|
+
"svelte": {
|
|
110
|
+
"optional": true
|
|
111
|
+
},
|
|
100
112
|
"vue": {
|
|
101
113
|
"optional": true
|
|
102
114
|
}
|
|
103
115
|
},
|
|
104
116
|
"devDependencies": {
|
|
105
|
-
"@angular/compiler": "
|
|
106
|
-
"@angular/compiler-cli": "
|
|
107
|
-
"@angular/core": "
|
|
117
|
+
"@angular/compiler": "~22.0.8",
|
|
118
|
+
"@angular/compiler-cli": "~22.0.8",
|
|
119
|
+
"@angular/core": "~22.0.8",
|
|
108
120
|
"@types/node": "^26.0.0",
|
|
109
121
|
"@types/react": "^19.2.0",
|
|
110
122
|
"@vue/runtime-core": "^3.5.13",
|
|
111
123
|
"react": "19.2.3",
|
|
124
|
+
"svelte": "^5.56.0",
|
|
112
125
|
"typescript": "~6.0.0",
|
|
113
|
-
"@symbiote-native/angular": "0.
|
|
114
|
-
"@symbiote-native/engine": "0.
|
|
115
|
-
"@symbiote-native/react": "0.
|
|
126
|
+
"@symbiote-native/angular": "0.7.0",
|
|
127
|
+
"@symbiote-native/engine": "0.2.0",
|
|
128
|
+
"@symbiote-native/react": "0.3.0",
|
|
129
|
+
"@symbiote-native/svelte": "0.2.0",
|
|
116
130
|
"@symbiote-native/test-utils": "0.1.6",
|
|
117
|
-
"@symbiote-native/vue": "0.
|
|
131
|
+
"@symbiote-native/vue": "0.4.0"
|
|
118
132
|
},
|
|
119
133
|
"scripts": {
|
|
120
134
|
"typecheck": "tsc --build",
|
package/src/angular/index.ts
CHANGED
|
@@ -3,4 +3,11 @@
|
|
|
3
3
|
// machine, style computation, and native-constants read all live in core, shared with React/Vue.
|
|
4
4
|
export { hide, isVisible } from '../core';
|
|
5
5
|
export { HideAnimationService } from './services/hide-animation.service';
|
|
6
|
-
export type {
|
|
6
|
+
export type {
|
|
7
|
+
IHideAnimationConfig,
|
|
8
|
+
IHideAnimationFailure,
|
|
9
|
+
IHideAnimationFailureStage,
|
|
10
|
+
IHideAnimationResult,
|
|
11
|
+
IManifest,
|
|
12
|
+
IHideConfig,
|
|
13
|
+
} from '../core';
|
|
@@ -1,35 +1,35 @@
|
|
|
1
1
|
import { computed, effect, inject, Injectable, Injector, type Signal } from '@angular/core';
|
|
2
2
|
import {
|
|
3
3
|
computeHideAnimationStyles,
|
|
4
|
-
getHideAnimationConstants,
|
|
5
4
|
HideAnimationController,
|
|
6
5
|
type IHideAnimationConfig,
|
|
7
6
|
type IHideAnimationResult,
|
|
8
7
|
} from '../../../core';
|
|
9
8
|
|
|
10
9
|
// Angular twin of React's `useHideAnimation` hook and Vue's `useHideAnimation` composable.
|
|
11
|
-
// Angular has no per-instance hook
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// the same reason Vue's composable also takes a getter rather than a value: there is no
|
|
15
|
-
// re-render/re-setup cycle here to hang a fresh read off of.
|
|
10
|
+
// Angular has no per-instance hook, so `connect()` stands in for it: call it ONCE (typically
|
|
11
|
+
// from a component's field initializer, inside an injection context) with a GETTER so it can
|
|
12
|
+
// keep reading the caller's own signals — same reason Vue's composable takes a getter too.
|
|
16
13
|
//
|
|
17
14
|
// readonly hideAnimation = inject(HideAnimationService).connect(() => this.config());
|
|
18
15
|
// // template: [style]="hideAnimation().container.style", (layout)="..." etc.
|
|
19
16
|
@Injectable({ providedIn: 'root' })
|
|
20
17
|
export class HideAnimationService {
|
|
21
|
-
// Captured in the constructor (
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
// `TunnelOut`, which passes its own captured `Injector` to `effect()` for the same reason.
|
|
18
|
+
// Captured in the constructor (always run inside an injection context by Angular's own DI)
|
|
19
|
+
// so `connect()` can create an `effect()` even when called from field-initializer code that
|
|
20
|
+
// isn't itself an active injection context — mirrors create-tunnel.ts's `TunnelOut`.
|
|
25
21
|
private readonly injector = inject(Injector);
|
|
26
22
|
|
|
23
|
+
// The controller reads the native constants in its constructor, so a missing RNBootSplash
|
|
24
|
+
// throws out of connect() and aborts the host component's construction — deliberately: that
|
|
25
|
+
// is a build error, and a splash quietly stuck in light mode would ship unnoticed.
|
|
27
26
|
connect(getConfig: () => IHideAnimationConfig): Signal<IHideAnimationResult> {
|
|
28
27
|
const controller = new HideAnimationController(getConfig());
|
|
29
|
-
const constants = getHideAnimationConstants();
|
|
30
28
|
|
|
31
29
|
effect(() => controller.updateConfig(getConfig()), { injector: this.injector });
|
|
32
30
|
|
|
33
|
-
return computed(() =>
|
|
31
|
+
return computed(() =>
|
|
32
|
+
computeHideAnimationStyles(getConfig(), controller.constants, controller),
|
|
33
|
+
);
|
|
34
34
|
}
|
|
35
35
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { dlog } from '@symbiote-native/engine';
|
|
1
2
|
import { hide } from './hide';
|
|
2
|
-
import
|
|
3
|
+
import { getHideAnimationConstants } from './native-module';
|
|
4
|
+
import type { IHideAnimationConfig, IHideAnimationConstants, IHideAnimationFailure } from './types';
|
|
3
5
|
|
|
4
6
|
type IReadinessState = {
|
|
5
7
|
layoutReady: boolean;
|
|
@@ -7,6 +9,7 @@ type IReadinessState = {
|
|
|
7
9
|
brandReady: boolean;
|
|
8
10
|
userReady: boolean;
|
|
9
11
|
animate: () => void;
|
|
12
|
+
onError: ((failure: IHideAnimationFailure) => void) | undefined;
|
|
10
13
|
animateHasBeenCalled: boolean;
|
|
11
14
|
};
|
|
12
15
|
|
|
@@ -16,9 +19,26 @@ type IReadinessState = {
|
|
|
16
19
|
// brandReady are captured ONCE at construction (mirrors the original's useRef factory,
|
|
17
20
|
// evaluated only on first render) — a config that later drops its logo/brand source does
|
|
18
21
|
// NOT retroactively flip readiness back on, only updateConfig's animate/userReady do.
|
|
22
|
+
//
|
|
23
|
+
// DELIBERATE DIVERGENCE from upstream, one place only: upstream swallows a rejected hide() with a
|
|
24
|
+
// bare `.catch(() => {})`, which strands the app under a splash it can no longer dismiss (see
|
|
25
|
+
// maybeRunAnimate). We fail open and report instead. Everything else, the unguarded constants read
|
|
26
|
+
// included, stays a faithful port.
|
|
19
27
|
export class HideAnimationController {
|
|
28
|
+
// Read once here rather than once per adapter: the values never change over a splash's lifetime.
|
|
29
|
+
// Unguarded on purpose — a missing RNBootSplash is a build error that must stay loud, see
|
|
30
|
+
// native-module.ts for the full reasoning before wrapping this in anything.
|
|
31
|
+
readonly constants: IHideAnimationConstants;
|
|
32
|
+
|
|
20
33
|
private readonly readiness: IReadinessState;
|
|
21
34
|
|
|
35
|
+
// dlog is DEBUG-gated, so it is the developer's seam, not the report: config.onError is the
|
|
36
|
+
// channel the app actually hears about this on.
|
|
37
|
+
private readonly reportFailure = (failure: IHideAnimationFailure): void => {
|
|
38
|
+
dlog(`splash-screen: hide animation degraded at "${failure.stage}": ${String(failure.error)}`);
|
|
39
|
+
this.readiness.onError?.(failure);
|
|
40
|
+
};
|
|
41
|
+
|
|
22
42
|
constructor(config: IHideAnimationConfig) {
|
|
23
43
|
this.readiness = {
|
|
24
44
|
layoutReady: false,
|
|
@@ -26,12 +46,15 @@ export class HideAnimationController {
|
|
|
26
46
|
brandReady: config.manifest.brand == null || config.brand == null,
|
|
27
47
|
userReady: config.ready ?? true,
|
|
28
48
|
animate: config.animate,
|
|
49
|
+
onError: config.onError,
|
|
29
50
|
animateHasBeenCalled: false,
|
|
30
51
|
};
|
|
52
|
+
this.constants = getHideAnimationConstants();
|
|
31
53
|
}
|
|
32
54
|
|
|
33
55
|
updateConfig(config: IHideAnimationConfig): void {
|
|
34
56
|
this.readiness.animate = config.animate;
|
|
57
|
+
this.readiness.onError = config.onError;
|
|
35
58
|
this.readiness.userReady = config.ready ?? true;
|
|
36
59
|
this.maybeRunAnimate();
|
|
37
60
|
}
|
|
@@ -62,9 +85,22 @@ export class HideAnimationController {
|
|
|
62
85
|
!state.animateHasBeenCalled
|
|
63
86
|
) {
|
|
64
87
|
state.animateHasBeenCalled = true;
|
|
88
|
+
// Fail open. animateHasBeenCalled is already true by the time hide() settles, so the gate
|
|
89
|
+
// is shut for good: on a rejection there is no readiness callback left that could retry,
|
|
90
|
+
// and skipping animate() means the caller's splash overlay never fades out — the app sits
|
|
91
|
+
// under it forever. Running animate() anyway costs at worst a fade over a native splash
|
|
92
|
+
// that is still up; not running it costs the whole app.
|
|
65
93
|
hide({ fade: false })
|
|
66
|
-
.
|
|
67
|
-
|
|
94
|
+
.catch((error: unknown) => {
|
|
95
|
+
this.reportFailure({ stage: 'hide', error });
|
|
96
|
+
})
|
|
97
|
+
.then(() => {
|
|
98
|
+
state.animate();
|
|
99
|
+
})
|
|
100
|
+
.catch((error: unknown) => {
|
|
101
|
+
// The caller's own animation throwing must not become an unhandled rejection either.
|
|
102
|
+
this.reportFailure({ stage: 'animate', error });
|
|
103
|
+
});
|
|
68
104
|
}
|
|
69
105
|
}
|
|
70
106
|
}
|
package/src/core/index.ts
CHANGED
|
@@ -14,6 +14,24 @@ type IRNBootSplashSpec = {
|
|
|
14
14
|
getConstants(): IHideAnimationConstants;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
+
// DELIBERATELY UNGUARDED — do not wrap this in a try/catch or hand it a fallback.
|
|
18
|
+
//
|
|
19
|
+
// A missing RNBootSplash is a BUILD error, not a runtime condition: the module is either linked
|
|
20
|
+
// into the binary or it is not, deterministically, on the very first launch. Upstream treats it
|
|
21
|
+
// the same way and does not soften it either — react-native-bootsplash's whole spec file is
|
|
22
|
+
// `TurboModuleRegistry.getEnforcing<Spec>("RNBootSplash")` (src/specs/NativeRNBootSplash.ts),
|
|
23
|
+
// which throws at module-IMPORT time, and its useHideAnimation calls `NativeModule.getConstants()`
|
|
24
|
+
// with no guard at all. `getEnforcing` exists precisely to make this loud.
|
|
25
|
+
//
|
|
26
|
+
// This repo has a live footgun that depends on it staying loud: `npm install` deletes the
|
|
27
|
+
// `.rn-bootsplash/` folder this package's podspec vendors at pod-install time (see CLAUDE.md), so
|
|
28
|
+
// a skipped `pod install` yields exactly this missing module. Degrading to light-mode defaults
|
|
29
|
+
// would turn that into "the splash looks fine, just never dark" and it would ship.
|
|
30
|
+
//
|
|
31
|
+
// Note the failure is currently invisible under @symbiote-native/react specifically: mount()
|
|
32
|
+
// (adapters/react/src/render.ts) wires the reconciler's uncaught-error callbacks to `noop`, so the
|
|
33
|
+
// throw never reaches the app. That is a defect in THAT adapter, tracked separately — it is not a
|
|
34
|
+
// reason to stop throwing here.
|
|
17
35
|
export function getHideAnimationConstants(): IHideAnimationConstants {
|
|
18
36
|
return getEnforcingNativeModule<IRNBootSplashSpec>(RN_BOOT_SPLASH_MODULE_NAME).getConstants();
|
|
19
37
|
}
|
package/src/core/types.ts
CHANGED
|
@@ -19,6 +19,21 @@ export type IManifest = {
|
|
|
19
19
|
};
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
+
// The two RECOVERABLE points in the hide sequence, both of them runtime failures of a native
|
|
23
|
+
// module that does exist:
|
|
24
|
+
// hide - the native hide() call rejected.
|
|
25
|
+
// animate - the caller's own fade-out threw.
|
|
26
|
+
// Neither is allowed to strand the app under a splash it can no longer dismiss, so each is
|
|
27
|
+
// reported and the sequence carries on. A `hide` failure means the native splash may still be up
|
|
28
|
+
// underneath the caller's fade-out. A MISSING native module is deliberately not on this list —
|
|
29
|
+
// that is a build error and it throws (see native-module.ts).
|
|
30
|
+
export type IHideAnimationFailureStage = 'hide' | 'animate';
|
|
31
|
+
|
|
32
|
+
export type IHideAnimationFailure = {
|
|
33
|
+
stage: IHideAnimationFailureStage;
|
|
34
|
+
error: unknown;
|
|
35
|
+
};
|
|
36
|
+
|
|
22
37
|
export type IHideAnimationConfig = {
|
|
23
38
|
manifest: IManifest;
|
|
24
39
|
ready?: boolean;
|
|
@@ -30,6 +45,10 @@ export type IHideAnimationConfig = {
|
|
|
30
45
|
|
|
31
46
|
animate: () => void;
|
|
32
47
|
|
|
48
|
+
// The caller's only way to tell a nominal hide from a degraded one: `animate()` now runs in
|
|
49
|
+
// both cases, so its firing no longer proves the native side succeeded.
|
|
50
|
+
onError?: (failure: IHideAnimationFailure) => void;
|
|
51
|
+
|
|
33
52
|
statusBarTranslucent?: boolean;
|
|
34
53
|
navigationBarTranslucent?: boolean;
|
|
35
54
|
};
|
|
@@ -1,25 +1,20 @@
|
|
|
1
|
-
//
|
|
2
|
-
// computation
|
|
3
|
-
//
|
|
4
|
-
// the controller
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// EVERY render — intentional, since `ready` flipping true only gets picked up this way.
|
|
1
|
+
// Port of react-native-bootsplash's own useHideAnimation over the framework-agnostic
|
|
2
|
+
// HideAnimationController + style computation in core/. The controller is built lazily ONCE
|
|
3
|
+
// via a useRef factory (never reconstructed across re-renders); the effect below has NO
|
|
4
|
+
// dependency array and re-syncs the controller's config after EVERY render on purpose, since
|
|
5
|
+
// `ready` flipping true is only picked up that way. The native constants ride along on that same
|
|
6
|
+
// once-built controller, so there is no separate useState to hold them.
|
|
8
7
|
//
|
|
9
|
-
// No useMemo around the style computation: upstream
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
// renders at boot; computing plainly is honest about the actual cost and simpler to read.
|
|
16
|
-
import { useEffect, useRef, useState } from 'react';
|
|
8
|
+
// No useMemo around the style computation: upstream memoizes on ~15 individual primitive
|
|
9
|
+
// fields so Object.is compares by value rather than by the config/manifest object's
|
|
10
|
+
// reference (callers construct that object fresh every render, so keying off the whole
|
|
11
|
+
// object would recompute anyway). Not worth reproducing for a splash screen shown a couple
|
|
12
|
+
// of renders at boot — computing plainly is simpler and equally cheap.
|
|
13
|
+
import { useEffect, useRef } from 'react';
|
|
17
14
|
import {
|
|
18
15
|
computeHideAnimationStyles,
|
|
19
|
-
getHideAnimationConstants,
|
|
20
16
|
HideAnimationController,
|
|
21
17
|
type IHideAnimationConfig,
|
|
22
|
-
type IHideAnimationConstants,
|
|
23
18
|
type IHideAnimationResult,
|
|
24
19
|
} from '../../../core';
|
|
25
20
|
|
|
@@ -30,11 +25,9 @@ export function useHideAnimation(config: IHideAnimationConfig): IHideAnimationRe
|
|
|
30
25
|
}
|
|
31
26
|
const controller = controllerRef.current;
|
|
32
27
|
|
|
33
|
-
const [constants] = useState<IHideAnimationConstants>(() => getHideAnimationConstants());
|
|
34
|
-
|
|
35
28
|
useEffect(() => {
|
|
36
29
|
controller.updateConfig(config);
|
|
37
30
|
});
|
|
38
31
|
|
|
39
|
-
return computeHideAnimationStyles(config, constants, controller);
|
|
32
|
+
return computeHideAnimationStyles(config, controller.constants, controller);
|
|
40
33
|
}
|
package/src/react/index.ts
CHANGED
|
@@ -6,4 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
export { hide, isVisible } from '../core';
|
|
8
8
|
export { useHideAnimation } from './hooks/use-hide-animation';
|
|
9
|
-
export type {
|
|
9
|
+
export type {
|
|
10
|
+
IHideAnimationConfig,
|
|
11
|
+
IHideAnimationFailure,
|
|
12
|
+
IHideAnimationFailureStage,
|
|
13
|
+
IHideAnimationResult,
|
|
14
|
+
IManifest,
|
|
15
|
+
IHideConfig,
|
|
16
|
+
} from '../core';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// @symbiote-native/splash-screen/svelte: the Svelte entry over the framework-agnostic core.
|
|
2
|
+
// hide/isVisible carry zero lifecycle and are re-exported verbatim; useHideAnimation wraps
|
|
3
|
+
// HideAnimationController + computeHideAnimationStyles with Svelte's own runes
|
|
4
|
+
// (runes/use-hide-animation.svelte.ts) — mirrors the lifecycle-bucket naming convention of
|
|
5
|
+
// adapters/svelte/src/runes (never `hooks`/`composables`, those are React's/Vue's terms).
|
|
6
|
+
|
|
7
|
+
export { hide, isVisible } from '../core';
|
|
8
|
+
export { useHideAnimation } from './runes/use-hide-animation.svelte';
|
|
9
|
+
export type {
|
|
10
|
+
IHideAnimationConfig,
|
|
11
|
+
IHideAnimationFailure,
|
|
12
|
+
IHideAnimationFailureStage,
|
|
13
|
+
IHideAnimationResult,
|
|
14
|
+
IManifest,
|
|
15
|
+
IHideConfig,
|
|
16
|
+
} from '../core';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Svelte lifecycle wiring over the framework-agnostic HideAnimationController + style
|
|
2
|
+
// computation (core/). A Svelte component's `<script>` body runs ONCE (like Vue's setup,
|
|
3
|
+
// unlike a React hook re-running every render), so this takes a config GETTER, not a plain
|
|
4
|
+
// value — the boxed-getter convention from use-color-scheme.svelte.ts. The controller stays
|
|
5
|
+
// a plain local, not `$state`: only its methods are called, nothing needs Svelte to react to
|
|
6
|
+
// the reference itself. `$effect` re-syncs the controller's config on every reactive read
|
|
7
|
+
// inside `getConfig()`, mirroring Vue's `watchEffect`. The controller also carries the native
|
|
8
|
+
// constants, read once in its constructor — so a missing RNBootSplash throws straight out of the
|
|
9
|
+
// component's script body, on purpose.
|
|
10
|
+
import {
|
|
11
|
+
computeHideAnimationStyles,
|
|
12
|
+
HideAnimationController,
|
|
13
|
+
type IHideAnimationConfig,
|
|
14
|
+
type IHideAnimationResult,
|
|
15
|
+
} from '../../core';
|
|
16
|
+
|
|
17
|
+
export function useHideAnimation(getConfig: () => IHideAnimationConfig): {
|
|
18
|
+
readonly current: IHideAnimationResult;
|
|
19
|
+
} {
|
|
20
|
+
const controller = new HideAnimationController(getConfig());
|
|
21
|
+
|
|
22
|
+
let result = $state<IHideAnimationResult>(
|
|
23
|
+
computeHideAnimationStyles(getConfig(), controller.constants, controller),
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
$effect(() => {
|
|
27
|
+
const config = getConfig();
|
|
28
|
+
controller.updateConfig(config);
|
|
29
|
+
result = computeHideAnimationStyles(config, controller.constants, controller);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
get current(): IHideAnimationResult {
|
|
34
|
+
return result;
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Pulls in svelte's ambient global declarations ($state/$effect/...) for this package's tsc
|
|
2
|
+
// program. adapters/svelte gets these for free because render.ts does a real value import from
|
|
3
|
+
// 'svelte' (any file in a program importing a module loads its ambient globals for the whole
|
|
4
|
+
// program); this package has no such import, so a triple-slash reference does the same job
|
|
5
|
+
// explicitly rather than relying on an incidental side-effect.
|
|
6
|
+
/// <reference types="svelte" />
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
// Vue lifecycle wiring over the framework-agnostic HideAnimationController + style
|
|
2
|
-
// computation (core/). A
|
|
3
|
-
//
|
|
4
|
-
// reactivity tracks whatever reactive refs the getter reads
|
|
5
|
-
// watchEffect/computed below on their change, mirroring
|
|
6
|
-
//
|
|
2
|
+
// computation (core/). A composable's setup body runs ONCE (unlike a React hook, which
|
|
3
|
+
// 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 and re-runs the
|
|
5
|
+
// watchEffect/computed below on their change, mirroring a React consumer passing a fresh
|
|
6
|
+
// config object every render.
|
|
7
7
|
//
|
|
8
|
-
// The controller is a plain local, not a Vue ref:
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
8
|
+
// The controller is a plain local, not a Vue ref: only its methods are called, so wrapping
|
|
9
|
+
// it would only add an unnecessary reactive Proxy — same identity discipline as
|
|
10
|
+
// `use-color-scheme.ts`'s subscription handle. It also carries the native constants, read once in
|
|
11
|
+
// its constructor — so a missing RNBootSplash throws straight out of setup, on purpose.
|
|
12
12
|
import { computed, watchEffect, type ComputedRef } from '@vue/runtime-core';
|
|
13
13
|
import {
|
|
14
14
|
computeHideAnimationStyles,
|
|
15
|
-
getHideAnimationConstants,
|
|
16
15
|
HideAnimationController,
|
|
17
16
|
type IHideAnimationConfig,
|
|
18
17
|
type IHideAnimationResult,
|
|
@@ -22,11 +21,10 @@ export function useHideAnimation(
|
|
|
22
21
|
getConfig: () => IHideAnimationConfig,
|
|
23
22
|
): ComputedRef<IHideAnimationResult> {
|
|
24
23
|
const controller = new HideAnimationController(getConfig());
|
|
25
|
-
const constants = getHideAnimationConstants();
|
|
26
24
|
|
|
27
25
|
watchEffect(() => {
|
|
28
26
|
controller.updateConfig(getConfig());
|
|
29
27
|
});
|
|
30
28
|
|
|
31
|
-
return computed(() => computeHideAnimationStyles(getConfig(), constants, controller));
|
|
29
|
+
return computed(() => computeHideAnimationStyles(getConfig(), controller.constants, controller));
|
|
32
30
|
}
|
package/src/vue/index.ts
CHANGED
|
@@ -6,4 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
export { hide, isVisible } from '../core';
|
|
8
8
|
export { useHideAnimation } from './composables/use-hide-animation';
|
|
9
|
-
export type {
|
|
9
|
+
export type {
|
|
10
|
+
IHideAnimationConfig,
|
|
11
|
+
IHideAnimationFailure,
|
|
12
|
+
IHideAnimationFailureStage,
|
|
13
|
+
IHideAnimationResult,
|
|
14
|
+
IManifest,
|
|
15
|
+
IHideConfig,
|
|
16
|
+
} from '../core';
|
|
@@ -17,6 +17,14 @@ native_splash_screen_root = File.dirname(native_splash_screen_package_json)
|
|
|
17
17
|
# NSClassFromString crash on first launch, not a build error. Fix: vendor (copy) the native iOS
|
|
18
18
|
# sources into a gitignored folder next to this podspec on every `pod install`, and point
|
|
19
19
|
# source_files at that copy (a purely-downward relative pattern).
|
|
20
|
+
#
|
|
21
|
+
# CONSEQUENCE, and it looks like something else entirely: this copy is made at PODSPEC
|
|
22
|
+
# EVALUATION time, i.e. during `pod install` — nothing in package.json regenerates it. So any
|
|
23
|
+
# `npm install` that replaces this package's folder in an example app (the routine
|
|
24
|
+
# pack-a-tarball-and-reinstall loop) deletes `.rn-bootsplash`, and the NEXT iOS build dies with
|
|
25
|
+
# Build input file cannot be found: '.../splash-screen/.rn-bootsplash/ios/RNBootSplash.mm'
|
|
26
|
+
# buried under hundreds of lines of clang argument dumps. It reads like a corrupt install; it is
|
|
27
|
+
# not. Fix: re-run `pod install` in that app's ios/ directory.
|
|
20
28
|
vendored_dir = File.join(__dir__, '.rn-bootsplash')
|
|
21
29
|
|
|
22
30
|
FileUtils.rm_rf(vendored_dir)
|