@fluentui-react-native/callout 0.29.6 → 0.29.7
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/CHANGELOG.md +11 -0
- package/README.md +18 -0
- package/lib/index.d.ts +2 -3
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -2
- package/lib/index.js.map +1 -1
- package/package.json +12 -27
- package/src/index.ts +2 -3
- package/.cache/tsconfig.tsbuildinfo +0 -1
- package/FRNCallout.podspec +0 -24
- package/NuGet.config +0 -10
- package/SPEC.md +0 -46
- package/jest.config.cjs +0 -1
- package/lib/Callout.d.ts +0 -6
- package/lib/Callout.d.ts.map +0 -1
- package/lib/Callout.js +0 -64
- package/lib/Callout.js.map +0 -1
- package/lib/Callout.types.d.ts +0 -85
- package/lib/Callout.types.d.ts.map +0 -1
- package/lib/Callout.types.js +0 -2
- package/lib/Callout.types.js.map +0 -1
- package/lib/CalloutNativeComponent.d.ts +0 -49
- package/lib/CalloutNativeComponent.d.ts.map +0 -1
- package/lib/CalloutNativeComponent.js +0 -9
- package/lib/CalloutNativeComponent.js.map +0 -1
- package/lib/__tests__/Callout.test.d.ts +0 -2
- package/lib/__tests__/Callout.test.d.ts.map +0 -1
- package/lib/__tests__/Callout.test.js +0 -96
- package/lib/__tests__/Callout.test.js.map +0 -1
- package/lib/__tests__/Callout.types.test.d.ts +0 -2
- package/lib/__tests__/Callout.types.test.d.ts.map +0 -1
- package/lib/__tests__/Callout.types.test.js +0 -21
- package/lib/__tests__/Callout.types.test.js.map +0 -1
- package/macos/CalloutManager.swift +0 -16
- package/macos/CalloutView.swift +0 -454
- package/macos/CalloutWindow.swift +0 -40
- package/macos/FRNCallout.h +0 -14
- package/macos/FRNCalloutManager.h +0 -5
- package/macos/FRNCalloutManager.m +0 -76
- package/macos/FlippedVisualEffectView.swift +0 -10
- package/macos/GuardedEventMonitor.swift +0 -28
- package/macos/RCTCalloutComponentView.h +0 -12
- package/macos/RCTCalloutComponentView.mm +0 -209
- package/react-native.config.cjs +0 -15
- package/src/Callout.stories.tsx +0 -276
- package/src/Callout.tsx +0 -111
- package/src/Callout.types.ts +0 -106
- package/src/CalloutNativeComponent.ts +0 -74
- package/src/__tests__/Callout.test.tsx +0 -157
- package/src/__tests__/Callout.types.test.ts +0 -30
- package/src/__tests__/__snapshots__/Callout.test.tsx.snap +0 -11
- package/tsconfig.json +0 -19
- package/windows/Callout/Callout.cpp +0 -420
- package/windows/Callout/Callout.def +0 -3
- package/windows/Callout/Callout.h +0 -25
- package/windows/Callout/Callout.vcxproj +0 -152
- package/windows/Callout/ReactPackageProvider.cpp +0 -25
- package/windows/Callout/ReactPackageProvider.h +0 -25
- package/windows/Callout/ReactPackageProvider.idl +0 -12
- package/windows/Callout/codegen/.clang-format +0 -2
- package/windows/Callout/codegen/react/components/FRNCalloutSpec/Callout.g.h +0 -375
- package/windows/Callout/packages.lock.json +0 -151
- package/windows/Callout/pch.cpp +0 -1
- package/windows/Callout/pch.h +0 -28
- package/windows/ExperimentalFeatures.props +0 -33
- package/windows/NuGet.Config +0 -13
package/macos/CalloutView.swift
DELETED
|
@@ -1,454 +0,0 @@
|
|
|
1
|
-
import AppKit
|
|
2
|
-
import Foundation
|
|
3
|
-
#if USE_REACT_AS_MODULE
|
|
4
|
-
import React
|
|
5
|
-
#endif // USE_REACT_AS_MODULE
|
|
6
|
-
|
|
7
|
-
@objc(FRNCalloutView)
|
|
8
|
-
open class CalloutView: RCTView, CalloutWindowLifeCycleDelegate {
|
|
9
|
-
|
|
10
|
-
@objc public var target: NSNumber? {
|
|
11
|
-
didSet {
|
|
12
|
-
let targetView = bridge?.uiManager.view(forReactTag: target)
|
|
13
|
-
if (targetView == nil && target != nil) {
|
|
14
|
-
preconditionFailure("Invalid target")
|
|
15
|
-
}
|
|
16
|
-
anchorView = targetView
|
|
17
|
-
updateCalloutFrameToAnchor()
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
@objc public var anchorRect: NSRect = .null {
|
|
22
|
-
didSet {
|
|
23
|
-
updateCalloutFrameToAnchor()
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
@objc public var directionalHint: NSRectEdge = .maxY {
|
|
28
|
-
didSet {
|
|
29
|
-
updateCalloutFrameToAnchor()
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
@objc public var setInitialFocus: Bool = false
|
|
34
|
-
|
|
35
|
-
@objc public var onShow: RCTDirectEventBlock?
|
|
36
|
-
|
|
37
|
-
@objc public var onDismiss: RCTDirectEventBlock?
|
|
38
|
-
|
|
39
|
-
@objc public func focusWindow() {
|
|
40
|
-
calloutWindow.makeKey()
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
@objc public func blurWindow() {
|
|
44
|
-
calloutWindow.parent?.makeKey()
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
public weak var bridge: RCTBridge?
|
|
48
|
-
|
|
49
|
-
@objc public override init(frame frameRect: NSRect) {
|
|
50
|
-
super.init(frame: frameRect)
|
|
51
|
-
|
|
52
|
-
backgroundColor = .clear
|
|
53
|
-
borderColor = .clear
|
|
54
|
-
|
|
55
|
-
// Listens for mouse clicks in the main menu bar while callout is shown
|
|
56
|
-
NotificationCenter.default.addObserver(
|
|
57
|
-
self,
|
|
58
|
-
selector: #selector(menuDidBeginTracking),
|
|
59
|
-
name: NSMenu.didBeginTrackingNotification,
|
|
60
|
-
object: nil)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
public required init?(coder: NSCoder) {
|
|
64
|
-
preconditionFailure()
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
convenience init(bridge: RCTBridge) {
|
|
68
|
-
self.init(frame: .zero)
|
|
69
|
-
self.bridge = bridge
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
public override func viewDidMoveToWindow() {
|
|
73
|
-
super.viewDidMoveToWindow()
|
|
74
|
-
if (window != nil) {
|
|
75
|
-
showCallout()
|
|
76
|
-
} else {
|
|
77
|
-
dismissCallout()
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
public override func updateLayer() {
|
|
82
|
-
if let layer = calloutWindow.contentView?.layer {
|
|
83
|
-
layer.borderColor = borderColor.cgColor
|
|
84
|
-
layer.borderWidth = borderWidth
|
|
85
|
-
layer.backgroundColor = backgroundColor.cgColor
|
|
86
|
-
layer.cornerRadius = borderRadius
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// MARK: Fabric interface
|
|
91
|
-
|
|
92
|
-
@objc public var contentProxyView: NSView {
|
|
93
|
-
return proxyView
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
@objc public func setAnchorView(_ view: NSView?) {
|
|
97
|
-
anchorView = view
|
|
98
|
-
updateCalloutFrameToAnchor()
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
@objc public func mountContentSubview(_ subview: NSView, at index: Int) {
|
|
102
|
-
if index >= proxyView.subviews.count {
|
|
103
|
-
proxyView.addSubview(subview)
|
|
104
|
-
} else {
|
|
105
|
-
proxyView.addSubview(subview, positioned: .below, relativeTo: proxyView.subviews[index])
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
@objc public func unmountContentSubview(_ subview: NSView) {
|
|
110
|
-
if subview.superview == proxyView {
|
|
111
|
-
subview.removeFromSuperview()
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
@objc public func updateContentSize(_ size: NSSize) {
|
|
116
|
-
proxyView.frame = NSRect(origin: .zero, size: size)
|
|
117
|
-
updateCalloutFrameToAnchor()
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// MARK: RCTComponent Overrides
|
|
121
|
-
|
|
122
|
-
public override func insertReactSubview(_ subview: NSView!, at atIndex: Int) {
|
|
123
|
-
proxyView.insertReactSubview(subview, at: atIndex)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
public override func didUpdateReactSubviews() {
|
|
127
|
-
proxyView.didUpdateReactSubviews()
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
public override func reactSetFrame(_ frame: CGRect) {
|
|
131
|
-
proxyView.reactSetFrame(frame)
|
|
132
|
-
updateCalloutFrameToAnchor()
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// MARK: WindowLifeCycleDelegate
|
|
136
|
-
|
|
137
|
-
func calloutWillDismiss(window: CalloutWindow) {
|
|
138
|
-
onDismissCallout()
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// MARK: Private methods
|
|
142
|
-
|
|
143
|
-
private func showCallout() {
|
|
144
|
-
guard !isCalloutWindowShown else {
|
|
145
|
-
return
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
updateCalloutFrameToAnchor()
|
|
149
|
-
calloutWindow.orderFront(self)
|
|
150
|
-
if (setInitialFocus) {
|
|
151
|
-
calloutWindow.makeKey()
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Dismiss the Callout if the window is no longer active.
|
|
155
|
-
NotificationCenter.default.addObserver(self, selector: #selector(dismissCallout), name: NSApplication.didResignActiveNotification, object: nil)
|
|
156
|
-
|
|
157
|
-
mouseEventMonitor.addLocalMonitorForEvents(matching: .leftMouseDown, handler: { [weak self] (event) -> NSEvent? in
|
|
158
|
-
func isClickInsideWindowHierarchy(window: NSWindow?, event: NSEvent) -> Bool {
|
|
159
|
-
guard let window = window else {
|
|
160
|
-
return false
|
|
161
|
-
}
|
|
162
|
-
guard let clickedWindow = event.window else {
|
|
163
|
-
return false
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
var isClickInHierarchy = false
|
|
167
|
-
|
|
168
|
-
if (window.isEqual(to: clickedWindow)) {
|
|
169
|
-
isClickInHierarchy = true
|
|
170
|
-
} else {
|
|
171
|
-
if let childWindows = window.childWindows {
|
|
172
|
-
for childWindow in childWindows {
|
|
173
|
-
isClickInHierarchy = isClickInsideWindowHierarchy(window: childWindow, event: event)
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return isClickInHierarchy
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
var window: NSWindow? = self?.calloutWindow
|
|
182
|
-
while (window?.parent as? CalloutWindow != nil) {
|
|
183
|
-
window = window?.parent
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (!isClickInsideWindowHierarchy(window: window, event: event)) {
|
|
187
|
-
self?.dismissCallout()
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return event
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
isCalloutWindowShown = true
|
|
194
|
-
onShowCallout()
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
@objc private func dismissCallout() {
|
|
198
|
-
guard isCalloutWindowShown else {
|
|
199
|
-
return
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Dismiss any children Callouts (I.E: Submenus) first
|
|
203
|
-
if let childWindows = calloutWindow.childWindows {
|
|
204
|
-
for childWindow in childWindows {
|
|
205
|
-
if let childCallout = childWindow as? CalloutWindow {
|
|
206
|
-
childCallout.dismissCallout()
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
calloutWindow.dismissCallout()
|
|
212
|
-
|
|
213
|
-
NotificationCenter.default.removeObserver(self)
|
|
214
|
-
mouseEventMonitor.removeMonitor()
|
|
215
|
-
|
|
216
|
-
isCalloutWindowShown = false
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/// Sets the frame of the Callout Window (in screen coordinates to be off of the Anchor on the preferred edge
|
|
220
|
-
private func updateCalloutFrameToAnchor() {
|
|
221
|
-
guard window != nil else {
|
|
222
|
-
return
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Prefer anchorView over anchorRect if available
|
|
226
|
-
let anchorScreenRect = anchorView != nil ? calculateAnchorViewScreenRect() : calculateAnchorRectScreenRect()
|
|
227
|
-
let calloutScreenRect = bestCalloutRect(relativeTo: anchorScreenRect)
|
|
228
|
-
|
|
229
|
-
// Because we immediately update the rect as props come in, there's a possibility that we have neither
|
|
230
|
-
// of anchorRect and target. Don't update until we have at least one.
|
|
231
|
-
guard !calloutScreenRect.isEmpty else {
|
|
232
|
-
return
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
proxyView.frame.origin = .zero
|
|
236
|
-
calloutWindow.setFrame(calloutScreenRect, display: false)
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/// Calculates the NSRect of the Anchor Rect in screen coordinates
|
|
240
|
-
private func calculateAnchorRectScreenRect() -> NSRect {
|
|
241
|
-
guard let window = window else {
|
|
242
|
-
preconditionFailure("No window found")
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
if (window.screen == nil) {
|
|
246
|
-
preconditionFailure("No screen Available")
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// The anchor Rect is given in the coordinate space of the root view.
|
|
250
|
-
// Find the root view to convert to screen coordinates
|
|
251
|
-
var rootView: NSView = self
|
|
252
|
-
while (!rootView.isReactRootView()) {
|
|
253
|
-
rootView = rootView.reactSuperview()
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
let anchorRect = self.anchorRect
|
|
257
|
-
|
|
258
|
-
// Since the root view is flipped, we already have anchorRect in the "correct"
|
|
259
|
-
// (i.e. flipped) coordinate space. Since we need to provide screen rects to Apple,
|
|
260
|
-
// we will once again arrive at the "correct" (not flipped) coordinate space.
|
|
261
|
-
let anchorRectInWindow = rootView.convert(anchorRect, to: nil)
|
|
262
|
-
let anchorRectInScreenCoordinates = window.convertToScreen(anchorRectInWindow)
|
|
263
|
-
|
|
264
|
-
return anchorRectInScreenCoordinates
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/// Calculates the NSRect of the anchorView in the coordinate space of the current screen
|
|
268
|
-
private func calculateAnchorViewScreenRect() -> NSRect {
|
|
269
|
-
guard let anchorView = anchorView else {
|
|
270
|
-
preconditionFailure("No anchor view provided to position the Callout")
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
guard let window = window else {
|
|
274
|
-
preconditionFailure("No window found")
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
let anchorBoundsInWindow = anchorView.convert(anchorView.bounds, to: nil)
|
|
278
|
-
let anchorFrameInScreenCoordinates = window.convertToScreen(anchorBoundsInWindow)
|
|
279
|
-
|
|
280
|
-
return anchorFrameInScreenCoordinates
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/// Calculates the rect in screen coordinates the callout should be positioned in relative to the anchor rect, adjusting if we are close to a screen edge
|
|
284
|
-
private func bestCalloutRect(relativeTo anchorScreenRect: NSRect) -> NSRect {
|
|
285
|
-
|
|
286
|
-
guard let screenFrame = window?.screen?.visibleFrame ?? NSScreen.main?.visibleFrame else {
|
|
287
|
-
preconditionFailure("No Screen Available")
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
let calloutFrame = proxyView.frame
|
|
291
|
-
|
|
292
|
-
// Find our preferred origin based on the directional hint
|
|
293
|
-
let calloutOrigin: NSPoint = {
|
|
294
|
-
var origin = NSPoint()
|
|
295
|
-
|
|
296
|
-
switch(directionalHint) {
|
|
297
|
-
case .minX:
|
|
298
|
-
origin.x = NSMinX(anchorScreenRect) - calloutFrame.size.width
|
|
299
|
-
origin.y = NSMaxY(anchorScreenRect) - calloutFrame.size.height
|
|
300
|
-
case .minY:
|
|
301
|
-
origin.x = NSMinX(anchorScreenRect)
|
|
302
|
-
origin.y = NSMaxY(anchorScreenRect)
|
|
303
|
-
case .maxX:
|
|
304
|
-
origin.x = NSMaxX(anchorScreenRect)
|
|
305
|
-
origin.y = NSMaxY(anchorScreenRect) - calloutFrame.size.height
|
|
306
|
-
case .maxY:
|
|
307
|
-
// When in RTL mode, align the right edges of the menu and flyout anchor
|
|
308
|
-
if (NSApp.userInterfaceLayoutDirection == .rightToLeft || RCTI18nUtil.sharedInstance().isRTL()) {
|
|
309
|
-
origin.x = NSMaxX(anchorScreenRect) - calloutFrame.size.width
|
|
310
|
-
} else {
|
|
311
|
-
origin.x = NSMinX(anchorScreenRect);
|
|
312
|
-
}
|
|
313
|
-
origin.y = NSMinY(anchorScreenRect) - calloutFrame.size.height
|
|
314
|
-
@unknown default:
|
|
315
|
-
preconditionFailure("Unknown directional hint")
|
|
316
|
-
}
|
|
317
|
-
return origin
|
|
318
|
-
}()
|
|
319
|
-
|
|
320
|
-
var calloutScreenRect = NSRect(origin: calloutOrigin, size: calloutFrame.size)
|
|
321
|
-
|
|
322
|
-
// Reposition the callout if it doesn't fit on screen
|
|
323
|
-
if (!NSContainsRect(screenFrame, calloutScreenRect)) {
|
|
324
|
-
switch(directionalHint) {
|
|
325
|
-
case .minX:
|
|
326
|
-
// If we go off the left edge, we may need to flip to presenting rightward from the rightEdge of anchorScreenRect
|
|
327
|
-
if (NSMinX(calloutScreenRect) < NSMinX(screenFrame)) {
|
|
328
|
-
let maxXEdgeSpace = NSMaxX(screenFrame) - NSMaxX(anchorScreenRect)
|
|
329
|
-
let minXEdgeSpace = NSMinX(anchorScreenRect) - NSMinX(screenFrame)
|
|
330
|
-
if (maxXEdgeSpace > minXEdgeSpace) {
|
|
331
|
-
calloutScreenRect.origin.x = NSMaxX(anchorScreenRect);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
// If we go off the bottom of the screen, just slide up until we fit
|
|
335
|
-
if (NSMinY(calloutScreenRect) < NSMinY(screenFrame)) {
|
|
336
|
-
calloutScreenRect.origin.y = NSMinY(screenFrame)
|
|
337
|
-
}
|
|
338
|
-
case .minY:
|
|
339
|
-
// If we go off the top of the screen, check if there's more room on screen below the anchorScreenRect.
|
|
340
|
-
if (NSMaxY(calloutScreenRect) > NSMaxY(screenFrame)) {
|
|
341
|
-
let maxYEdgeSpace = NSMaxY(screenFrame) - NSMaxY(anchorScreenRect)
|
|
342
|
-
let minYEdgeSpace = NSMinY(anchorScreenRect) - NSMinY(screenFrame)
|
|
343
|
-
|
|
344
|
-
// Flip to presenting below anchorScreenRect
|
|
345
|
-
if (minYEdgeSpace > maxYEdgeSpace) {
|
|
346
|
-
calloutScreenRect.origin.y = NSMinY(anchorScreenRect) - NSHeight(calloutScreenRect)
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
// If we go off the right edge, just slide to the left until we fit
|
|
350
|
-
if (NSMaxX(calloutScreenRect) > NSMaxX(screenFrame)) {
|
|
351
|
-
calloutScreenRect.origin.x = NSMaxY(screenFrame) - NSWidth(calloutScreenRect)
|
|
352
|
-
}
|
|
353
|
-
case .maxX:
|
|
354
|
-
// If we go off the right edge, we may need to flip to presenting leftward from the leftEdge of anchorScreenRect
|
|
355
|
-
if (NSMaxX(calloutScreenRect) > NSMaxX(screenFrame)) {
|
|
356
|
-
let maxXEdgeSpace = NSMaxX(screenFrame) - NSMaxX(anchorScreenRect)
|
|
357
|
-
let minXEdgeSpace = NSMinX(anchorScreenRect) - NSMinX(screenFrame)
|
|
358
|
-
if (minXEdgeSpace > maxXEdgeSpace) {
|
|
359
|
-
calloutScreenRect.origin.x = NSMinX(anchorScreenRect) - NSWidth(calloutScreenRect)
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
// If we go off the bottom of the screen, just slide up until we fit
|
|
363
|
-
if (NSMinY(calloutScreenRect) < NSMinY(screenFrame)) {
|
|
364
|
-
calloutScreenRect.origin.y = NSMinY(screenFrame);
|
|
365
|
-
}
|
|
366
|
-
case .maxY:
|
|
367
|
-
// If we go off the bottom of the screen, check if there's more room on screen above the anchorScreenRect.
|
|
368
|
-
if (NSMinY(calloutScreenRect) < NSMinY(screenFrame)) {
|
|
369
|
-
let maxYEdgeSpace = NSMaxY(screenFrame) - NSMaxY(anchorScreenRect)
|
|
370
|
-
let minYEdgeSpace = NSMinY(anchorScreenRect) - NSMinY(screenFrame)
|
|
371
|
-
|
|
372
|
-
// Flip to presenting above anchorScreenRect
|
|
373
|
-
if (maxYEdgeSpace > minYEdgeSpace) {
|
|
374
|
-
calloutScreenRect.origin.y = NSMaxY(anchorScreenRect)
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
// If we go off the right edge, just slide to the left until we fit
|
|
378
|
-
if (NSMaxX(calloutScreenRect) > NSMaxX(screenFrame)) {
|
|
379
|
-
calloutScreenRect.origin.x = NSMaxX(screenFrame) - NSWidth(calloutScreenRect)
|
|
380
|
-
}
|
|
381
|
-
// If we go off the left edge in RTL, just slide to the right so we're fully onscreen
|
|
382
|
-
if (NSMinX(calloutScreenRect) < NSMinX(screenFrame)) {
|
|
383
|
-
calloutScreenRect.origin.x = 0;
|
|
384
|
-
}
|
|
385
|
-
@unknown default:
|
|
386
|
-
preconditionFailure("Unknown directional hint")
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
return calloutScreenRect
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
private func onShowCallout() {
|
|
393
|
-
onShow?([:])
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
private func onDismissCallout() {
|
|
397
|
-
if let onDismiss = onDismiss {
|
|
398
|
-
let event: [AnyHashable: Any] = ["target": reactTag ?? NSNumber(value: 0)]
|
|
399
|
-
onDismiss(event)
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// The app's main menu bar is active while callout is shown, dismiss.
|
|
404
|
-
@objc private func menuDidBeginTracking() {
|
|
405
|
-
self.dismissCallout()
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// MARK: Private variables
|
|
409
|
-
|
|
410
|
-
/// The view the Callout is presented from.
|
|
411
|
-
private var anchorView: NSView?
|
|
412
|
-
|
|
413
|
-
/// The view we forward Callout's Children to. It's hosted within the CalloutWindow's
|
|
414
|
-
/// view hierarchy, ensuring our React Views are not placed in the main window.
|
|
415
|
-
private lazy var proxyView: NSView = {
|
|
416
|
-
let visualEffectView = FlippedVisualEffectView()
|
|
417
|
-
visualEffectView.translatesAutoresizingMaskIntoConstraints = false
|
|
418
|
-
visualEffectView.material = .menu
|
|
419
|
-
visualEffectView.state = .active
|
|
420
|
-
visualEffectView.wantsLayer = true
|
|
421
|
-
|
|
422
|
-
/**
|
|
423
|
-
* We can't directly call touchHandler.attach(to:) because `visualEffectView` is not an RCTUIView.
|
|
424
|
-
* We get around this limitation by just replicating what `attach` did internally: add a gestureRecognizer.
|
|
425
|
-
*/
|
|
426
|
-
if let bridge = bridge {
|
|
427
|
-
guard let touchHandler = RCTTouchHandler(bridge: bridge) else {
|
|
428
|
-
preconditionFailure("Callout could not create RCTTouchHandler")
|
|
429
|
-
}
|
|
430
|
-
visualEffectView.addGestureRecognizer(touchHandler)
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
return visualEffectView
|
|
434
|
-
}()
|
|
435
|
-
|
|
436
|
-
private lazy var calloutWindow: CalloutWindow = {
|
|
437
|
-
let window = CalloutWindow()
|
|
438
|
-
window.lifeCycleDelegate = self
|
|
439
|
-
|
|
440
|
-
guard let contentView = window.contentView else {
|
|
441
|
-
preconditionFailure("Callout window has no content view")
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
contentView.addSubview(proxyView)
|
|
445
|
-
if let parentWindow = self.window {
|
|
446
|
-
parentWindow.addChildWindow(window, ordered: .above)
|
|
447
|
-
}
|
|
448
|
-
return window
|
|
449
|
-
}()
|
|
450
|
-
|
|
451
|
-
private var mouseEventMonitor = GuardedEventMonitor()
|
|
452
|
-
|
|
453
|
-
private var isCalloutWindowShown = false
|
|
454
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import Foundation
|
|
2
|
-
import AppKit
|
|
3
|
-
|
|
4
|
-
protocol CalloutWindowLifeCycleDelegate: AnyObject {
|
|
5
|
-
/// Notify the delegate that the Callout is about to dismiss
|
|
6
|
-
func calloutWillDismiss(window: CalloutWindow)
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
class CalloutWindow: NSWindow {
|
|
10
|
-
weak var lifeCycleDelegate: CalloutWindowLifeCycleDelegate?
|
|
11
|
-
|
|
12
|
-
override init(contentRect: NSRect, styleMask style: NSWindow.StyleMask, backing backingStoreType: NSWindow.BackingStoreType, defer flag: Bool) {
|
|
13
|
-
super.init(contentRect: contentRect, styleMask: style, backing: backingStoreType, defer: flag)
|
|
14
|
-
|
|
15
|
-
styleMask = .borderless
|
|
16
|
-
level = .popUpMenu
|
|
17
|
-
backgroundColor = .clear
|
|
18
|
-
isMovable = false
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Required to get a key view loop in the window
|
|
22
|
-
override var canBecomeKey: Bool {
|
|
23
|
-
return true
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
override var canBecomeMain: Bool {
|
|
27
|
-
return false
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Required to close the window on escape key press
|
|
31
|
-
override func cancelOperation(_ sender: Any?) {
|
|
32
|
-
dismissCallout()
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
@objc public func dismissCallout() {
|
|
36
|
-
lifeCycleDelegate?.calloutWillDismiss(window: self)
|
|
37
|
-
orderOut(self)
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
package/macos/FRNCallout.h
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// FRNCallout.h
|
|
3
|
-
// Pods
|
|
4
|
-
//
|
|
5
|
-
// Created by Patrick Boyd on 9/24/24.
|
|
6
|
-
//
|
|
7
|
-
// Created as a dummy file to enable importing FRNCallout-Swift.h and invoking Swift functions from Obj-C
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
#ifndef FRNCallout_h
|
|
11
|
-
#define FRNCallout_h
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
#endif /* FRNCallout_h */
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
#import "FRNCalloutManager.h"
|
|
2
|
-
|
|
3
|
-
#import "FRNCallout-Swift.h"
|
|
4
|
-
|
|
5
|
-
@implementation RCTConvert (FRNCalloutAdditions)
|
|
6
|
-
|
|
7
|
-
// RCTConvert does not properly convert a JS screenRect into a native CGRect/NSRect,
|
|
8
|
-
// due to the mismatch of x/y and screenX/screenY. Let's do it manually.
|
|
9
|
-
// Note this does not take into account that macOS uses a flipped Y axis.
|
|
10
|
-
+ (NSRect)screenRect:(id)json
|
|
11
|
-
{
|
|
12
|
-
CGFloat x = [RCTConvert CGFloat:json[@"screenX"]];
|
|
13
|
-
CGFloat y = [RCTConvert CGFloat:json[@"screenY"]];
|
|
14
|
-
CGFloat width = [RCTConvert CGFloat:json[@"width"]];
|
|
15
|
-
CGFloat height = [RCTConvert CGFloat:json[@"height"]];
|
|
16
|
-
return NSMakeRect(x, y, width, height);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Collapse the directional hint options to the 4 NSRectEdge options.
|
|
20
|
-
// CalloutView uses MinY for placement above the anchor and MaxY for placement below it.
|
|
21
|
-
RCT_ENUM_CONVERTER(NSRectEdge, (@{
|
|
22
|
-
@"leftTopEdge": @(NSRectEdgeMinX),
|
|
23
|
-
@"leftCenter": @(NSRectEdgeMinX),
|
|
24
|
-
@"leftBottomEdge": @(NSRectEdgeMinX),
|
|
25
|
-
@"topLeftEdge": @(NSRectEdgeMinY),
|
|
26
|
-
@"topAutoEdge": @(NSRectEdgeMinY),
|
|
27
|
-
@"topCenter": @(NSRectEdgeMinY),
|
|
28
|
-
@"topRightEdge": @(NSRectEdgeMinY),
|
|
29
|
-
@"rightTopEdge": @(NSRectEdgeMaxX),
|
|
30
|
-
@"rightCenter": @(NSRectEdgeMaxX),
|
|
31
|
-
@"rightBottomEdge": @(NSRectEdgeMaxX),
|
|
32
|
-
@"bottomLeftEdge": @(NSRectEdgeMaxY),
|
|
33
|
-
@"bottomAutoEdge": @(NSRectEdgeMaxY),
|
|
34
|
-
@"bottomCenter": @(NSRectEdgeMaxY),
|
|
35
|
-
@"bottomRightEdge": @(NSRectEdgeMaxY),
|
|
36
|
-
}), NSRectEdgeMaxY, integerValue);
|
|
37
|
-
|
|
38
|
-
@end
|
|
39
|
-
|
|
40
|
-
@interface RCT_EXTERN_MODULE(RCTCalloutManager, RCTViewManager)
|
|
41
|
-
|
|
42
|
-
RCT_EXPORT_METHOD(focusWindow : (nonnull NSNumber *)viewTag)
|
|
43
|
-
{
|
|
44
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
45
|
-
NSView *view = [self.bridge.uiManager viewForReactTag:viewTag];
|
|
46
|
-
|
|
47
|
-
if ([view isKindOfClass:[FRNCalloutView class]]) {
|
|
48
|
-
[(FRNCalloutView *)view focusWindow];
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
RCT_EXPORT_METHOD(blurWindow : (nonnull NSNumber *)viewTag)
|
|
54
|
-
{
|
|
55
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
56
|
-
NSView *view = [self.bridge.uiManager viewForReactTag:viewTag];
|
|
57
|
-
|
|
58
|
-
if ([view isKindOfClass:[FRNCalloutView class]]) {
|
|
59
|
-
[(FRNCalloutView *)view blurWindow];
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
RCT_EXPORT_VIEW_PROPERTY(target, NSNumber)
|
|
65
|
-
|
|
66
|
-
RCT_EXPORT_VIEW_PROPERTY(anchorRect, screenRect)
|
|
67
|
-
|
|
68
|
-
RCT_EXPORT_VIEW_PROPERTY(directionalHint, NSRectEdge)
|
|
69
|
-
|
|
70
|
-
RCT_EXPORT_VIEW_PROPERTY(setInitialFocus, BOOL)
|
|
71
|
-
|
|
72
|
-
RCT_EXPORT_VIEW_PROPERTY(onShow, RCTDirectEventBlock)
|
|
73
|
-
|
|
74
|
-
RCT_EXPORT_VIEW_PROPERTY(onDismiss, RCTDirectEventBlock)
|
|
75
|
-
|
|
76
|
-
@end
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import AppKit
|
|
2
|
-
|
|
3
|
-
/// React Native macOS uses a flipped coordinate space by default. Let's stay consistent and
|
|
4
|
-
/// ensure any views hosting React Native views are also flipped. This helps RCTTouchHandler
|
|
5
|
-
/// register clicks in the right location.
|
|
6
|
-
internal class FlippedVisualEffectView: NSVisualEffectView {
|
|
7
|
-
override var isFlipped: Bool {
|
|
8
|
-
return true
|
|
9
|
-
}
|
|
10
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import AppKit
|
|
2
|
-
|
|
3
|
-
/// NSEvent localMonitors are an _old_ API that requires you to do some memory management yourself.
|
|
4
|
-
/// You _must_ call `removeMonitor`exactly once per monitor created from. This helper class enforces
|
|
5
|
-
/// we set the reference to nil after `removeMonitor` so that subsequent calls don't cause an over release.
|
|
6
|
-
/// One instance should only be used to allocate and manage one NSEvent monitor.
|
|
7
|
-
/// https://developer.apple.com/documentation/appkit/nsevent/1533709-removemonitor
|
|
8
|
-
internal class GuardedEventMonitor: Any {
|
|
9
|
-
|
|
10
|
-
private var allocatedMonitor: Any?
|
|
11
|
-
|
|
12
|
-
func addLocalMonitorForEvents(matching mask: NSEvent.EventTypeMask, handler block: @escaping (NSEvent) -> NSEvent?) {
|
|
13
|
-
if (allocatedMonitor == nil) {
|
|
14
|
-
allocatedMonitor = NSEvent.addLocalMonitorForEvents(matching: mask, handler: block)
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
func removeMonitor() {
|
|
19
|
-
if let monitor = allocatedMonitor {
|
|
20
|
-
NSEvent.removeMonitor(monitor)
|
|
21
|
-
allocatedMonitor = nil
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
deinit {
|
|
26
|
-
removeMonitor()
|
|
27
|
-
}
|
|
28
|
-
}
|