@oxy.so/bloom 7.1.1 → 7.1.2
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/docs/adoption-matrix.mdx +2 -2
- package/docs/bottom-sheet.mdx +1 -1
- package/lib/commonjs/bottom-sheet/BottomSheetBase.js +33 -49
- package/lib/commonjs/bottom-sheet/BottomSheetBase.js.map +1 -1
- package/lib/commonjs/dialog/DialogBottomSheet.js +1 -1
- package/lib/commonjs/dialog/DialogBottomSheet.js.map +1 -1
- package/lib/commonjs/surface/SurfacePaint.js +17 -4
- package/lib/commonjs/surface/SurfacePaint.js.map +1 -1
- package/lib/module/bottom-sheet/BottomSheetBase.js +33 -49
- package/lib/module/bottom-sheet/BottomSheetBase.js.map +1 -1
- package/lib/module/dialog/DialogBottomSheet.js +1 -1
- package/lib/module/dialog/DialogBottomSheet.js.map +1 -1
- package/lib/module/surface/SurfacePaint.js +18 -5
- package/lib/module/surface/SurfacePaint.js.map +1 -1
- package/lib/typescript/commonjs/bottom-sheet/BottomSheetBase.d.ts.map +1 -1
- package/lib/typescript/commonjs/bottom-sheet/types.d.ts +1 -0
- package/lib/typescript/commonjs/bottom-sheet/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/surface/SurfacePaint.d.ts.map +1 -1
- package/lib/typescript/module/bottom-sheet/BottomSheetBase.d.ts.map +1 -1
- package/lib/typescript/module/bottom-sheet/types.d.ts +1 -0
- package/lib/typescript/module/bottom-sheet/types.d.ts.map +1 -1
- package/lib/typescript/module/surface/SurfacePaint.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/support/adoption-matrix.ts +683 -0
- package/src/__tests__/support/card-surface.ts +25 -0
- package/src/__tests__/support/collision-fixture-barrel.ts +20 -0
- package/src/__tests__/support/commerce-harness.tsx +97 -0
- package/src/__tests__/support/composite.ts +68 -0
- package/src/__tests__/support/constructed-style-sheets.ts +68 -0
- package/src/__tests__/support/messages-in.ts +10 -0
- package/src/__tests__/support/press-host.ts +30 -0
- package/src/__tests__/support/rendered-style.ts +99 -0
- package/src/__tests__/support/unread-hook-fixture.ts +33 -0
- package/src/__tests__/support/worklet-capture-fixture.tsx +28 -0
- package/src/agent-avatar/__tests__/artwork-golden.json +774 -0
- package/src/agent-creator/__tests__/support/character-runtime-fixture.tsx +26 -0
- package/src/bottom-sheet/BottomSheetBase.tsx +24 -34
- package/src/bottom-sheet/types.ts +1 -0
- package/src/dialog/DialogBottomSheet.tsx +1 -1
- package/src/surface/SurfacePaint.tsx +14 -4
- package/src/theme/__tests__/__fixtures__/golden-resolved-tokens.json +9346 -0
- package/src/theme/__tests__/__fixtures__/tonal-glass-primary-failures.json +69 -0
- package/src/theme/__tests__/fixtures/color-engine-golden.json +1 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ContextType, ReactNode } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
CharacterCapabilitiesContext,
|
|
4
|
+
CharacterRuntimeContext,
|
|
5
|
+
} from '../../../agent-avatar/context';
|
|
6
|
+
|
|
7
|
+
/** Supply both internal channels without starting the browser engine in UI tests. */
|
|
8
|
+
export function CharacterRuntimeFixture({
|
|
9
|
+
value,
|
|
10
|
+
children,
|
|
11
|
+
}: {
|
|
12
|
+
value: ContextType<typeof CharacterRuntimeContext> &
|
|
13
|
+
ContextType<typeof CharacterCapabilitiesContext>;
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
}) {
|
|
16
|
+
const { runtimeUrl, reportCapabilities, ...catalog } = value;
|
|
17
|
+
return (
|
|
18
|
+
<CharacterRuntimeContext.Provider
|
|
19
|
+
value={{ runtimeUrl, reportCapabilities }}
|
|
20
|
+
>
|
|
21
|
+
<CharacterCapabilitiesContext.Provider value={catalog}>
|
|
22
|
+
{children}
|
|
23
|
+
</CharacterCapabilitiesContext.Provider>
|
|
24
|
+
</CharacterRuntimeContext.Provider>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -133,14 +133,11 @@ export const BottomSheetBase = forwardRef((props: BottomSheetBaseProps, ref: Rea
|
|
|
133
133
|
const bodyPanRef = useRef<GestureType | undefined>(undefined);
|
|
134
134
|
const handlePanRef = useRef<GestureType | undefined>(undefined);
|
|
135
135
|
|
|
136
|
-
//
|
|
136
|
+
// Completion is not a user request: once closed, every owner must hear it.
|
|
137
|
+
// Vetoes are checked before a user-triggered close starts, never after it.
|
|
137
138
|
const safeClose = useCallback(() => {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
} else if (!onDismissAttempt) {
|
|
141
|
-
onDismiss?.();
|
|
142
|
-
}
|
|
143
|
-
}, [onDismissAttempt, onDismiss]);
|
|
139
|
+
onDismiss?.();
|
|
140
|
+
}, [onDismiss]);
|
|
144
141
|
|
|
145
142
|
// Mirror `safeClose` and `rendered` into refs so the unmount cleanup can
|
|
146
143
|
// fire the latest dismiss callback when needed, without re-binding the
|
|
@@ -251,6 +248,20 @@ export const BottomSheetBase = forwardRef((props: BottomSheetBaseProps, ref: Rea
|
|
|
251
248
|
};
|
|
252
249
|
}, [colors.border]);
|
|
253
250
|
|
|
251
|
+
// Gesture callbacks cross to JS before consulting the consumer's veto.
|
|
252
|
+
// A refused drag springs back; it never hides the Modal or strands its owner.
|
|
253
|
+
const requestPanDismiss = useCallback((generation: number, velocity: number) => {
|
|
254
|
+
if (closeGenerationRef.current !== generation || hasClosedRef.current) return;
|
|
255
|
+
if (onDismissAttempt && !onDismissAttempt()) {
|
|
256
|
+
translateY.value = withSpring(0, { ...SPRING_CONFIG, velocity });
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
translateY.value = withSpring(screenHeightSV.value, { ...SPRING_CONFIG, velocity });
|
|
260
|
+
opacity.value = withTiming(0, { ...MOTION_RECIPES.dismiss, duration: 250 }, (finished) => {
|
|
261
|
+
if (finished) runOnJS(finishClose)(generation);
|
|
262
|
+
});
|
|
263
|
+
}, [onDismissAttempt, translateY, screenHeightSV, opacity, finishClose]);
|
|
264
|
+
|
|
254
265
|
const present = useCallback(() => {
|
|
255
266
|
setRendered(true);
|
|
256
267
|
setVisible(true);
|
|
@@ -336,15 +347,7 @@ export const BottomSheetBase = forwardRef((props: BottomSheetBaseProps, ref: Rea
|
|
|
336
347
|
(distance > closeThreshold && velocity > -300);
|
|
337
348
|
|
|
338
349
|
if (shouldClose) {
|
|
339
|
-
|
|
340
|
-
// moment the close gesture commits. The completion
|
|
341
|
-
// callback only fires `finishClose` if no reopen
|
|
342
|
-
// bumped the generation in between.
|
|
343
|
-
const generation = closeGeneration.value;
|
|
344
|
-
translateY.value = withSpring(screenHeightSV.value, { ...SPRING_CONFIG, velocity });
|
|
345
|
-
opacity.value = withTiming(0, { ...MOTION_RECIPES.dismiss, duration: 250 }, (finished) => {
|
|
346
|
-
if (finished) runOnJS(finishClose)(generation);
|
|
347
|
-
});
|
|
350
|
+
runOnJS(requestPanDismiss)(closeGeneration.value, velocity);
|
|
348
351
|
} else {
|
|
349
352
|
translateY.value = withSpring(0, { ...SPRING_CONFIG, velocity });
|
|
350
353
|
}
|
|
@@ -396,16 +399,7 @@ export const BottomSheetBase = forwardRef((props: BottomSheetBaseProps, ref: Rea
|
|
|
396
399
|
(distance > closeThreshold && velocity > -300);
|
|
397
400
|
|
|
398
401
|
if (shouldClose) {
|
|
399
|
-
|
|
400
|
-
translateY.value = withSpring(screenHeightSV.value, {
|
|
401
|
-
...SPRING_CONFIG,
|
|
402
|
-
velocity: velocity,
|
|
403
|
-
});
|
|
404
|
-
opacity.value = withTiming(0, { ...MOTION_RECIPES.dismiss, duration: 250 }, (finished) => {
|
|
405
|
-
if (finished) {
|
|
406
|
-
runOnJS(finishClose)(generation);
|
|
407
|
-
}
|
|
408
|
-
});
|
|
402
|
+
runOnJS(requestPanDismiss)(closeGeneration.value, velocity);
|
|
409
403
|
} else {
|
|
410
404
|
translateY.value = withSpring(0, {
|
|
411
405
|
...SPRING_CONFIG,
|
|
@@ -417,7 +411,7 @@ export const BottomSheetBase = forwardRef((props: BottomSheetBaseProps, ref: Rea
|
|
|
417
411
|
// values that change the gesture's behavior. `finishClose` is stable
|
|
418
412
|
// (useCallback with stable deps).
|
|
419
413
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
420
|
-
}, [enablePanDownToClose, detached, manualActivation, nativeGesture,
|
|
414
|
+
}, [enablePanDownToClose, detached, manualActivation, nativeGesture, requestPanDismiss]);
|
|
421
415
|
|
|
422
416
|
// Dedicated handle pan — only built in `manualActivation` mode. Always
|
|
423
417
|
// active so users can drag the handle even while content is mid-scroll.
|
|
@@ -456,17 +450,13 @@ export const BottomSheetBase = forwardRef((props: BottomSheetBaseProps, ref: Rea
|
|
|
456
450
|
(distance > closeThreshold && velocity > -300);
|
|
457
451
|
|
|
458
452
|
if (shouldClose) {
|
|
459
|
-
|
|
460
|
-
translateY.value = withSpring(screenHeightSV.value, { ...SPRING_CONFIG, velocity });
|
|
461
|
-
opacity.value = withTiming(0, { ...MOTION_RECIPES.dismiss, duration: 250 }, (finished) => {
|
|
462
|
-
if (finished) runOnJS(finishClose)(generation);
|
|
463
|
-
});
|
|
453
|
+
runOnJS(requestPanDismiss)(closeGeneration.value, velocity);
|
|
464
454
|
} else {
|
|
465
455
|
translateY.value = withSpring(0, { ...SPRING_CONFIG, velocity });
|
|
466
456
|
}
|
|
467
457
|
});
|
|
468
458
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
469
|
-
}, [manualActivation, enablePanDownToClose, enableHandlePanningGesture, detached,
|
|
459
|
+
}, [manualActivation, enablePanDownToClose, enableHandlePanningGesture, detached, requestPanDismiss]);
|
|
470
460
|
|
|
471
461
|
// CRITICAL — the shared values each `useAnimatedStyle` READS (translateY,
|
|
472
462
|
// opacity, screenHeightSV, keyboardHeight) MUST be listed in its dependency
|
|
@@ -698,7 +688,7 @@ export const BottomSheetBase = forwardRef((props: BottomSheetBaseProps, ref: Rea
|
|
|
698
688
|
: <View style={[styles.nonScrollableContent, surfaceCorners, { overflow: 'hidden' }]}>{surfaceChildren}</View>;
|
|
699
689
|
|
|
700
690
|
return (
|
|
701
|
-
<Shell visible={rendered} onRequestClose={
|
|
691
|
+
<Shell visible={rendered} onRequestClose={handleBackdropPress} keyboardHeight={keyboardHeight}>
|
|
702
692
|
{/* Web keyboard and focus: in on open, back on close, Tab kept in
|
|
703
693
|
the sheet, Escape a USER dismissal — through `onDismissAttempt`,
|
|
704
694
|
exactly like the backdrop. The sheet had no keyboard path at
|
|
@@ -38,6 +38,7 @@ export interface BottomSheetProps {
|
|
|
38
38
|
*/
|
|
39
39
|
style?: StyleProp<AnimatedStyle<ViewStyle>>;
|
|
40
40
|
enableHandlePanningGesture?: boolean;
|
|
41
|
+
/** Veto user Back/backdrop/Escape/pan requests before closing; imperative dismiss bypasses it. */
|
|
41
42
|
onDismissAttempt?: () => boolean;
|
|
42
43
|
detached?: boolean; // If true, shows with margins and rounded corners. If false, full width with rounded top only.
|
|
43
44
|
/**
|
|
@@ -197,7 +197,7 @@ export function DialogBottomSheet({
|
|
|
197
197
|
const maxHeightPercent: `${number}%` = `${Math.round(maxHeightRatio * 100)}%`;
|
|
198
198
|
return [
|
|
199
199
|
{
|
|
200
|
-
backgroundColor:
|
|
200
|
+
backgroundColor: theme.colors.background,
|
|
201
201
|
...surfaceStyle(SURFACE_SHAPES.sheet),
|
|
202
202
|
maxHeight: maxHeightPercent,
|
|
203
203
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { surfaceStyle } from '../shapes/surface-style';
|
|
2
2
|
import type { SurfaceShape } from '../shapes/corner-types';
|
|
3
|
-
import React, { memo, useEffect } from 'react';
|
|
4
|
-
import { Platform, StyleSheet, View, type ViewStyle } from 'react-native';
|
|
3
|
+
import React, { memo, useCallback, useEffect, useState } from 'react';
|
|
4
|
+
import { Platform, StyleSheet, View, type LayoutChangeEvent, type ViewStyle } from 'react-native';
|
|
5
5
|
import Svg, { Defs, LinearGradient, Rect, Stop } from 'react-native-svg';
|
|
6
6
|
import { useSvgIdPrefix } from '../styles/svg-id';
|
|
7
7
|
import { StyledView } from '../styles/styled-primitives';
|
|
@@ -47,6 +47,13 @@ export const SurfacePaint = memo(function SurfacePaint({
|
|
|
47
47
|
const resolvedFill = resolveSurfaceTint(fill ?? 'transparent');
|
|
48
48
|
const id = useSvgIdPrefix('bloom-surface');
|
|
49
49
|
const isWeb = Platform.OS === 'web';
|
|
50
|
+
const [nativeSize, setNativeSize] = useState<{ width: number; height: number } | null>(null);
|
|
51
|
+
const onNativeLayout = useCallback((event: LayoutChangeEvent) => {
|
|
52
|
+
const { width, height } = event.nativeEvent.layout;
|
|
53
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return;
|
|
54
|
+
setNativeSize(previous => previous?.width === width && previous.height === height
|
|
55
|
+
? previous : { width, height });
|
|
56
|
+
}, []);
|
|
50
57
|
useSurfaceRefraction(isWeb);
|
|
51
58
|
useEffect(() => {
|
|
52
59
|
if (isWeb) adoptStyleSheet('bloom-surface-paint-web-css', WEB_PAINT_CSS);
|
|
@@ -67,17 +74,20 @@ export const SurfacePaint = memo(function SurfacePaint({
|
|
|
67
74
|
return (
|
|
68
75
|
<View
|
|
69
76
|
testID={testID}
|
|
77
|
+
onLayout={onNativeLayout}
|
|
70
78
|
pointerEvents="none"
|
|
71
79
|
style={[StyleSheet.absoluteFill, geometry, { overflow: 'hidden' }]}
|
|
72
80
|
>
|
|
81
|
+
{/* Android SVG percentage paths cache canvas dimensions after first paint.
|
|
82
|
+
Update the actual Rect geometry when this backing View changes size. */}
|
|
73
83
|
<Svg width="100%" height="100%" style={StyleSheet.absoluteFill}>
|
|
74
84
|
<Defs>
|
|
75
85
|
{sheen ? <LinearGradient id={`${id}-sheen`} x1="0" y1="0" x2="0" y2="1">
|
|
76
86
|
{optics.sheen.map(stop => <Stop key={stop.offset} offset={String(stop.offset)} stopColor={stop.color} stopOpacity={stop.opacity} />)}
|
|
77
87
|
</LinearGradient> : null}
|
|
78
88
|
</Defs>
|
|
79
|
-
{fill !== undefined ? <Rect x="0" y="0" width=
|
|
80
|
-
{sheen ? <Rect x="0" y="0" width=
|
|
89
|
+
{fill !== undefined ? <Rect x="0" y="0" width={nativeSize?.width ?? '100%'} height={nativeSize?.height ?? '100%'} fill={base.color} fillOpacity={base.opacity} /> : null}
|
|
90
|
+
{sheen ? <Rect x="0" y="0" width={nativeSize?.width ?? '100%'} height={nativeSize?.height ?? '100%'} fill={`url(#${id}-sheen)`} /> : null}
|
|
81
91
|
</Svg>
|
|
82
92
|
<View pointerEvents="none" style={[StyleSheet.absoluteFill, geometry, { boxShadow: optics.rim }]} />
|
|
83
93
|
</View>
|