@fluentui-react-native/callout 0.28.5 → 0.29.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/.cache/tsconfig.tsbuildinfo +1 -1
- package/CHANGELOG.md +65 -0
- package/FRNCallout.podspec +1 -1
- package/NuGet.config +10 -0
- package/SPEC.md +46 -0
- package/lib/Callout.d.ts +2 -5
- package/lib/Callout.d.ts.map +1 -1
- package/lib/Callout.js +57 -58
- package/lib/Callout.js.map +1 -1
- package/lib/Callout.types.d.ts +51 -152
- package/lib/Callout.types.d.ts.map +1 -1
- package/lib/Callout.types.js.map +1 -1
- package/lib/CalloutNativeComponent.d.ts +7 -3
- package/lib/CalloutNativeComponent.d.ts.map +1 -1
- package/lib/CalloutNativeComponent.js +3 -1
- package/lib/CalloutNativeComponent.js.map +1 -1
- package/lib/__tests__/Callout.test.js +94 -10
- package/lib/__tests__/Callout.test.js.map +1 -1
- package/lib/__tests__/Callout.types.test.d.ts +2 -0
- package/lib/__tests__/Callout.types.test.d.ts.map +1 -0
- package/lib/__tests__/Callout.types.test.js +21 -0
- package/lib/__tests__/Callout.types.test.js.map +1 -0
- package/lib/index.d.ts +1 -2
- package/lib/index.d.ts.map +1 -1
- package/macos/CalloutManager.swift +1 -1
- package/macos/CalloutView.swift +49 -17
- package/macos/FRNCalloutManager.m +1 -2
- package/macos/RCTCalloutComponentView.h +12 -0
- package/macos/RCTCalloutComponentView.mm +208 -0
- package/package.json +27 -12
- package/react-native.config.cjs +15 -0
- package/src/Callout.stories.tsx +271 -0
- package/src/Callout.tsx +98 -64
- package/src/Callout.types.ts +50 -172
- package/src/CalloutNativeComponent.ts +10 -5
- package/src/__tests__/Callout.test.tsx +152 -8
- package/src/__tests__/Callout.types.test.ts +30 -0
- package/src/__tests__/__snapshots__/Callout.test.tsx.snap +8 -10
- package/src/index.ts +4 -4
- package/tsconfig.json +2 -13
- package/windows/Callout/Callout.cpp +398 -0
- package/windows/Callout/Callout.def +3 -0
- package/windows/Callout/Callout.h +25 -0
- package/windows/Callout/Callout.vcxproj +152 -0
- package/windows/Callout/ReactPackageProvider.cpp +25 -0
- package/windows/Callout/ReactPackageProvider.h +25 -0
- package/windows/Callout/ReactPackageProvider.idl +12 -0
- package/windows/Callout/codegen/.clang-format +2 -0
- package/windows/Callout/codegen/react/components/FRNCalloutSpec/Callout.g.h +375 -0
- package/windows/Callout/packages.lock.json +151 -0
- package/windows/Callout/pch.cpp +1 -0
- package/windows/Callout/pch.h +28 -0
- package/windows/ExperimentalFeatures.props +33 -0
- package/windows/NuGet.Config +13 -0
- package/lib/Callout.settings.d.ts +0 -4
- package/lib/Callout.settings.d.ts.map +0 -1
- package/lib/Callout.settings.js +0 -16
- package/lib/Callout.settings.js.map +0 -1
- package/lib/Callout.settings.macos.d.ts +0 -4
- package/lib/Callout.settings.macos.d.ts.map +0 -1
- package/lib/Callout.settings.macos.js +0 -19
- package/lib/Callout.settings.macos.js.map +0 -1
- package/lib/CalloutNativeComponent.macos.d.ts +0 -45
- package/lib/CalloutNativeComponent.macos.d.ts.map +0 -1
- package/lib/CalloutNativeComponent.macos.js +0 -7
- package/lib/CalloutNativeComponent.macos.js.map +0 -1
- package/src/Callout.settings.macos.ts +0 -22
- package/src/Callout.settings.ts +0 -19
- package/src/CalloutNativeComponent.macos.ts +0 -69
package/src/Callout.types.ts
CHANGED
|
@@ -1,17 +1,8 @@
|
|
|
1
1
|
import type * as React from 'react';
|
|
2
|
-
import type { KeyboardMetrics,
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
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
|
-
|
|
63
|
-
|
|
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
|
-
|
|
99
|
-
*/
|
|
58
|
+
/** Minimum padding from display edges. */
|
|
59
|
+
minPadding?: number;
|
|
60
|
+
/** Minimum Callout width. */
|
|
100
61
|
minWidth?: number | `${number}%`;
|
|
62
|
+
}
|
|
101
63
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
|
71
|
+
export interface CalloutProps extends ViewProps, CalloutTokens {
|
|
109
72
|
/**
|
|
110
|
-
* A string
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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?: (
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
'
|
|
56
|
+
'bottomLeftEdge'
|
|
54
57
|
>;
|
|
55
58
|
target?: UnsafeMixed;
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
export type CalloutComponentType = HostComponent<NativeProps>;
|
|
59
62
|
|
|
60
|
-
|
|
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:
|
|
68
|
+
export const Commands: NativeCalloutCommands = codegenNativeCommands<NativeCalloutCommands>({
|
|
66
69
|
supportedCommands: ['blurWindow', 'focusWindow'],
|
|
67
70
|
});
|
|
68
71
|
|
|
69
|
-
export default codegenNativeComponent<NativeProps>('
|
|
72
|
+
export default codegenNativeComponent<NativeProps>('Callout', {
|
|
73
|
+
paperComponentName: 'RCTCallout',
|
|
74
|
+
}) as CalloutComponentType;
|
|
@@ -1,13 +1,157 @@
|
|
|
1
|
-
|
|
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
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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": "../../
|
|
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
|
}
|