@fluentui-react-native/callout 0.28.6 → 0.29.4

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.
Files changed (69) hide show
  1. package/.cache/tsconfig.tsbuildinfo +1 -1
  2. package/CHANGELOG.md +62 -0
  3. package/FRNCallout.podspec +1 -1
  4. package/NuGet.config +10 -0
  5. package/SPEC.md +46 -0
  6. package/lib/Callout.d.ts +2 -5
  7. package/lib/Callout.d.ts.map +1 -1
  8. package/lib/Callout.js +57 -58
  9. package/lib/Callout.js.map +1 -1
  10. package/lib/Callout.types.d.ts +51 -152
  11. package/lib/Callout.types.d.ts.map +1 -1
  12. package/lib/Callout.types.js.map +1 -1
  13. package/lib/CalloutNativeComponent.d.ts +7 -3
  14. package/lib/CalloutNativeComponent.d.ts.map +1 -1
  15. package/lib/CalloutNativeComponent.js +3 -1
  16. package/lib/CalloutNativeComponent.js.map +1 -1
  17. package/lib/__tests__/Callout.test.js +94 -10
  18. package/lib/__tests__/Callout.test.js.map +1 -1
  19. package/lib/__tests__/Callout.types.test.d.ts +2 -0
  20. package/lib/__tests__/Callout.types.test.d.ts.map +1 -0
  21. package/lib/__tests__/Callout.types.test.js +21 -0
  22. package/lib/__tests__/Callout.types.test.js.map +1 -0
  23. package/lib/index.d.ts +1 -2
  24. package/lib/index.d.ts.map +1 -1
  25. package/macos/CalloutManager.swift +1 -1
  26. package/macos/CalloutView.swift +49 -17
  27. package/macos/FRNCalloutManager.m +11 -11
  28. package/macos/RCTCalloutComponentView.h +12 -0
  29. package/macos/RCTCalloutComponentView.mm +209 -0
  30. package/package.json +26 -10
  31. package/react-native.config.cjs +15 -0
  32. package/src/Callout.stories.tsx +276 -0
  33. package/src/Callout.tsx +98 -64
  34. package/src/Callout.types.ts +50 -172
  35. package/src/CalloutNativeComponent.ts +10 -5
  36. package/src/__tests__/Callout.test.tsx +152 -8
  37. package/src/__tests__/Callout.types.test.ts +30 -0
  38. package/src/__tests__/__snapshots__/Callout.test.tsx.snap +8 -10
  39. package/src/index.ts +4 -4
  40. package/tsconfig.json +2 -13
  41. package/windows/Callout/Callout.cpp +420 -0
  42. package/windows/Callout/Callout.def +3 -0
  43. package/windows/Callout/Callout.h +25 -0
  44. package/windows/Callout/Callout.vcxproj +152 -0
  45. package/windows/Callout/ReactPackageProvider.cpp +25 -0
  46. package/windows/Callout/ReactPackageProvider.h +25 -0
  47. package/windows/Callout/ReactPackageProvider.idl +12 -0
  48. package/windows/Callout/codegen/.clang-format +2 -0
  49. package/windows/Callout/codegen/react/components/FRNCalloutSpec/Callout.g.h +375 -0
  50. package/windows/Callout/packages.lock.json +151 -0
  51. package/windows/Callout/pch.cpp +1 -0
  52. package/windows/Callout/pch.h +28 -0
  53. package/windows/ExperimentalFeatures.props +33 -0
  54. package/windows/NuGet.Config +13 -0
  55. package/lib/Callout.settings.d.ts +0 -4
  56. package/lib/Callout.settings.d.ts.map +0 -1
  57. package/lib/Callout.settings.js +0 -16
  58. package/lib/Callout.settings.js.map +0 -1
  59. package/lib/Callout.settings.macos.d.ts +0 -4
  60. package/lib/Callout.settings.macos.d.ts.map +0 -1
  61. package/lib/Callout.settings.macos.js +0 -19
  62. package/lib/Callout.settings.macos.js.map +0 -1
  63. package/lib/CalloutNativeComponent.macos.d.ts +0 -45
  64. package/lib/CalloutNativeComponent.macos.d.ts.map +0 -1
  65. package/lib/CalloutNativeComponent.macos.js +0 -7
  66. package/lib/CalloutNativeComponent.macos.js.map +0 -1
  67. package/src/Callout.settings.macos.ts +0 -22
  68. package/src/Callout.settings.ts +0 -19
  69. package/src/CalloutNativeComponent.macos.ts +0 -69
