Skip to content

Commit 5007011

Browse files
alregnerderegtd
authored andcommitted
Basic MacOS support for ReactXP and reactxp-video. (#245)
1 parent 6b1e1ee commit 5007011

File tree

10 files changed

+418
-0
lines changed

10 files changed

+418
-0
lines changed

extensions/video/index.macos.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
'use strict';
2+
3+
module.exports = require('./dist/macos/PluginBase.js');
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
* PluginBase.ts
3+
*
4+
* Copyright (c) Microsoft Corporation. All rights reserved.
5+
* Licensed under the MIT license.
6+
*
7+
* Base export for the MacOS implementation of the plugin.
8+
*/
9+
10+
import Video from '../native-common/Video';
11+
import Types = require('../common/Types');
12+
13+
export { Video as default, Types };

index.macos.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
'use strict';
2+
3+
module.exports = require('./dist/macos/ReactXP.js');

src/macos/Accessibility.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/**
2+
* Accessibility.tsx
3+
*
4+
* Copyright (c) Microsoft Corporation. All rights reserved.
5+
* Licensed under the MIT license.
6+
*
7+
* MacOS variant of Accessibility that performs announcements by calling
8+
* React Native announcement API.
9+
*/
10+
11+
import RN = require('react-native');
12+
13+
import { Accessibility as NativeAccessibility } from '../native-common/Accessibility';
14+
15+
interface AnnouncementFinishedPayload {
16+
announcement: string;
17+
success: boolean;
18+
}
19+
20+
const RetryTimeout = 3000; // 3 seconds
21+
22+
export class Accessibility extends NativeAccessibility {
23+
// Queue of pending announcements.
24+
private _announcementQueue: string[] = [];
25+
private _retryTimestamp: number;
26+
27+
constructor() {
28+
super();
29+
30+
// Some versions of RN don't support this interface.
31+
if (RN.AccessibilityInfo) {
32+
// Subscribe to an event to get notified when an announcement will finish.
33+
RN.AccessibilityInfo.addEventListener('announcementFinished', this._recalcAnnouncement);
34+
}
35+
}
36+
37+
protected _updateScreenReaderStatus(isEnabled: boolean) {
38+
super._updateScreenReaderStatus(isEnabled);
39+
// Empty announcement queue when screen reader is disabled.
40+
if (!isEnabled && this._announcementQueue.length > 0) {
41+
this._announcementQueue = [];
42+
}
43+
}
44+
45+
announceForAccessibility(announcement: string): void {
46+
super.announceForAccessibility(announcement);
47+
48+
// Update the queue only if screen reader is enabled. Else we won't receive a callback of
49+
// announcement did finish and queued items will never be removed.
50+
if (this._isScreenReaderEnabled) {
51+
this._announcementQueue.push(announcement);
52+
// Post announcement if it's the only announcement in queue.
53+
if (this._announcementQueue.length === 1) {
54+
this._postAnnouncement(announcement);
55+
}
56+
}
57+
}
58+
59+
private _postAnnouncement(announcement: string, resetTimestamp = true): void {
60+
if (resetTimestamp) {
61+
this._retryTimestamp = Date.now();
62+
}
63+
64+
// Some versions of RN don't support this interface.
65+
if (RN.AccessibilityInfo && RN.AccessibilityInfo.announceForAccessibility) {
66+
RN.AccessibilityInfo.announceForAccessibility(announcement);
67+
}
68+
}
69+
70+
private _recalcAnnouncement = (payload: AnnouncementFinishedPayload) => {
71+
if (this._announcementQueue.length === 0) {
72+
return;
73+
}
74+
75+
const postedAnnouncement = this._announcementQueue[0];
76+
// Handle retries if it's the announcement we posted. Drop other announcements.
77+
if (payload.announcement === postedAnnouncement) {
78+
const timeElapsed = Date.now() - this._retryTimestamp;
79+
80+
if (!payload.success && timeElapsed < RetryTimeout) {
81+
this._postAnnouncement(payload.announcement, false);
82+
} else {
83+
// Successfully announced or timed out. Schedule next announcement.
84+
this._announcementQueue.shift();
85+
if (this._announcementQueue.length > 0) {
86+
const nextAnnouncement = this._announcementQueue[0];
87+
this._postAnnouncement(nextAnnouncement);
88+
}
89+
}
90+
}
91+
}
92+
}
93+
94+
export default new Accessibility();

src/macos/AccessibilityUtil.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* AccessibilityUtil.ts
3+
*
4+
* Copyright (c) Microsoft Corporation. All rights reserved.
5+
* Licensed under the MIT license.
6+
*
7+
* MacOS-specific accessibility utils.
8+
*/
9+
10+
import React = require('react');
11+
import RN = require('react-native');
12+
13+
import Accessibility from '../native-common/Accessibility';
14+
import { AccessibilityPlatformUtil } from '../common/AccessibilityUtil';
15+
16+
export class AccessibilityUtil extends AccessibilityPlatformUtil {
17+
setAccessibilityFocus(component: React.Component<any, any>): void {
18+
if (Accessibility.isScreenReaderEnabled() && RN.AccessibilityInfo && RN.AccessibilityInfo.setAccessibilityFocus) {
19+
RN.AccessibilityInfo.setAccessibilityFocus(RN.findNodeHandle(component));
20+
}
21+
}
22+
}
23+
24+
export default new AccessibilityUtil();

src/macos/GestureView.tsx

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* GestureView.tsx
3+
*
4+
* Copyright (c) Microsoft Corporation. All rights reserved.
5+
* Licensed under the MIT license.
6+
*
7+
* MacOS-specific implementation of GestureView component.
8+
*/
9+
10+
import { GestureView as BaseGestureView } from '../native-common/GestureView';
11+
import Types = require('../common/Types');
12+
13+
const _preferredPanRatio = 3;
14+
15+
export class GestureView extends BaseGestureView {
16+
17+
constructor(props: Types.GestureViewProps) {
18+
super(props);
19+
}
20+
21+
protected _getPreferredPanRatio(): number {
22+
return _preferredPanRatio;
23+
}
24+
25+
protected _getEventTimestamp(e: Types.TouchEvent): number {
26+
let timestamp = e.timeStamp;
27+
28+
// Work around a bug in some versions of RN where "timestamp" is
29+
// capitalized differently for some events.
30+
if (!timestamp) {
31+
timestamp = (e as any).timestamp;
32+
}
33+
34+
if (!timestamp) {
35+
return 0;
36+
}
37+
38+
return timestamp.valueOf();
39+
}
40+
}
41+
42+
export default GestureView;

src/macos/Input.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* Input.ts
3+
*
4+
* Copyright (c) Microsoft Corporation. All rights reserved.
5+
* Licensed under the MIT license.
6+
*
7+
* MacOS implementation of Input interface.
8+
*/
9+
10+
import RN = require('react-native');
11+
12+
import RX = require('../common/Interfaces');
13+
14+
export class Input extends RX.Input {
15+
constructor() {
16+
super();
17+
}
18+
}
19+
20+
export default new Input();

src/macos/Linking.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* Linking.ts
3+
*
4+
* Copyright (c) Microsoft Corporation. All rights reserved.
5+
* Licensed under the MIT license.
6+
*
7+
* MacOS-specific implementation for deep linking.
8+
*/
9+
10+
import Types = require('../common/Types');
11+
import { Linking as CommonLinking } from '../native-common/Linking';
12+
13+
export class Linking extends CommonLinking {
14+
// Escaped SMS uri - sms:<phoneNumber>&body=<messageString>
15+
protected _createSmsUrl(smsInfo: Types.SmsInfo) {
16+
let smsUrl = 'sms:';
17+
if (smsInfo.phoneNumber) {
18+
smsUrl += encodeURI(smsInfo.phoneNumber);
19+
}
20+
21+
if (smsInfo.body) {
22+
// (keep ios version for now)iOS uses the & delimiter instead of the regular ?.
23+
smsUrl += '&body=' + encodeURIComponent(smsInfo.body);
24+
}
25+
return smsUrl;
26+
}
27+
}
28+
29+
export default new Linking();

src/macos/ReactXP.ts

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/**
2+
* ReactXP.ts
3+
*
4+
* Copyright (c) Microsoft Corporation. All rights reserved.
5+
* Licensed under the MIT license.
6+
*
7+
* Wrapper for all ReactXP functionality. Users of ReactXP should import just this
8+
* file instead of internals.
9+
*/
10+
11+
import React = require('react');
12+
13+
import RXInterface = require('../common/Interfaces');
14+
import RXTypes = require('../common/Types');
15+
import AnimatedImpl = require('../native-common/Animated');
16+
17+
// -- STRANGE THINGS GOING ON HERE --
18+
// See web/ReactXP.tsx for more details.
19+
20+
import { default as AccessibilityImpl, Accessibility as AccessibilityType } from './Accessibility';
21+
import { default as ActivityIndicatorImpl, ActivityIndicator as ActivityIndicatorType } from '../native-common/ActivityIndicator';
22+
import { default as AlertImpl, Alert as AlertType } from '../native-common/Alert';
23+
import { default as AppImpl, App as AppType } from '../native-common/App';
24+
import { default as ButtonImpl, Button as ButtonType } from '../native-common/Button';
25+
import { default as PickerImpl, Picker as PickerType } from '../native-common/Picker';
26+
import { default as ImageImpl, Image as ImageType } from '../native-common/Image';
27+
import { default as InputImpl, Input as InputType } from '../native-common/Input';
28+
import { default as ClipboardImpl, Clipboard as ClipboardType } from '../native-common/Clipboard';
29+
import { default as GestureViewImpl, GestureView as GestureViewType } from './GestureView';
30+
import { default as InternationalImpl, International as InternationalType } from '../native-common/International';
31+
import { default as LinkImpl, Link as LinkType } from '../native-common/Link';
32+
import { default as LinkingImpl, Linking as LinkingType } from './Linking';
33+
import { default as LocationImpl, Location as LocationType } from '../common/Location';
34+
import { default as ModalImpl, Modal as ModalType } from '../native-common/Modal';
35+
import { default as NetworkImpl, Network as NetworkType } from '../native-common/Network';
36+
import { default as PlatformImpl, Platform as PlatformType } from '../native-common/Platform';
37+
import { default as PopupImpl, Popup as PopupType } from '../native-common/Popup';
38+
import { default as ScrollViewImpl, ScrollView as ScrollViewType } from '../native-common/ScrollView';
39+
import { default as StatusBarImpl, StatusBar as StatusBarType } from './StatusBar';
40+
import { default as StorageImpl, Storage as StorageType } from '../native-common/Storage';
41+
import { default as StylesImpl, Styles as StylesType } from '../native-common/Styles';
42+
import { default as TextImpl, Text as TextType } from '../native-common/Text';
43+
import { default as TextInputImpl, TextInput as TextInputType } from '../native-common/TextInput';
44+
import { default as UserInterfaceImpl, UserInterface as UserInterfaceType } from '../native-common/UserInterface';
45+
import { default as UserPresenceImpl, UserPresence as UserPresenceType } from '../native-common/UserPresence';
46+
import { default as ViewImpl, View as ViewType } from '../native-common/View';
47+
import { default as WebViewImpl, WebView as WebViewType } from '../native-common/WebView';
48+
import ViewBase from '../native-common/ViewBase';
49+
50+
// Initialize the MacOS default view style. This is required because on RN for MacOS, the default
51+
// overflow is 'visible', but we want it to be 'hidden' (the default for ReactXP and RN Android).
52+
const _defaultViewStyle = StylesImpl.createViewStyle({
53+
overflow: 'hidden'
54+
});
55+
ViewBase.setDefaultViewStyle(_defaultViewStyle);
56+
57+
// Initialize MacOS implementation of platform accessibility helpers inside the singleton
58+
// instance of native-common AccessibilityUtil. This is to let native-common components access
59+
// platform specific APIs through native-common implementation itself.
60+
import AccessibilityUtil from '../native-common/AccessibilityUtil';
61+
import AccessibilityPlatformUtil from './AccessibilityUtil';
62+
63+
AccessibilityUtil.setAccessibilityPlatformUtil(AccessibilityPlatformUtil);
64+
65+
// -- STRANGE THINGS GOING ON HERE --
66+
// See web/ReactXP.tsx for more details.
67+
68+
module ReactXP {
69+
export type Accessibility = AccessibilityType;
70+
export var Accessibility = AccessibilityImpl;
71+
export import Animated = AnimatedImpl.Animated;
72+
export type ActivityIndicator = ActivityIndicatorType;
73+
export var ActivityIndicator = ActivityIndicatorImpl;
74+
export type Alert = AlertType;
75+
export var Alert = AlertImpl;
76+
export type App = AppType;
77+
export var App = AppImpl;
78+
export type Button = ButtonType;
79+
export var Button = ButtonImpl;
80+
export type Picker = PickerType;
81+
export var Picker = PickerImpl;
82+
export type Clipboard = ClipboardType;
83+
export var Clipboard = ClipboardImpl;
84+
export type GestureView = GestureViewType;
85+
export var GestureView = GestureViewImpl;
86+
export type Image = ImageType;
87+
export var Image = ImageImpl;
88+
export type Input = InputType;
89+
export var Input = InputImpl;
90+
export type International = InternationalType;
91+
export var International = InternationalImpl;
92+
export type Link = LinkType;
93+
export var Link = LinkImpl;
94+
export type Linking = LinkingType;
95+
export var Linking = LinkingImpl;
96+
export type Location = LocationType;
97+
export var Location = LocationImpl;
98+
export type Modal = ModalType;
99+
export var Modal = ModalImpl;
100+
export type Network = NetworkType;
101+
export var Network = NetworkImpl;
102+
export type Platform = PlatformType;
103+
export var Platform = PlatformImpl;
104+
export type Popup = PopupType;
105+
export var Popup = PopupImpl;
106+
export type ScrollView = ScrollViewType;
107+
export var ScrollView = ScrollViewImpl;
108+
export type StatusBar = StatusBarType;
109+
export var StatusBar = StatusBarImpl;
110+
export type Storage = StorageType;
111+
export var Storage = StorageImpl;
112+
export type Styles = StylesType;
113+
export var Styles = StylesImpl;
114+
export type Text = TextType;
115+
export var Text = TextImpl;
116+
export type TextInput = TextInputType;
117+
export var TextInput = TextInputImpl;
118+
export type UserInterface = UserInterfaceType;
119+
export var UserInterface = UserInterfaceImpl;
120+
export type UserPresence = UserPresenceType;
121+
export var UserPresence = UserPresenceImpl;
122+
export type View = ViewType;
123+
export var View = ViewImpl;
124+
export type WebView = WebViewType;
125+
export var WebView = WebViewImpl;
126+
127+
export import CommonProps = RXTypes.CommonProps;
128+
export import CommonStyledProps = RXTypes.CommonStyledProps;
129+
export import Types = RXTypes;
130+
131+
export import Component = React.Component;
132+
export import createElement = React.createElement;
133+
export import Children = React.Children;
134+
export var __spread = (React as any).__spread;
135+
}
136+
137+
export = ReactXP;
138+
139+
// -- STRANGE THINGS GOING ON HERE --
140+
// See web/ReactXP.tsx for more details.
141+
142+
/* tslint:disable:no-unused-variable */
143+
var _rxImplementsRxInterface: RXInterface.ReactXP = ReactXP;
144+
/* tslint:enable:no-unused-variable */
145+
146+
/*
147+
var rx = module.exports;
148+
Object.keys(rx)
149+
.filter(key => rx[key] && rx[key].prototype instanceof React.Component && !rx[key].displayName)
150+
.forEach(key => rx[key].displayName = 'RX.' + key + '');
151+
*/

0 commit comments

Comments
 (0)