@satinhq/kit 0.1.0-beta.1 → 0.1.0-beta.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/NOTICE +2 -2
- package/README.md +1 -1
- package/dist/acknowledgements/inventory.json +5 -5
- package/dist/actions/RowActions.d.ts +2 -2
- package/dist/actions/RowActions.js +2 -2
- package/dist/app/deployment.js +8 -2
- package/dist/app/installCheck.js +4 -3
- package/dist/app/launchWatchdog.js +6 -1
- package/dist/controls/civilDate.d.ts +1 -1
- package/dist/controls/civilDate.js +1 -1
- package/dist/docs/components/file-importer.md +1 -1
- package/dist/docs/components/paste-button.md +1 -1
- package/dist/docs/components/photos-picker.md +1 -1
- package/dist/docs/components/share-link.md +1 -1
- package/dist/docs/limitations.md +1 -1
- package/dist/docs/push.md +3 -3
- package/dist/docs/reference.md +1870 -0
- package/dist/glass/GlassMaterialRoot.d.ts +3 -1
- package/dist/glass/GlassMaterialRoot.js +2 -2
- package/dist/glass/MaterialEffectRoot.js +5 -1
- package/dist/glass/errors.d.ts +3 -1
- package/dist/glass/errors.js +49 -3
- package/dist/glass/fallback/canvasOwnership.d.ts +7 -0
- package/dist/glass/fallback/canvasOwnership.js +23 -0
- package/dist/glass/fallback/material.js +3 -2
- package/dist/glass/fallback/regions.d.ts +1 -0
- package/dist/glass/fallback/regions.js +9 -12
- package/dist/glass/fallback/runtime.d.ts +2 -0
- package/dist/glass/fallback/runtime.js +15 -4
- package/dist/glass/lens/controlLift.d.ts +1 -1
- package/dist/glass/lens/controlLift.js +1 -1
- package/dist/glass/lens/interactivePress.d.ts +2 -2
- package/dist/glass/lens/interactivePress.js +2 -2
- package/dist/glass/lens/menu-opening-native-fits.json +1 -1
- package/dist/glass/lens/menuContextFit.json +1 -1
- package/dist/glass/lens/menuDropFit.json +1 -1
- package/dist/glass/lens/menuField.d.ts +2 -2
- package/dist/glass/lens/menuField.js +3 -3
- package/dist/glass/lens/menuGlyphDropFit.json +1 -1
- package/dist/glass/lens/menuMorphFit.json +1 -1
- package/dist/glass/lens/menuMorphPhoneFit.json +1 -1
- package/dist/glass/lens/tabAccessoryMotion.d.ts +1 -1
- package/dist/glass/lens/tabSearchMember.d.ts +1 -1
- package/dist/glass/lens/tabSearchMember.js +1 -1
- package/dist/glass/menuPresenter.js +1 -1
- package/dist/glass/regions.d.ts +1 -1
- package/dist/glass/regions.js +1 -1
- package/dist/glass/shapeTrack.d.ts +1 -1
- package/dist/glass/submenuMotion.d.ts +1 -1
- package/dist/glass/submenuMotion.js +1 -1
- package/dist/glass/webgpu/client.d.ts +3 -0
- package/dist/glass/webgpu/client.js +57 -9
- package/dist/glass/webgpu/entry.worker.js +3 -6
- package/dist/glass/webgpu/errorCode.js +3 -2
- package/dist/glass/webgpu/frameClock.d.ts +6 -2
- package/dist/glass/webgpu/frameClock.js +9 -2
- package/dist/glass/webgpu/frameProbe.d.ts +1 -1
- package/dist/glass/webgpu/frameProbe.js +3 -3
- package/dist/glass/webgpu/layers.d.ts +5 -1
- package/dist/glass/webgpu/layers.js +61 -6
- package/dist/glass/webgpu/minimize.js +20 -2
- package/dist/glass/webgpu/production.js +13 -1
- package/dist/glass/webgpu/raster.d.ts +18 -0
- package/dist/glass/webgpu/raster.js +105 -15
- package/dist/glass/webgpu/rasterMaterial.d.ts +4 -0
- package/dist/glass/webgpu/rasterMaterial.js +17 -1
- package/dist/glass/webgpu/regionScene.d.ts +4 -1
- package/dist/glass/webgpu/regionScene.js +30 -1
- package/dist/glass/webgpu/renderer.js +23 -7
- package/dist/glass/webgpu/sourcePixels.d.ts +4 -4
- package/dist/glass/webgpu/sourcePixels.js +48 -8
- package/dist/keyboard/adapter.js +2 -2
- package/dist/keyboard/avoidance.d.ts +1 -1
- package/dist/keyboard/avoidance.js +1 -1
- package/dist/keyboard/motion.d.ts +1 -1
- package/dist/keyboard/motion.js +1 -1
- package/dist/list-form/ListForm.d.ts +2 -2
- package/dist/list-form/ListForm.js +1 -1
- package/dist/list-form/ListItem.js +1 -1
- package/dist/list-form/RowLabel.d.ts +1 -1
- package/dist/list-form/RowLabel.js +1 -1
- package/dist/list-form/Slider.js +1 -1
- package/dist/list-form/editMotion.js +1 -1
- package/dist/list-form/editing.d.ts +5 -5
- package/dist/list-form/editing.js +1 -1
- package/dist/motion/animationValue.d.ts +2 -2
- package/dist/motion/animationValue.js +9 -10
- package/dist/motion/matchedGeometry.d.ts +4 -4
- package/dist/motion/matchedGeometry.js +7 -8
- package/dist/motion/springTrack.d.ts +1 -2
- package/dist/motion/springTrack.js +10 -5
- package/dist/navigation/ScreenContainer.js +1 -1
- package/dist/navigation/chromeCurves.js +1 -1
- package/dist/navigation/types.d.ts +1 -1
- package/dist/navigation/zoomGeometry.js +1 -1
- package/dist/navigation/zoomMotion.d.ts +1 -1
- package/dist/navigation/zoomMotion.js +2 -2
- package/dist/navigation/zoomTransition.js +3 -4
- package/dist/presentation/Presentation.d.ts +2 -2
- package/dist/presentation/Presentation.js +1 -1
- package/dist/presentation/presentationSource.js +1 -1
- package/dist/public/controls.d.ts +4 -4
- package/dist/public/controls.js +7 -7
- package/dist/public/glass.d.ts +3 -4
- package/dist/public/glass.js +1 -2
- package/dist/public/navigationTransition.d.ts +1 -1
- package/dist/public/navigationTransition.js +1 -1
- package/dist/public/scroll.d.ts +1 -1
- package/dist/public/scroll.js +1 -1
- package/dist/public/toolbar.js +1 -1
- package/dist/root/SatinRoot.js +9 -5
- package/dist/safe-area/SafeAreaInset.js +3 -3
- package/dist/safe-area/modifiers.js +2 -2
- package/dist/safe-area/useSafeAreaMetrics.js +2 -2
- package/dist/scroll/ScrollSurface.d.ts +5 -5
- package/dist/scroll/ScrollSurface.js +1 -1
- package/dist/scroll/position.js +1 -1
- package/dist/scroll/screenGeometry.d.ts +1 -1
- package/dist/scroll/screenGeometry.js +1 -1
- package/dist/search/SearchDrawer.js +1 -1
- package/dist/search/searchMotion.d.ts +1 -1
- package/dist/search/searchMotion.js +1 -1
- package/dist/search/useSearchPresentation.js +1 -1
- package/dist/shapes/corners.js +1 -1
- package/dist/styles.css +55 -34
- package/dist/sw/renderServiceWorker.js +12 -3
- package/dist/text-input/TextEditor.js +7 -3
- package/package.json +4 -4
- package/dist/docs/swiftui-reference.md +0 -1895
|
@@ -5,7 +5,7 @@ export type EditMode = 'inactive' | 'active';
|
|
|
5
5
|
/** A row's identity in `List` selection: the `ForEach` id. */
|
|
6
6
|
export type ListItemID = string | number;
|
|
7
7
|
/**
|
|
8
|
-
* Every navigation destination owns one edit mode,
|
|
8
|
+
* Every navigation destination owns one edit mode, for its retained entry:
|
|
9
9
|
* its `EditButton` and its lists read and write the same value, and another screen keeps its own.
|
|
10
10
|
*/
|
|
11
11
|
export declare function EditModeScope({ children }: {
|
|
@@ -25,14 +25,14 @@ export type ListSelectionModel = Readonly<{
|
|
|
25
25
|
/** `single` selects one row (an optional binding); `multiple` collects a set. */
|
|
26
26
|
mode: 'single' | 'multiple';
|
|
27
27
|
isSelected(id: ListItemID): boolean;
|
|
28
|
-
/** A row tap: replaces a single selection; toggles a set only while editing
|
|
28
|
+
/** A row tap: replaces a single selection; toggles a set only while editing. */
|
|
29
29
|
activate(id: ListItemID, editing: boolean): void;
|
|
30
30
|
}>;
|
|
31
31
|
export declare const ListSelectionContext: import("react").Context<Readonly<{
|
|
32
32
|
/** `single` selects one row (an optional binding); `multiple` collects a set. */
|
|
33
33
|
mode: 'single' | 'multiple';
|
|
34
34
|
isSelected(id: ListItemID): boolean;
|
|
35
|
-
/** A row tap: replaces a single selection; toggles a set only while editing
|
|
35
|
+
/** A row tap: replaces a single selection; toggles a set only while editing. */
|
|
36
36
|
activate(id: ListItemID, editing: boolean): void;
|
|
37
37
|
}> | null>;
|
|
38
38
|
/**
|
|
@@ -82,7 +82,7 @@ export declare function forEachRows<T>(props: ForEachProps<T>): ForEachRow[];
|
|
|
82
82
|
/** What an editable row needs from its list item: edit state, selection and the item's actions. */
|
|
83
83
|
export type ListItemState = Readonly<{
|
|
84
84
|
editing: boolean;
|
|
85
|
-
/** While editing with a selection, a row's own press selects it, as a
|
|
85
|
+
/** While editing with a selection, a row's own press selects it, as a list row does. */
|
|
86
86
|
pressSelects: boolean;
|
|
87
87
|
select(): void;
|
|
88
88
|
/** Rows that also declare actions hand them to the item, which owns the row's one gesture host. */
|
|
@@ -94,7 +94,7 @@ export type RowActionsDelegation = RowActionsProps & Readonly<{
|
|
|
94
94
|
}>;
|
|
95
95
|
export declare const ListItemContext: import("react").Context<Readonly<{
|
|
96
96
|
editing: boolean;
|
|
97
|
-
/** While editing with a selection, a row's own press selects it, as a
|
|
97
|
+
/** While editing with a selection, a row's own press selects it, as a list row does. */
|
|
98
98
|
pressSelects: boolean;
|
|
99
99
|
select(): void;
|
|
100
100
|
/** Rows that also declare actions hand them to the item, which owns the row's one gesture host. */
|
|
@@ -3,7 +3,7 @@ import { createContext, Fragment, useContext, useMemo, useState } from 'react';
|
|
|
3
3
|
const inactiveEverywhere = ['inactive', () => { }];
|
|
4
4
|
const EditModeContext = createContext(inactiveEverywhere);
|
|
5
5
|
/**
|
|
6
|
-
* Every navigation destination owns one edit mode,
|
|
6
|
+
* Every navigation destination owns one edit mode, for its retained entry:
|
|
7
7
|
* its `EditButton` and its lists read and write the same value, and another screen keeps its own.
|
|
8
8
|
*/
|
|
9
9
|
export function EditModeScope({ children }) {
|
|
@@ -4,14 +4,14 @@ declare const brand: unique symbol;
|
|
|
4
4
|
export type Animation = Readonly<{
|
|
5
5
|
[brand]: true;
|
|
6
6
|
}>;
|
|
7
|
-
/**
|
|
7
|
+
/** Options for a spring defined by duration and bounce. */
|
|
8
8
|
export type SpringOptions = Readonly<{
|
|
9
9
|
/** The perceptual duration in milliseconds. Defaults to 500. */
|
|
10
10
|
durationMs?: number;
|
|
11
11
|
/** 0 settles without overshoot. Values are clamped to 0 through 0.99. Defaults to 0. */
|
|
12
12
|
bounce?: number;
|
|
13
13
|
}>;
|
|
14
|
-
/**
|
|
14
|
+
/** Named spring timing values. `default` is the curve `withAnimation` uses with no argument. */
|
|
15
15
|
export declare const Animation: Readonly<{
|
|
16
16
|
spring: (options?: SpringOptions) => Animation;
|
|
17
17
|
smooth: Animation;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
const brand = Symbol('satin.animation');
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Duration is the
|
|
4
4
|
* period of the undamped spring (omega = 2π / duration), and bounce is 1 − damping ratio for
|
|
5
|
-
* bounce ≥ 0.
|
|
5
|
+
* bounce ≥ 0. Negative, over-damped bounces are not offered: the shared spring
|
|
6
6
|
* solver (glass/lens/menuSpring.ts) models damping ratios up to 1.
|
|
7
7
|
*/
|
|
8
8
|
function spring({ durationMs = 500, bounce = 0 } = {}) {
|
|
@@ -15,24 +15,23 @@ function spring({ durationMs = 500, bounce = 0 } = {}) {
|
|
|
15
15
|
const animation = Object.freeze({ [brand]: true, omega, zeta });
|
|
16
16
|
return animation;
|
|
17
17
|
}
|
|
18
|
-
/**
|
|
18
|
+
/** Named spring timing values. `default` is the curve `withAnimation` uses with no argument. */
|
|
19
19
|
export const Animation = Object.freeze({
|
|
20
20
|
spring,
|
|
21
|
-
/**
|
|
21
|
+
/** Smooth spring: duration 0.5 s, bounce 0. */
|
|
22
22
|
smooth: spring({ durationMs: 500, bounce: 0 }),
|
|
23
|
-
/**
|
|
23
|
+
/** Snappy spring: duration 0.5 s, bounce 0.15. */
|
|
24
24
|
snappy: spring({ durationMs: 500, bounce: 0.15 }),
|
|
25
|
-
/**
|
|
25
|
+
/** Bouncy spring: duration 0.5 s, bounce 0.3. */
|
|
26
26
|
bouncy: spring({ durationMs: 500, bounce: 0.3 }),
|
|
27
|
-
/**
|
|
27
|
+
/** Default spring. The value is the measured fit recorded in the measured reference
|
|
28
28
|
* (matchedGeometryEffect, October 11); see DEFAULT_ANIMATION_DURATION_MS. */
|
|
29
29
|
get default() {
|
|
30
30
|
return defaultAnimation;
|
|
31
31
|
},
|
|
32
32
|
});
|
|
33
|
-
/*
|
|
34
|
-
*
|
|
35
|
-
* matched-geometry implementation (the measured reference, October 11). */
|
|
33
|
+
/* Default perceptual duration for the critically damped animation spring. The measured
|
|
34
|
+
* fit and recording provenance live in the measured reference. */
|
|
36
35
|
export const DEFAULT_ANIMATION_DURATION_MS = 500;
|
|
37
36
|
const defaultAnimation = spring({ durationMs: DEFAULT_ANIMATION_DURATION_MS, bounce: 0 });
|
|
38
37
|
/** The spring with a unit displacement and the given initial velocity (units per second). */
|
|
@@ -27,22 +27,22 @@ export type MatchedGeometryEffectProps = Readonly<{
|
|
|
27
27
|
children: ReactNode;
|
|
28
28
|
}>;
|
|
29
29
|
/**
|
|
30
|
-
*
|
|
30
|
+
* When a `withAnimation` change removes this
|
|
31
31
|
* view and inserts another with the same id, the new one moves from the old one's frame to its
|
|
32
32
|
* own while the two cross-fade. A view that only moves or resizes
|
|
33
33
|
* animates from where it was. A matched view with no partner fades in or out.
|
|
34
34
|
*
|
|
35
35
|
* Wrap one element that has no transform of its own. Changes made outside `withAnimation`
|
|
36
|
-
* apply at once
|
|
36
|
+
* apply at once.
|
|
37
37
|
* @example
|
|
38
38
|
* const art = useNamespace()
|
|
39
39
|
* <MatchedGeometryEffect id="artwork" in={art}><Image src={cover} alt="" /></MatchedGeometryEffect>
|
|
40
|
-
* @experimental
|
|
40
|
+
* @experimental Matches the frame around its center only.
|
|
41
41
|
* A non-source view does not follow its source outside an animation.
|
|
42
42
|
*/
|
|
43
43
|
export declare function MatchedGeometryEffect({ id, in: namespace, isSource, children, }: MatchedGeometryEffectProps): import("react").JSX.Element;
|
|
44
44
|
/**
|
|
45
|
-
*
|
|
45
|
+
* Applies the state changes in `update`, then animates the
|
|
46
46
|
* `MatchedGeometryEffect` views they move, insert or remove. Call it from an event handler.
|
|
47
47
|
* Keyed List rows also move to their new positions. Other views take their new layout at once.
|
|
48
48
|
* Returns the callback result and propagates errors. Only synchronous updates participate.
|
|
@@ -17,9 +17,8 @@ import { fadeSettleMs, runFrameTimes, sampleChannel, sampleProgress, } from './s
|
|
|
17
17
|
*
|
|
18
18
|
* A view whose id leaves and enters in the same change keeps its identity: the new view starts
|
|
19
19
|
* from the old one's frame and fades in, and the old view stays on screen as a "ghost" (its own
|
|
20
|
-
* detached DOM node, already decoded) that travels with it and fades out.
|
|
21
|
-
*
|
|
22
|
-
* on the animation's own spring (the measured reference, October 11).
|
|
20
|
+
* detached DOM node, already decoded) that travels with it and fades out. Their opacities sum to 1
|
|
21
|
+
* throughout the spring-driven cross-fade. Measurement provenance lives in the measured reference.
|
|
23
22
|
*/
|
|
24
23
|
const namespaceBrand = Symbol('satin.namespace');
|
|
25
24
|
const entries = new Set();
|
|
@@ -40,17 +39,17 @@ export function defineNamespace() {
|
|
|
40
39
|
return Object.freeze({ [namespaceBrand]: true });
|
|
41
40
|
}
|
|
42
41
|
/**
|
|
43
|
-
*
|
|
42
|
+
* When a `withAnimation` change removes this
|
|
44
43
|
* view and inserts another with the same id, the new one moves from the old one's frame to its
|
|
45
44
|
* own while the two cross-fade. A view that only moves or resizes
|
|
46
45
|
* animates from where it was. A matched view with no partner fades in or out.
|
|
47
46
|
*
|
|
48
47
|
* Wrap one element that has no transform of its own. Changes made outside `withAnimation`
|
|
49
|
-
* apply at once
|
|
48
|
+
* apply at once.
|
|
50
49
|
* @example
|
|
51
50
|
* const art = useNamespace()
|
|
52
51
|
* <MatchedGeometryEffect id="artwork" in={art}><Image src={cover} alt="" /></MatchedGeometryEffect>
|
|
53
|
-
* @experimental
|
|
52
|
+
* @experimental Matches the frame around its center only.
|
|
54
53
|
* A non-source view does not follow its source outside an animation.
|
|
55
54
|
*/
|
|
56
55
|
export function MatchedGeometryEffect({ id, in: namespace, isSource = true, children, }) {
|
|
@@ -226,13 +225,13 @@ function moveView(element, key, from, box, animation) {
|
|
|
226
225
|
target: box,
|
|
227
226
|
velocity: from.velocity,
|
|
228
227
|
animation,
|
|
229
|
-
// A new partner fades in
|
|
228
|
+
// A new partner fades in through the default opacity transition; a view that stays carries
|
|
230
229
|
// on from the opacity it is showing.
|
|
231
230
|
opacity: { from: stayed ? from.opacity : 0, to: 1 },
|
|
232
231
|
ghost: false,
|
|
233
232
|
});
|
|
234
233
|
}
|
|
235
|
-
/**
|
|
234
|
+
/** An opacity transition for a matched view with no partner. */
|
|
236
235
|
function fadeView(element, key, box, animation, opacity) {
|
|
237
236
|
startRun({
|
|
238
237
|
element,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { type MenuSpring } from '../glass/lens/menuSpring.js';
|
|
2
|
-
/** A
|
|
3
|
-
* `delay` ms after the animation starts. */
|
|
2
|
+
/** A unit-mass spring defined by stiffness and damping, released after an optional delay in ms. */
|
|
4
3
|
export type SpringParameters = Readonly<{
|
|
5
4
|
stiffness: number;
|
|
6
5
|
damping: number;
|
|
@@ -28,19 +28,22 @@ export function sampledKeyframes(track, frame) {
|
|
|
28
28
|
return track.progress.map((progress, index) => ({ ...frame(progress), offset: index / last }));
|
|
29
29
|
}
|
|
30
30
|
export function sampledMotionTrack(duration, sample, tolerance = 0.2) {
|
|
31
|
-
if (!(duration > 0) ||
|
|
31
|
+
if (!(duration > 0) ||
|
|
32
|
+
!Number.isFinite(duration) ||
|
|
33
|
+
!(tolerance > 0) ||
|
|
34
|
+
!Number.isFinite(tolerance))
|
|
32
35
|
throw Error('Motion tracks require a finite positive duration and tolerance');
|
|
33
36
|
let count = Math.max(1, Math.ceil(duration / (1000 / 120)));
|
|
34
37
|
// GPU and compositor consumers require even offsets. Refine the whole grid,
|
|
35
38
|
// rather than giving them independent adaptive sample positions.
|
|
36
39
|
while (count <= 4096) {
|
|
37
|
-
const values = Array.from({ length: count + 1 }, (_, index) => sample(duration * index / count));
|
|
40
|
+
const values = Array.from({ length: count + 1 }, (_, index) => sample((duration * index) / count));
|
|
38
41
|
if (!values.every(Number.isFinite))
|
|
39
42
|
throw Error('Motion track contains a non-finite value');
|
|
40
43
|
let accurate = true;
|
|
41
44
|
for (let index = 0; accurate && index < count; index++) {
|
|
42
45
|
for (const fraction of [0.25, 0.5, 0.75]) {
|
|
43
|
-
const exact = sample(duration * (index + fraction) / count);
|
|
46
|
+
const exact = sample((duration * (index + fraction)) / count);
|
|
44
47
|
const linear = values[index] + (values[index + 1] - values[index]) * fraction;
|
|
45
48
|
if (!Number.isFinite(exact))
|
|
46
49
|
throw Error('Motion track contains a non-finite value');
|
|
@@ -59,11 +62,13 @@ export function sampledMotionTrack(duration, sample, tolerance = 0.2) {
|
|
|
59
62
|
/** Read the submitted segment, including its velocity, when a run is interrupted. */
|
|
60
63
|
export function sampledMotionState(track, milliseconds) {
|
|
61
64
|
const last = track.values.length - 1;
|
|
62
|
-
const position = Math.max(0, Math.min(last, milliseconds / track.duration * last));
|
|
65
|
+
const position = Math.max(0, Math.min(last, (milliseconds / track.duration) * last));
|
|
63
66
|
const index = Math.min(last - 1, Math.floor(position));
|
|
64
67
|
const from = track.values[index], to = track.values[index + 1];
|
|
65
68
|
return {
|
|
66
69
|
value: from + (to - from) * (position - index),
|
|
67
|
-
velocity: milliseconds < 0 || milliseconds >= track.duration
|
|
70
|
+
velocity: milliseconds < 0 || milliseconds >= track.duration
|
|
71
|
+
? 0
|
|
72
|
+
: (((to - from) * last) / track.duration) * 1000,
|
|
68
73
|
};
|
|
69
74
|
}
|
|
@@ -130,7 +130,7 @@ export function ScreenContainer({ children, entry, interactionBlocked = false, c
|
|
|
130
130
|
}), [entry.key, entry.params, entry.transition.phase, isCovered, scrollRegistry, tabActive]);
|
|
131
131
|
useLayoutEffect(() => navigator[navigatorScrollRuntime].bind(entry.key, scrollRegistry), [entry.key, navigator, scrollRegistry]);
|
|
132
132
|
// Without a primary scroll view, content is laid out in the safe area below the bar, as a
|
|
133
|
-
//
|
|
133
|
+
// VStack is (styles.css). The attribute follows the registry in the same frame.
|
|
134
134
|
// The primary's vertical scroll indicator is drawn in the screen frame (scroll/scrollIndicator.ts).
|
|
135
135
|
const motionLayer = useRef(null);
|
|
136
136
|
const indicatorTrack = useRef(null);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Measured iOS 27 navigation bar chrome curves (what bar items do while a page moves) and the
|
|
2
2
|
// keyframe builders that turn them into Web Animations keyframes. Pure data and math: no DOM.
|
|
3
3
|
/** Incoming bar chrome, measured on the iOS 27.0 Simulator from the presentation layers of stock
|
|
4
|
-
*
|
|
4
|
+
* Measured at every display frame,
|
|
5
5
|
* light and dark agree within 0.04): milliseconds since the page starts moving. A push brings the
|
|
6
6
|
* destination's glass items in faster than a pop brings the revealed screen's back: a push shows
|
|
7
7
|
* 0.56 at 100 ms (Reminders-style list → list, back circle and trailing "…" together), a pop 0.30
|
|
@@ -16,7 +16,7 @@ export type NavigationBarDefinition<Params = unknown> = Readonly<{
|
|
|
16
16
|
display?: TitleDisplayMode;
|
|
17
17
|
visibility?: Visibility;
|
|
18
18
|
title: string | ((params: Params) => string);
|
|
19
|
-
/**
|
|
19
|
+
/** A line under the title, inline or large. */
|
|
20
20
|
subtitle?: string;
|
|
21
21
|
}>;
|
|
22
22
|
export type TransitionControllerContext = Readonly<{
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
*
|
|
2
|
+
* Zoom navigation geometry, as pure functions of a rectangle.
|
|
3
3
|
*
|
|
4
4
|
* At every moment the pushed screen is shown inside a rounded "window" rectangle that moves between
|
|
5
5
|
* the source view's frame and the full screen. As natively, the screen's content is scaled so its
|
|
@@ -9,7 +9,7 @@ export type ZoomSprings = Readonly<{
|
|
|
9
9
|
size: ZoomSpring;
|
|
10
10
|
}>;
|
|
11
11
|
/**
|
|
12
|
-
* Push: the centre and the width move together, critically damped (half way at 88 ms;
|
|
12
|
+
* Push: the centre and the width move together, critically damped (half way at 88 ms; the measured
|
|
13
13
|
* duration 0.33 s); the height lags slightly. Native moves the width with the centre, so the
|
|
14
14
|
* width shares the centre's spring here and the height takes the size spring (rms 0.002, 51 ticks).
|
|
15
15
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Measured values
|
|
2
|
+
* Measured spring values for the zoom navigation transition on iOS 27.
|
|
3
3
|
*
|
|
4
4
|
* native reference: a stock native fixture (a 180 pt tile with a 10 pt corner in a
|
|
5
5
|
* NavigationStack grid, pushed to a full-screen page), iPhone 17 Pro Max Simulator, iOS 27.0.
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* (the measured reference, October 11).
|
|
11
11
|
*/
|
|
12
12
|
/**
|
|
13
|
-
* Push: the centre and the width move together, critically damped (half way at 88 ms;
|
|
13
|
+
* Push: the centre and the width move together, critically damped (half way at 88 ms; the measured
|
|
14
14
|
* duration 0.33 s); the height lags slightly. Native moves the width with the centre, so the
|
|
15
15
|
* width shares the centre's spring here and the height takes the size spring (rms 0.002, 51 ticks).
|
|
16
16
|
*/
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
*
|
|
3
|
-
* from, and `.navigationTransition(.zoom(sourceID:in:))` on the pushed screen.
|
|
2
|
+
* Zoom navigation connects a MatchedTransitionSource to a screen configured with a zoom transition.
|
|
4
3
|
*
|
|
5
4
|
* A push whose screen asks for a zoom, and whose source is on screen in the screen it covers, grows
|
|
6
5
|
* out of the source instead of sliding in. Its pop (the back button) and a downward drag on the
|
|
@@ -116,7 +115,7 @@ export function visibleZoomSource(entryKey, rear, excluded) {
|
|
|
116
115
|
}
|
|
117
116
|
return chosen?.view;
|
|
118
117
|
}
|
|
119
|
-
/**
|
|
118
|
+
/** Zoom transitions require a visible source: at least part of it in the viewport. */
|
|
120
119
|
function isOnScreen(view) {
|
|
121
120
|
const bounds = view.getBoundingClientRect();
|
|
122
121
|
if (!(bounds.width > 0 && bounds.height > 0))
|
|
@@ -950,7 +949,7 @@ export function windowKeyframes(from, to, springs, velocity) {
|
|
|
950
949
|
frames.push({ ms: frames.at(-1).ms + FRAME_MS, rect: to });
|
|
951
950
|
return frames;
|
|
952
951
|
}
|
|
953
|
-
/** A measured Core Animation spring as Satin's `Animation` (
|
|
952
|
+
/** A measured Core Animation spring as Satin's `Animation` (the duration/bounce representation is
|
|
954
953
|
* exact: omega = 2π / duration, damping ratio = 1 − bounce). */
|
|
955
954
|
function animationOf(spring) {
|
|
956
955
|
return SpringAnimation.spring({
|
|
@@ -19,7 +19,7 @@ export type SheetProps = PresentationProps & {
|
|
|
19
19
|
* content that fills the card (Now Playing). A modal NavigationStack root's title is drawn by
|
|
20
20
|
* its navigation bar either way. */
|
|
21
21
|
titleVisibility?: 'visible' | 'hidden';
|
|
22
|
-
/**
|
|
22
|
+
/** Control the sheet drag indicator. `automatic` shows it only when there is more than one
|
|
23
23
|
* detent to drag between, as native does. */
|
|
24
24
|
dragIndicator?: 'automatic' | 'visible' | 'hidden';
|
|
25
25
|
};
|
|
@@ -27,7 +27,7 @@ export declare function Sheet(props: SheetProps): import("react").JSX.Element |
|
|
|
27
27
|
export type FullScreenCoverProps = PresentationProps & {
|
|
28
28
|
children: ReactNode;
|
|
29
29
|
};
|
|
30
|
-
/**
|
|
30
|
+
/** An opaque presentation over the whole screen that slides up from
|
|
31
31
|
* the bottom edge. It has no grabber and no interactive dismissal; close it with its binding or
|
|
32
32
|
* `useDismiss()`. Its title labels the dialog for assistive technology and is not drawn. */
|
|
33
33
|
export declare function FullScreenCover({ children, ...props }: FullScreenCoverProps): import("react").JSX.Element | null;
|
|
@@ -23,7 +23,7 @@ export function Sheet(props) {
|
|
|
23
23
|
props.onDismiss?.();
|
|
24
24
|
} })) : null;
|
|
25
25
|
}
|
|
26
|
-
/**
|
|
26
|
+
/** An opaque presentation over the whole screen that slides up from
|
|
27
27
|
* the bottom edge. It has no grabber and no interactive dismissal; close it with its binding or
|
|
28
28
|
* `useDismiss()`. Its title labels the dialog for assistive technology and is not drawn. */
|
|
29
29
|
export function FullScreenCover({ children, ...props }) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** The control a presentation grows from when the app names no anchor. Native iOS 27 presents
|
|
2
2
|
* `confirmationDialog` as a popover from the view that asked for it: a swipe action's dialog points
|
|
3
3
|
* at its row, a toolbar button's dialog grows out of the button. Satin's natural structure attaches
|
|
4
|
-
* the dialog at screen level, so the pressed control
|
|
4
|
+
* the dialog at screen level, so the pressed control supplies the attachment point. */
|
|
5
5
|
const lastPress = new WeakMap();
|
|
6
6
|
const tracked = new WeakSet();
|
|
7
7
|
// A press older than this did not open the dialog (a timer or a data change did).
|
|
@@ -112,7 +112,7 @@ export type ButtonProps = AriaAttributes & RowInteractionProps & {
|
|
|
112
112
|
iconOnly?: false;
|
|
113
113
|
});
|
|
114
114
|
/**
|
|
115
|
-
*
|
|
115
|
+
* A pressable control that adapts to where it is: a tinted row in a `List`, a glass button in a
|
|
116
116
|
* toolbar, otherwise the `buttonStyle` you choose. `role="destructive"` carries into every context.
|
|
117
117
|
*
|
|
118
118
|
* @example
|
|
@@ -291,7 +291,7 @@ export type VerticalTextFieldProps = Omit<TextareaHTMLAttributes<HTMLTextAreaEle
|
|
|
291
291
|
/** A single-line field, or with `axis="vertical"` a field that grows to its `lineLimit`. */
|
|
292
292
|
export type TextFieldProps = HorizontalTextFieldProps | VerticalTextFieldProps;
|
|
293
293
|
/**
|
|
294
|
-
*
|
|
294
|
+
* Single-line controlled text with a label, an optional `prompt` and keyboard intent from
|
|
295
295
|
* `kind`. In a `List` or `Form` it becomes the row; elsewhere it draws a standalone field.
|
|
296
296
|
* `axis="vertical"` grows with its content.
|
|
297
297
|
*
|
|
@@ -314,7 +314,7 @@ export type SecureFieldProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'type
|
|
|
314
314
|
prompt?: string;
|
|
315
315
|
textFieldStyle?: TextFieldStyle;
|
|
316
316
|
};
|
|
317
|
-
/**
|
|
317
|
+
/** Password entry with secure system input, adapting to a row in a form. */
|
|
318
318
|
export declare const SecureField: import("react").ForwardRefExoticComponent<Omit<InputHTMLAttributes<HTMLInputElement>, "defaultValue" | "onChange" | "onSubmit" | "placeholder" | "size" | "type" | "value"> & {
|
|
319
319
|
label: string;
|
|
320
320
|
value: string;
|
|
@@ -336,7 +336,7 @@ export type TextEditorProps = Omit<TextareaHTMLAttributes<HTMLTextAreaElement>,
|
|
|
336
336
|
prompt?: string;
|
|
337
337
|
textFieldStyle?: TextFieldStyle;
|
|
338
338
|
};
|
|
339
|
-
/**
|
|
339
|
+
/** Multiline text entry, adapting to a row in a form. */
|
|
340
340
|
export declare const TextEditor: import("react").ForwardRefExoticComponent<Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, "defaultValue" | "onChange" | "placeholder" | "rows" | "value"> & {
|
|
341
341
|
value: string;
|
|
342
342
|
onChange(value: string): void;
|
package/dist/public/controls.js
CHANGED
|
@@ -52,7 +52,7 @@ export function Form(props) {
|
|
|
52
52
|
return (_jsx(List, { ...props, listStyle: "insetGrouped", scrollDismissesKeyboard: props.scrollDismissesKeyboard ?? 'immediately' }));
|
|
53
53
|
}
|
|
54
54
|
/**
|
|
55
|
-
*
|
|
55
|
+
* A pressable control that adapts to where it is: a tinted row in a `List`, a glass button in a
|
|
56
56
|
* toolbar, otherwise the `buttonStyle` you choose. `role="destructive"` carries into every context.
|
|
57
57
|
*
|
|
58
58
|
* @example
|
|
@@ -72,9 +72,9 @@ export const Button = forwardRef(function Button({ children, symbol, role, butto
|
|
|
72
72
|
const resolvedBorderShape = resolveButtonBorderShape(buttonBorderShape);
|
|
73
73
|
const [isPressed, setPressed] = useState(false);
|
|
74
74
|
// A toolbar button labelled with an icon-only Label or a SymbolImage is a glass circle, and so is a
|
|
75
|
-
// titled button with a symbol
|
|
75
|
+
// A titled toolbar button with a symbol uses a circular control.
|
|
76
76
|
// draws only its symbol (iOS 27.0 native reference), the title becoming its accessible name. A bare symbol
|
|
77
|
-
//
|
|
77
|
+
// An icon-only toolbar button uses the same circle.
|
|
78
78
|
const symbolTitle = toolbar && symbol && typeof children === 'string' && !rest.iconOnly ? children : undefined;
|
|
79
79
|
const bareSymbolName = toolbar && symbol && (children == null || typeof children === 'boolean') && !rest.iconOnly
|
|
80
80
|
? rest['aria-label']?.trim() || symbol
|
|
@@ -236,7 +236,7 @@ function toggleLabel(props, descriptionId) {
|
|
|
236
236
|
* options={[{ id: 'home', label: 'Home', symbol: 'house', color: systemColor('red') }]} />
|
|
237
237
|
*/
|
|
238
238
|
export function Picker({ pickerStyle = 'menu', selection, onSelectionChange, disabled, ...rest }) {
|
|
239
|
-
// `ControlStyleProvider disabled` disables a Picker
|
|
239
|
+
// `ControlStyleProvider disabled` disables a Picker throughout its subtree.
|
|
240
240
|
const configuration = useControlStyle({ disabled });
|
|
241
241
|
const props = {
|
|
242
242
|
...rest,
|
|
@@ -276,7 +276,7 @@ const Field = forwardRef(function TextField({ prompt, textFieldStyle, style, ...
|
|
|
276
276
|
return (_jsx(Fields.TextField, { ...fieldProps, style: style, ref: ref, placeholder: prompt ?? (hidden ? props.label : undefined), variant: variant, labelVisibility: hidden ? 'hidden' : 'visible' }));
|
|
277
277
|
});
|
|
278
278
|
/**
|
|
279
|
-
*
|
|
279
|
+
* Single-line controlled text with a label, an optional `prompt` and keyboard intent from
|
|
280
280
|
* `kind`. In a `List` or `Form` it becomes the row; elsewhere it draws a standalone field.
|
|
281
281
|
* `axis="vertical"` grows with its content.
|
|
282
282
|
*
|
|
@@ -285,12 +285,12 @@ const Field = forwardRef(function TextField({ prompt, textFieldStyle, style, ...
|
|
|
285
285
|
*/
|
|
286
286
|
// The cast keeps single-line refs and events precisely typed when axis is omitted.
|
|
287
287
|
export const TextField = Field;
|
|
288
|
-
/**
|
|
288
|
+
/** Password entry with secure system input, adapting to a row in a form. */
|
|
289
289
|
export const SecureField = forwardRef(function SecureField({ prompt, textFieldStyle, style, ...props }, ref) {
|
|
290
290
|
const variant = useFieldVariant(textFieldStyle);
|
|
291
291
|
return (_jsx(Fields.SecureField, { ...props, style: style, ref: ref, placeholder: prompt ?? (variant !== 'standalone' ? props.label : undefined), variant: variant, labelVisibility: prompt !== undefined || variant !== 'standalone' ? 'hidden' : 'visible' }));
|
|
292
292
|
});
|
|
293
|
-
/**
|
|
293
|
+
/** Multiline text entry, adapting to a row in a form. */
|
|
294
294
|
export const TextEditor = forwardRef(function TextEditor({ prompt, textFieldStyle, style, ...props }, ref) {
|
|
295
295
|
const variant = useFieldVariant(textFieldStyle);
|
|
296
296
|
return (_jsx(Editor, { ...props, minLines: props.minLines ?? (variant === 'row' ? 1 : 3), style: style, ref: ref, placeholder: prompt ?? (variant === 'plain' ? props.label : undefined), variant: variant, labelVisibility: prompt !== undefined || variant !== 'standalone' ? 'hidden' : 'visible' }));
|
package/dist/public/glass.d.ts
CHANGED
|
@@ -14,14 +14,13 @@ export type GlassEffectProps = HTMLAttributes<HTMLDivElement> & {
|
|
|
14
14
|
in?: NamespaceID;
|
|
15
15
|
/** Morph identity within namespace; independent of the DOM id. */
|
|
16
16
|
glassEffectID?: string;
|
|
17
|
-
/**
|
|
17
|
+
/** Effects with one union id draw as one shape. */
|
|
18
18
|
glassEffectUnion?: string;
|
|
19
|
-
/**
|
|
19
|
+
/** Controls how an identified effect enters or leaves its container. */
|
|
20
20
|
glassEffectTransition?: 'matchedGeometry' | 'materialize' | 'identity';
|
|
21
21
|
};
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
24
|
-
* capsule by default). Inside a `GlassEffectContainer`, effects with ids morph into one another.
|
|
23
|
+
* Draws glass behind its content in the given shape, using a capsule by default. Inside a `GlassEffectContainer`, effects with ids morph into one another.
|
|
25
24
|
*
|
|
26
25
|
* @example
|
|
27
26
|
* <GlassEffect glass={Glass.regular.interactive()} shape="circle">
|
package/dist/public/glass.js
CHANGED
|
@@ -3,8 +3,7 @@ import { Glass, resolveGlass } from './glassValue.js';
|
|
|
3
3
|
import { GlassFieldContext } from './fieldContext.js';
|
|
4
4
|
import { GlassEffect as Effect } from '../glass/GlassEffect.js';
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
* capsule by default). Inside a `GlassEffectContainer`, effects with ids morph into one another.
|
|
6
|
+
* Draws glass behind its content in the given shape, using a capsule by default. Inside a `GlassEffectContainer`, effects with ids morph into one another.
|
|
8
7
|
*
|
|
9
8
|
* @example
|
|
10
9
|
* <GlassEffect glass={Glass.regular.interactive()} shape="circle">
|
|
@@ -17,7 +17,7 @@ export type ZoomTransitionOptions = Readonly<{
|
|
|
17
17
|
sourceID: string;
|
|
18
18
|
in: NamespaceID;
|
|
19
19
|
}>;
|
|
20
|
-
/**
|
|
20
|
+
/** Transitions between retained navigation entries. */
|
|
21
21
|
export declare const NavigationTransition: Readonly<{
|
|
22
22
|
automatic: NavigationTransition;
|
|
23
23
|
zoom: (options: ZoomTransitionOptions) => NavigationTransition;
|
|
@@ -4,7 +4,7 @@ import { NavigatorContext } from '../navigation/navigationContext.js';
|
|
|
4
4
|
import { installZoomDismissDrag } from '../navigation/zoomDrag.js';
|
|
5
5
|
import { registerTransitionSource, registerZoomRequest } from '../navigation/zoomTransition.js';
|
|
6
6
|
const brand = Symbol('satin.navigationTransition');
|
|
7
|
-
/**
|
|
7
|
+
/** Transitions between retained navigation entries. */
|
|
8
8
|
export const NavigationTransition = Object.freeze({
|
|
9
9
|
/** The platform's push: the screen slides in from the trailing edge. */
|
|
10
10
|
automatic: Object.freeze({ [brand]: true, kind: 'automatic' }),
|
package/dist/public/scroll.d.ts
CHANGED
|
@@ -34,5 +34,5 @@ export type ScrollViewProps = Omit<HTMLAttributes<HTMLDivElement>, 'id'> & {
|
|
|
34
34
|
scrollPosition?: never;
|
|
35
35
|
onScrollGeometryChange?: never;
|
|
36
36
|
});
|
|
37
|
-
/**
|
|
37
|
+
/** A scroll surface with framework-owned restoration and native scrolling. */
|
|
38
38
|
export declare const ScrollView: import("react").ForwardRefExoticComponent<ScrollViewProps & import("react").RefAttributes<ScrollViewHandle>>;
|
package/dist/public/scroll.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef } from 'react';
|
|
3
3
|
import { ScrollSurface } from '../scroll/ScrollSurface.js';
|
|
4
|
-
/**
|
|
4
|
+
/** A scroll surface with framework-owned restoration and native scrolling. */
|
|
5
5
|
export const ScrollView = forwardRef(function ScrollView(props, ref) {
|
|
6
6
|
return _jsx(ScrollSurface, { ...props, ref: ref });
|
|
7
7
|
});
|
package/dist/public/toolbar.js
CHANGED
|
@@ -69,7 +69,7 @@ export function ToolbarSpacer({ sizing = 'flexible', placement = 'automatic', })
|
|
|
69
69
|
/** `ToolbarItemGroup`: its items render as consecutive `ToolbarItem`s of the placement,
|
|
70
70
|
* which share one glass capsule (iOS 26+ draws a group and adjacent items identically). */
|
|
71
71
|
export function ToolbarItemGroup({ children, placement }) {
|
|
72
|
-
//
|
|
72
|
+
// gives confirmation actions separate capsules, even inside a group.
|
|
73
73
|
if (placement === 'confirmationAction')
|
|
74
74
|
return (_jsx(ToolbarItem, { placement: placement, children: _jsx("div", { "data-satin-toolbar-confirmation-group": "true", children: children }) }));
|
|
75
75
|
return (_jsx(_Fragment, { children: Children.toArray(children).map((child, index) => (_jsx(ToolbarItem, { placement: placement, children: child }, isValidElement(child) && child.key !== null ? child.key : index))) }));
|
package/dist/root/SatinRoot.js
CHANGED
|
@@ -159,6 +159,7 @@ export function SatinRoot({ background: legacyBackground, app: suppliedApp, offl
|
|
|
159
159
|
const downloadedBytes = useRef(0);
|
|
160
160
|
const [launchAttempt, setLaunchAttempt] = useState(0);
|
|
161
161
|
const [glassReady, setGlassReady] = useState(false);
|
|
162
|
+
const [bundlePrepared, setBundlePrepared] = useState(import.meta.env.DEV || !app);
|
|
162
163
|
const [glassError, setGlassError] = useState(null);
|
|
163
164
|
const [glassFailed, setGlassFailed] = useState(false);
|
|
164
165
|
useLayoutEffect(() => {
|
|
@@ -235,10 +236,13 @@ export function SatinRoot({ background: legacyBackground, app: suppliedApp, offl
|
|
|
235
236
|
downloadActivity.current = performance.now();
|
|
236
237
|
downloadedBytes.current = progress.loaded;
|
|
237
238
|
}
|
|
238
|
-
//
|
|
239
|
-
//
|
|
240
|
-
|
|
241
|
-
|
|
239
|
+
// First-install bytes describe a real download in every profile.
|
|
240
|
+
// Applied-update confirmation remains a separate successor phase.
|
|
241
|
+
setLaunchProgress(progress);
|
|
242
|
+
}).then(ready => {
|
|
243
|
+
if (!cancelled && ready)
|
|
244
|
+
setBundlePrepared(true);
|
|
245
|
+
return ready;
|
|
242
246
|
}),
|
|
243
247
|
prepareLaunchContent(rootRef.current, contentPreparation.signal),
|
|
244
248
|
]);
|
|
@@ -489,7 +493,7 @@ export function SatinRoot({ background: legacyBackground, app: suppliedApp, offl
|
|
|
489
493
|
return;
|
|
490
494
|
return startDevFidelityAssertions(initialHistoryLength.current);
|
|
491
495
|
}, []);
|
|
492
|
-
return (_jsx(RuntimeContext, { value: runtime, children: _jsxs(MediaPlayerRoot, { onActivityChange: onMediaActivityChange, children: [_jsx("div", { ...props, ref: rootRef, className: className, "data-satin-role": "chrome", "data-satin-root": "true", "data-satin-launch": launch, "data-satin-presentation": presentationPending ? 'preparing' : 'ready', inert: launch !== 'ready' || presentationPending, "data-satin-layout-horizontal": nextLayout.layoutClass.horizontal, "data-satin-layout-vertical": nextLayout.layoutClass.vertical, style: geometryStyles, children: _jsx(AdaptiveTabsContext, { value: tabs, children: _jsx("div", { "data-satin-status-band-source": "scene", children: _jsxs(GlassMaterialRoot, { displacement: glassDisplacement, onLaunchReady: setGlassReady, children: [DeveloperMenu && app && launch === 'ready' ? (_jsx(Suspense, { fallback: null, children: _jsx(DeveloperMenu, {}) })) : null, app ? (_jsx(OrientationGuard, { app: app, width: metrics.safeFrame.width, height: metrics.safeFrame.height, children: _jsx(ReachabilityProvider, { profile: app, offlinePresentation: offlinePresentation, children: children }) })) : (children)] }) }) }) }), launch === 'failed'
|
|
496
|
+
return (_jsx(RuntimeContext, { value: runtime, children: _jsxs(MediaPlayerRoot, { onActivityChange: onMediaActivityChange, children: [_jsx("div", { ...props, ref: rootRef, className: className, "data-satin-role": "chrome", "data-satin-root": "true", "data-satin-launch": launch, "data-satin-presentation": presentationPending ? 'preparing' : 'ready', inert: launch !== 'ready' || presentationPending, "data-satin-layout-horizontal": nextLayout.layoutClass.horizontal, "data-satin-layout-vertical": nextLayout.layoutClass.vertical, style: geometryStyles, children: _jsx(AdaptiveTabsContext, { value: tabs, children: _jsx("div", { "data-satin-status-band-source": "scene", children: _jsxs(GlassMaterialRoot, { displacement: glassDisplacement, prepared: bundlePrepared, onLaunchReady: setGlassReady, children: [DeveloperMenu && app && launch === 'ready' ? (_jsx(Suspense, { fallback: null, children: _jsx(DeveloperMenu, {}) })) : null, app ? (_jsx(OrientationGuard, { app: app, width: metrics.safeFrame.width, height: metrics.safeFrame.height, children: _jsx(ReachabilityProvider, { profile: app, offlinePresentation: offlinePresentation, children: children }) })) : (children)] }) }) }) }), launch === 'failed'
|
|
493
497
|
? createPortal(_jsxs("div", { "data-satin-role": "chrome", "data-satin-launch-error": "true", "data-satin-glass-error": glassError ? true : undefined, role: "alert", style: { zIndex: launchErrorLayer }, children: [!glassError ? (_jsx("img", { className: "satin-launch-error-icon", src: document.querySelector('#satin-launch-screen .satin-launch-icon img')?.src ??
|
|
494
498
|
app?.launchScreen?.icon ??
|
|
495
499
|
app?.icons[0]?.src, alt: "" })) : null, _jsxs("div", { className: "satin-launch-error-message", children: [_jsx("h1", { children: glassError ? 'Glass Renderer Failed' : 'Unable to Open App' }), glassError ? (_jsx("pre", { style: { whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' }, children: `${glassError.code} · ${glassError.stage}\n${glassError.message}${glassError.reason ? `\n${glassError.reason}` : ''}` })) : (_jsx("p", { children: preparationError })), _jsx("button", { type: "button", onClick: async () => {
|