@@ -1,17 +1,8 @@
1
1
  import type * as React from 'react';
2
- import type { KeyboardMetrics, ViewStyle } from 'react-native';
2
+ import type { AnimatableNumericValue, ColorValue, KeyboardMetrics, ViewProps } from 'react-native';
3
3
 
4
- import type { IViewProps } from '@fluentui-react-native/adapters';
5
- import type { IBackgroundColorTokens, IBorderTokens } from '@fluentui-react-native/tokens';
6
- import type { IRenderData } from '@uifabricshared/foundation-composable';
7
-
8
- import type { CalloutNativeCommands } from './CalloutNativeComponent';
9
4
  export const calloutName = 'Callout';
10
5
 
11
- /**
12
- * Properties and Tokens for FluentUI React Native Callout
13
- */
14
-
15
6
  export type DirectionalHint =
16
7
  | 'leftTopEdge'
17
8
  | 'leftCenter'
@@ -24,7 +15,6 @@ export type DirectionalHint =
24
15
  | 'rightCenter'
25
16
  | 'rightBottomEdge'
26
17
  | 'bottomLeftEdge'
27
- | 'bottonLeftEdge' // Typo in the original code, should be 'bottomLeftEdge'
28
18
  | 'bottomAutoEdge'
29
19
  | 'bottomCenter'
30
20
  | 'bottomRightEdge';
@@ -33,196 +23,84 @@ export type DismissBehaviors = 'preventDismissOnKeyDown' | 'preventDismissOnClic
33
23
 
34
24
  export interface RestoreFocusEvent {
35
25
  nativeEvent: {
36
- /**
37
- * containsFocus is true if the Callout had focus while being dismissed.
38
- */
26
+ /** True when the Callout contained focus while it was dismissed. */
39
27
  containsFocus: boolean;
40
28
  };
41
29
  }
42
30
 
43
- interface OmittedBorderTokens {
44
- borderStyle?: ViewStyle['borderStyle'];
45
- }
46
-
47
- type CalloutBorderTokens = Omit<IBorderTokens, keyof OmittedBorderTokens>;
48
-
49
- export interface ICalloutTokens extends IBackgroundColorTokens, CalloutBorderTokens {
50
- /**
51
- * AnchorRect arbitrary anchor rectangle; coordinate system is in DIPs, relative
52
- * to the React surface origin.
53
- */
31
+ /**
32
+ * Optional native appearance and positioning values. Callout applies only values
33
+ * supplied by the caller and does not resolve theme defaults.
34
+ */
35
+ export interface CalloutTokens {
36
+ /** Anchor rectangle in DIPs relative to the React surface origin. */
54
37
  anchorRect?: KeyboardMetrics;
55
-
56
- /**
57
- * Width of the beak on the Callout indicating its anchor.
58
- */
38
+ /** Native Callout background color. */
39
+ backgroundColor?: ColorValue;
40
+ /** Width of the beak that points toward the anchor. */
59
41
  beakWidth?: number;
60
-
61
- /**
62
- * Defines the suggested drop direction and alignment for the Callout to use, relative
63
- * to the anchor information.
64
- */
42
+ /** Native Callout border color. */
43
+ borderColor?: ColorValue;
44
+ /** Native Callout corner radius. */
45
+ borderRadius?: AnimatableNumericValue | string;
46
+ /** Native Callout border width. */
47
+ borderWidth?: number;
48
+ /** Preferred placement relative to the anchor. */
65
49
  directionalHint?: DirectionalHint;
66
-
67
- /**
68
- * Defines variations on how the callout dismissal may be controlled. the async eventing
69
- * of React-Native makes passing some aspects of dismissal control over to JS difficult.
70
- * Moreover, the native platform or host may have competing priorities with regards to transient UI
71
- * that generate bi-directional lifetime management between JS (which mounts and unmounts the
72
- * component) and native (which may tear down the transient UI without JS input).
73
- *
74
- * This property provides control over the latter issue, enabling relevant native platform
75
- * interactions with transient UI to be managed from JS.
76
- *
77
- * These behaviors should generally be orthogonal, and therefore combinable.
78
- */
50
+ /** Native dismissal behaviors that may be combined. */
79
51
  dismissBehaviors?: DismissBehaviors[];
80
-
81
- /**
82
- * Defines the size of the gap between the anchor and the Callout. Not used if
83
- * no anchor information is provided.
84
- */
52
+ /** Gap between the anchor and Callout. */
85
53
  gapSpace?: number;
86
-
87
- /**
88
- * Defines a maximum height for the Callout.
89
- */
54
+ /** Maximum Callout height. */
90
55
  maxHeight?: number | `${number}%`;
91
-
92
- /**
93
- * Defines a maximum width for the Callout.
94
- */
56
+ /** Maximum Callout width. */
95
57
  maxWidth?: number | `${number}%`;
96
-
97
- /**
98
- * Defines a minimum width for the Callout.
99
- */
58
+ /** Minimum padding from display edges. */
59
+ minPadding?: number;
60
+ /** Minimum Callout width. */
100
61
  minWidth?: number | `${number}%`;
62
+ }
101
63
 
102
- /**
103
- * Defines the minimum padding between the Callout and the display edges.
104
- */
105
- minPadding?: number;
64
+ export interface CalloutHandle {
65
+ /** Makes the native Callout window resign key status. */
66
+ blurWindow: () => void;
67
+ /** Makes the native Callout window key. */
68
+ focusWindow: () => void;
106
69
  }
107
70
 
108
- export interface ICalloutProps extends IViewProps, ICalloutTokens {
71
+ export interface CalloutProps extends ViewProps, CalloutTokens {
109
72
  /**
110
- * A string that should be announced when the callout is shown.
73
+ * A string announced when the Callout is shown.
111
74
  * @platform win32
112
75
  */
113
76
  accessibilityOnShowAnnouncement?: string;
114
-
77
+ /** Ref used to invoke native Callout window commands. */
78
+ componentRef?: React.Ref<CalloutHandle>;
115
79
  /**
116
- * A RefObject to access the IFocusable interface. Use this to access the public methods and properties of the component.
117
- */
118
- componentRef?: React.RefObject<CalloutNativeCommands>;
119
-
120
- /**
121
- * Defines event redirection behaviors for pointer events relative to the Callout control.
122
- *
123
- * Pointer events (i.e. mouse, pen, touch) often have the effect of initiating and dismissing Callout controls.
124
- * Typically the instigating events are click events, but when using a mouse may also include pointer enter
125
- * and leave events i.e. hover. Once opened, many Callout scenarios desire modal-like interactions, where
126
- * the user must interact with or relative to the Callout before interacting with other controls again. A common
127
- * example is context menus, where pointer events away from the menu may dismiss the context menu. Such an interaction
128
- * can initiate unintended actions such as invoking a button or otherwise pointer-responsive surface. The modal-like
129
- * behavior is the most common case of Callout usage, and having "safe" areas to press-dismiss the Callout without
130
- * invoking additional actions is useful.
131
- *
132
- * This modal-like behavior, where true modal behavior would specifically require interacting with the modal component until
133
- * it is dismissed, has implications for canonical event routing in React-Native. Canonically all pointer events are passed in
134
- * a Capture phase from the root element to the target element, then bubbled back from the target element to the root element.
135
- * This structure allows many interaction patterns to be implemented that may include stopping event routing in each phase.
136
- * This event routing can disrupt the modal-like behavior desired by a Callout control, which in native frameworks may take
137
- * advantage of a "take event capture" feature -- making the Callout the first handler of the pointer event. The event may optionally
138
- * be forwarded afterwards to the canonical event routing pattern for further handling (and often is not forwarded).
139
- *
140
- * One impact of "taking event capture" is the implicit behavior for mouse events on the parent surface of the Callout -- if
141
- * event capture is taken by the Callout, paired events may fire such as a mouse leave event. This makes sense if we think about
142
- * the event behavior after the Callout has taken capture; mouse enter and mouse leave events would only propagate to the parent surface
143
- * if the Callout event handling decided to forward said events (often not). As paired events, however, there should not be a
144
- * mouse enter event without a corresponding mouse leave event (or vice versa). Since mouse enter and mouse leave events would not
145
- * naturally fire on the parent surface when the Callout has pointer capture, the parent surface should receive a mouse leave event when
146
- * the Callout takes capture.
147
- *
148
- * Coming back to the start of this comment, some Callout scenarios may be initiated by mouse enter/leave events. A common example is
149
- * tooltips, which are supported on lower-level React-Native components, but such behavior is not exclusive to tooltips.
150
- * The Callout control is generally used for more advanced scenarios, as mentioned before often desiring modal-like behavior and accordingly
151
- * the default behavior of the Callout control is to take pointer capture. Accounting for the paired mouse enter/leave events in the paragraph
152
- * above, it is difficult to have a mouse enter/leave-invoked Callout that takes pointer capture -- without significant workarounds,
153
- * the Callout would open and close cyclically from repeated mouse enter/leave events. There are other scenarios where taking pointer capture
154
- * is not desirable, such as a semi-persistent Callout; to support these scenarios the doNotTakePointerCapture property is offered to
155
- * specify if the Callout should not take pointer capture when it opens.
156
- *
157
- * While pointer capture is generally managed in native frameworks via imperative APIs, managing pointer capture is offered as a property that
158
- * only affects the Callout's on-show behavior when it would typically take pointer capture. This design implies that changing this property after
159
- * the Callout has been shown has no effect -- similar to the Callout's onShow callback. Doing so simplifies Callout usage for developers,
160
- * not needing to mind native platform pointer capture that may be complex particularly when React-Native is integrated into a larger app.
161
- * Pointer capture may be lost (and regained) but is otherwise abstracted away from the developer. More advanced control of pointer capture
162
- * would need to be provided by a native module independent of any particular control.
163
- *
164
- * When the Callout is closed, pointer capture is released by the Callout and further handled by the native platform -- generally returning
165
- * pointer capture to the parent surface.
80
+ * Prevents the native Callout from taking pointer capture when shown.
166
81
  * @platform win32
167
82
  */
168
83
  doNotTakePointerCapture?: boolean;
169
-
170
- /**
171
- * Adds a beak to the Callout, pointing to the anchor target.
172
- * Notable Win32 limitation: Beak rendering currently limits the border width to its default, and the
173
- * border width prop will not be honored.
174
- * @platform win32
175
- */
84
+ /** Displays a beak that points toward the anchor. */
176
85
  isBeakVisible?: boolean;
177
-
178
- /**
179
- * Callback invoked when the callout has been dismissed.
180
- */
86
+ /** Invoked after native dismissal. */
181
87
  onDismiss?: () => void;
182
-
183
88
  /**
184
- * Callback invoked during callout dismissal; if set, focus will not be restored by the callout and onRestoreFocus must
185
- * result in focus being moved to the appropriate focusable target.
186
- *
187
- * The callee should carefully consider their scenarios to avoid dropping focus, or inappropriately
188
- * moving focus from another component. Focus is not guaranteed to have entered the React-Native surface at all, and
189
- * this callback is most appropriate for components strictly controlling focus.
190
- *
191
- * restoreFocusEvent.nativeEvent.containsFocus is true if the Callout had focus while being dismissed.
89
+ * Invoked during dismissal when the caller owns focus restoration.
192
90
  * @platform win32
193
91
  */
194
- onRestoreFocus?: (restoreFocusEvent: RestoreFocusEvent) => void;
195
-
196
- /**
197
- * Callback invoked when the callout has been shown.
198
- */
92
+ onRestoreFocus?: (event: RestoreFocusEvent) => void;
93
+ /** Invoked after the Callout is shown. */
199
94
  onShow?: () => void;
200
-
201
- /**
202
- * Determines whether the Callout sets focus when displayed.
203
- * On macOS: this determines whether the Callout becomes the key window.
204
- * On win32: If true then the callout will attempt to focus the first focusable element that it contains.
205
- * If it doesn't find an element, no focus will be set. This means that it's the contents responsibility
206
- * to either set focus or have focusable items.
207
- */
95
+ /** Requests initial focus when the Callout is shown. */
208
96
  setInitialFocus?: boolean;
209
-
210
- /**
211
- * Target node the callout uses for relative positioning; the anchor of the callout.
212
- * A ref is the typical usage; certain components may proffer a string as an anchor target, such as
213
- * anchoring to a point inside the component.
214
- */
215
- target?: React.RefObject<React.Component> | string;
97
+ /** Ref or registered native anchor identifier used for relative positioning. */
98
+ target?: React.RefObject<React.Component | null> | string;
216
99
  }
217
100
 
218
- export type ICalloutSlotProps = {
219
- root: ICalloutProps;
220
- };
221
-
222
- export type ICalloutRenderData = IRenderData<ICalloutSlotProps>;
223
-
224
- export interface ICalloutType {
225
- props: ICalloutProps;
226
- slotProps: ICalloutSlotProps;
227
- tokens: ICalloutTokens;
228
- }
101
+ /** @deprecated Use CalloutProps. */
102
+ export type ICalloutProps = CalloutProps;
103
+ /** @deprecated Use CalloutTokens. */
104
+ export type ICalloutTokens = CalloutTokens;
105
+ /** @deprecated Use CalloutHandle. */
106
+ export type CalloutNativeCommands = CalloutHandle;
@@ -19,12 +19,16 @@ export interface NativeProps extends ViewProps {
19
19
  accessibilityOnShowAnnouncement?: string;
20
20
  anchorRect?: AnchorRect;
21
21
 
22
+ beakWidth?: Int32;
22
23
  dismissBehaviors?: string[];
23
24
  doNotTakePointerCapture?: boolean;
24
25
  focusable?: boolean;
26
+ gapSpace?: Int32;
25
27
  isBeakVisible?: boolean;
26
28
  maxHeight?: Int32;
27
29
  maxWidth?: Int32;
30
+ minPadding?: Int32;
31
+ minWidth?: Int32;
28
32
  setInitialFocus?: boolean;
29
33
 
30
34
  // targetAnchor?: string; // Win32 only Callout can target an anchor registered in the anchor registry // Can be a node id or an anchor ID - This need to be reworked as Mixed types are not supported going forward
@@ -46,24 +50,25 @@ export interface NativeProps extends ViewProps {
46
50
  | 'rightCenter'
47
51
  | 'rightBottomEdge'
48
52
  | 'bottomLeftEdge'
49
- | 'bottonLeftEdge' // Typo in the original code, should be 'bottomLeftEdge'
50
53
  | 'bottomAutoEdge'
51
54
  | 'bottomCenter'
52
55
  | 'bottomRightEdge',
53
- 'bottonLeftEdge'
56
+ 'bottomLeftEdge'
54
57
  >;
55
58
  target?: UnsafeMixed;
56
59
  }
57
60
 
58
61
  export type CalloutComponentType = HostComponent<NativeProps>;
59
62
 
60
- export interface CalloutNativeCommands {
63
+ interface NativeCalloutCommands {
61
64
  focusWindow: (viewRef: React.ElementRef<CalloutComponentType>) => void;
62
65
  blurWindow: (viewRef: React.ElementRef<CalloutComponentType>) => void;
63
66
  }
64
67
 
65
- export const Commands: CalloutNativeCommands = codegenNativeCommands<CalloutNativeCommands>({
68
+ export const Commands: NativeCalloutCommands = codegenNativeCommands<NativeCalloutCommands>({
66
69
  supportedCommands: ['blurWindow', 'focusWindow'],
67
70
  });
68
71
 
69
- export default codegenNativeComponent<NativeProps>('RCTCallout') as CalloutComponentType;
72
+ export default codegenNativeComponent<NativeProps>('Callout', {
73
+ paperComponentName: 'RCTCallout',
74
+ }) as CalloutComponentType;
@@ -1,13 +1,157 @@
1
- import { act } from 'react';
1
+ /** @jsxImportSource @fluentui-react-native/framework-base */
2
+ import * as React from 'react';
3
+ import { StyleSheet, Text, View } from 'react-native';
2
4
 
3
- import * as renderer from 'react-test-renderer';
5
+ import { act, render } from '@testing-library/react-native';
6
+ import { isPhasedComponent, useSlot } from '@fluentui-react-native/framework-base';
7
+ import type { SlotProp } from '@fluentui-react-native/framework-base';
4
8
 
5
- import { Callout } from '..';
9
+ import { Callout } from '../Callout';
10
+ import type { CalloutHandle } from '../Callout.types';
11
+ import { Commands } from '../CalloutNativeComponent';
6
12
 
7
- it('Callout default props', () => {
8
- let component: renderer.ReactTestRenderer;
9
- act(() => {
10
- component = renderer.create(<Callout />);
13
+ jest.mock('react-native/Libraries/ReactNative/RendererProxy', () => ({
14
+ ...jest.requireActual('react-native/Libraries/ReactNative/RendererProxy'),
15
+ findNodeHandle: jest.fn(() => 42),
16
+ }));
17
+
18
+ type CalloutSlotConsumerProps = {
19
+ callout: SlotProp<typeof Callout>;
20
+ };
21
+
22
+ function CalloutSlotConsumer({ callout }: CalloutSlotConsumerProps) {
23
+ const CalloutSlot = useSlot(Callout, callout);
24
+ return (
25
+ <CalloutSlot accessibilityHint="rendered as a slot" testID="slotted-callout">
26
+ <Text>Slot content</Text>
27
+ </CalloutSlot>
28
+ );
29
+ }
30
+
31
+ describe('Callout', () => {
32
+ afterEach(() => {
33
+ jest.restoreAllMocks();
34
+ });
35
+
36
+ it('uses phased rendering and applies native-safe default styling', async () => {
37
+ expect(isPhasedComponent(Callout)).toBe(true);
38
+
39
+ const component = await render(<Callout testID="callout" />);
40
+ const callout = component.getByTestId('callout');
41
+
42
+ expect(callout.type).toBe('RCTCallout');
43
+ expect(StyleSheet.flatten(callout.props.style)).toMatchSnapshot();
44
+ });
45
+
46
+ it('translates caller-supplied appearance values and preserves user style precedence', async () => {
47
+ const component = await render(
48
+ <Callout
49
+ backgroundColor="red"
50
+ borderColor="green"
51
+ borderRadius={8}
52
+ borderWidth={2}
53
+ maxHeight={240}
54
+ maxWidth="80%"
55
+ minWidth={120}
56
+ style={{ backgroundColor: 'blue', opacity: 0.5 }}
57
+ testID="callout"
58
+ />,
59
+ );
60
+ const callout = component.getByTestId('callout');
61
+
62
+ expect(StyleSheet.flatten(callout.props.style)).toEqual({
63
+ position: 'absolute',
64
+ backgroundColor: 'blue',
65
+ borderColor: 'green',
66
+ borderRadius: 8,
67
+ borderWidth: 2,
68
+ maxHeight: 240,
69
+ maxWidth: '80%',
70
+ minWidth: 120,
71
+ opacity: 0.5,
72
+ });
73
+ expect(callout.props.backgroundColor).toBeUndefined();
74
+ expect(callout.props.maxHeight).toBe(240);
75
+ expect(callout.props.maxWidth).toBeUndefined();
76
+ expect(callout.props.minWidth).toBe(120);
77
+ });
78
+
79
+ it('forwards native behavior, accessibility, callbacks, and children', async () => {
80
+ const onDismiss = jest.fn();
81
+ const onShow = jest.fn();
82
+ const anchorRect = { screenX: 10, screenY: 20, width: 30, height: 40 };
83
+ const component = await render(
84
+ <Callout
85
+ accessibilityLabel="Formatting options"
86
+ anchorRect={anchorRect}
87
+ beakWidth={12}
88
+ directionalHint="rightTopEdge"
89
+ dismissBehaviors={['preventDismissOnClickOutside']}
90
+ gapSpace={4}
91
+ minPadding={8}
92
+ onDismiss={onDismiss}
93
+ onShow={onShow}
94
+ target="editor-anchor"
95
+ testID="callout"
96
+ >
97
+ <Text>Callout content</Text>
98
+ </Callout>,
99
+ );
100
+ const callout = component.getByTestId('callout');
101
+
102
+ expect(callout.props).toMatchObject({
103
+ accessibilityLabel: 'Formatting options',
104
+ anchorRect,
105
+ beakWidth: 12,
106
+ directionalHint: 'rightTopEdge',
107
+ dismissBehaviors: ['preventDismissOnClickOutside'],
108
+ gapSpace: 4,
109
+ minPadding: 8,
110
+ onDismiss,
111
+ onShow,
112
+ target: 'editor-anchor',
113
+ });
114
+ expect(component.getByText('Callout content')).toBeTruthy();
115
+ });
116
+
117
+ it('resolves a React target ref to its native handle', async () => {
118
+ const target = React.createRef<View>();
119
+ const component = await render(
120
+ <>
121
+ <View ref={target} />
122
+ <Callout target={target} testID="callout" />
123
+ </>,
124
+ );
125
+
126
+ expect(target.current).not.toBeNull();
127
+ expect(component.getByTestId('callout').props.target).toBe(42);
128
+ });
129
+
130
+ it('exposes native window commands through componentRef', async () => {
131
+ const focusWindow = jest.spyOn(Commands, 'focusWindow').mockImplementation(() => undefined);
132
+ const blurWindow = jest.spyOn(Commands, 'blurWindow').mockImplementation(() => undefined);
133
+ const componentRef = React.createRef<CalloutHandle>();
134
+
135
+ await render(<Callout componentRef={componentRef} testID="callout" />);
136
+
137
+ await act(async () => {
138
+ componentRef.current?.focusWindow();
139
+ componentRef.current?.blurWindow();
140
+ });
141
+
142
+ expect(focusWindow).toHaveBeenCalledTimes(1);
143
+ expect(blurWindow).toHaveBeenCalledTimes(1);
144
+ });
145
+
146
+ it('can be consumed as a slot with merged render props', async () => {
147
+ const component = await render(<CalloutSlotConsumer callout={{ accessibilityLabel: 'Slotted Callout', target: 'slot-anchor' }} />);
148
+ const callout = component.getByTestId('slotted-callout');
149
+
150
+ expect(callout.props).toMatchObject({
151
+ accessibilityHint: 'rendered as a slot',
152
+ accessibilityLabel: 'Slotted Callout',
153
+ target: 'slot-anchor',
154
+ });
155
+ expect(component.getByText('Slot content')).toBeTruthy();
11
156
  });
12
- expect(component!.toJSON()).toMatchSnapshot();
13
157
  });
@@ -0,0 +1,30 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ import * as React from 'react';
3
+
4
+ import type { SlotProp } from '@fluentui-react-native/framework-base';
5
+
6
+ import type { Callout } from '../Callout';
7
+ import type { CalloutHandle, CalloutProps, ICalloutProps } from '../Callout.types';
8
+
9
+ const componentRef = React.createRef<CalloutHandle>();
10
+ const target = React.createRef<React.Component>();
11
+
12
+ const props: CalloutProps = {
13
+ accessibilityLabel: 'Formatting options',
14
+ componentRef,
15
+ directionalHint: 'bottomCenter',
16
+ target,
17
+ };
18
+
19
+ const calloutSlot: SlotProp<typeof Callout> = props;
20
+ const legacyProps: ICalloutProps = props;
21
+
22
+ // @ts-expect-error Callout targets must be a ref or registered native anchor identifier.
23
+ const invalidTarget: CalloutProps = { target: 42 };
24
+
25
+ describe('Callout types', () => {
26
+ it('accepts public props as a slot contract', () => {
27
+ expect(calloutSlot).toBeDefined();
28
+ expect(legacyProps).toBeDefined();
29
+ });
30
+ });
@@ -1,13 +1,11 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
- exports[`Callout default props 1`] = `
4
- <RCTCallout
5
- style={
6
- {
7
- "backgroundColor": "#faf9f8",
8
- "borderColor": "#ffffff",
9
- "borderWidth": 1,
10
- }
11
- }
12
- />
3
+ exports[`Callout uses phased rendering and applies native-safe default styling 1`] = `
4
+ {
5
+ "backgroundColor": "#00000000",
6
+ "borderColor": "#00000000",
7
+ "borderRadius": 0,
8
+ "borderWidth": 0,
9
+ "position": "absolute",
10
+ }
13
11
  `;
package/src/index.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  export { calloutName } from './Callout.types';
2
2
  export type {
3
+ CalloutHandle,
4
+ CalloutNativeCommands,
5
+ CalloutProps,
6
+ CalloutTokens,
3
7
  DirectionalHint,
4
8
  DismissBehaviors,
5
9
  ICalloutProps,
6
- ICalloutRenderData,
7
- ICalloutSlotProps,
8
10
  ICalloutTokens,
9
- ICalloutType,
10
11
  RestoreFocusEvent,
11
12
  } from './Callout.types';
12
- export type { CalloutNativeCommands } from './CalloutNativeComponent';
13
13
  export { Callout } from './Callout';
package/tsconfig.json CHANGED
@@ -7,24 +7,13 @@
7
7
  "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo"
8
8
  },
9
9
  "include": ["src"],
10
+ "exclude": ["**/*.stories.ts", "**/*.stories.tsx"],
10
11
  "references": [
11
12
  {
12
- "path": "../../utils/adapters/tsconfig.json"
13
+ "path": "../../framework-base/tsconfig.json"
13
14
  },
14
15
  {
15
16
  "path": "../../../scripts/tsconfig.json"
16
- },
17
- {
18
- "path": "../../utils/tokens/tsconfig.json"
19
- },
20
- {
21
- "path": "../../deprecated/foundation-composable/tsconfig.json"
22
- },
23
- {
24
- "path": "../../deprecated/foundation-compose/tsconfig.json"
25
- },
26
- {
27
- "path": "../../deprecated/foundation-settings/tsconfig.json"
28
17
  }
29
18
  ]
30
19
  }