react-native-unified-action-sheet 0.2.0 → 0.4.0
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 +87 -1
- package/README.md +44 -112
- package/android/build.gradle +28 -1
- package/android/src/main/java/com/unifiedactionsheet/ActionSheetOptions.kt +41 -2
- package/android/src/main/java/com/unifiedactionsheet/PromptOptions.kt +27 -5
- package/android/src/main/java/com/unifiedactionsheet/SheetContentBuilder.kt +75 -9
- package/android/src/main/java/com/unifiedactionsheet/SheetPresenters.kt +61 -10
- package/android/src/main/java/com/unifiedactionsheet/UnifiedActionSheetModule.kt +14 -7
- package/android/src/material/java/com/unifiedactionsheet/BottomSheetPresenterFactory.kt +10 -0
- package/android/src/material/java/com/unifiedactionsheet/MaterialBottomSheetPresenter.kt +153 -0
- package/android/src/material/res/values/unified_action_sheet_bottom_sheet.xml +22 -0
- package/android/src/noMaterial/java/com/unifiedactionsheet/BottomSheetPresenterFactory.kt +10 -0
- package/app.plugin.js +1 -0
- package/ios/BottomSheetViewController.swift +210 -0
- package/ios/UnifiedActionSheet.mm +51 -5
- package/ios/UnifiedActionSheetImpl.swift +259 -22
- package/jest/index.d.ts +12 -9
- package/jest/index.js +37 -18
- package/lib/commonjs/NativeUnifiedActionSheet.js.map +1 -1
- package/lib/commonjs/index.js +102 -18
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/NativeUnifiedActionSheet.js.map +1 -1
- package/lib/module/index.js +102 -18
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/commonjs/src/NativeUnifiedActionSheet.d.ts +10 -1
- package/lib/typescript/commonjs/src/NativeUnifiedActionSheet.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/action-sheet-options.interface.d.ts +5 -2
- package/lib/typescript/commonjs/src/action-sheet-options.interface.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/common-options.interface.d.ts +9 -0
- package/lib/typescript/commonjs/src/common-options.interface.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/index.d.ts +6 -6
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/prompt-options.interface.d.ts +10 -6
- package/lib/typescript/commonjs/src/prompt-options.interface.d.ts.map +1 -1
- package/lib/typescript/module/src/NativeUnifiedActionSheet.d.ts +10 -1
- package/lib/typescript/module/src/NativeUnifiedActionSheet.d.ts.map +1 -1
- package/lib/typescript/module/src/action-sheet-options.interface.d.ts +5 -2
- package/lib/typescript/module/src/action-sheet-options.interface.d.ts.map +1 -1
- package/lib/typescript/module/src/common-options.interface.d.ts +9 -0
- package/lib/typescript/module/src/common-options.interface.d.ts.map +1 -1
- package/lib/typescript/module/src/index.d.ts +6 -6
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/prompt-options.interface.d.ts +10 -6
- package/lib/typescript/module/src/prompt-options.interface.d.ts.map +1 -1
- package/package.json +12 -2
- package/plugin/withUnifiedActionSheet.js +58 -0
- package/src/NativeUnifiedActionSheet.ts +12 -2
- package/src/action-sheet-options.interface.ts +12 -1
- package/src/common-options.interface.ts +18 -0
- package/src/index.tsx +147 -42
- package/src/prompt-options.interface.ts +21 -5
package/src/index.tsx
CHANGED
|
@@ -1,12 +1,26 @@
|
|
|
1
1
|
import { Platform } from 'react-native';
|
|
2
2
|
|
|
3
3
|
import type { Spec } from './NativeUnifiedActionSheet';
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
ActionSheetOptionsInterface,
|
|
6
|
+
ActionSheetResultInterface,
|
|
7
|
+
} from './action-sheet-options.interface';
|
|
8
|
+
import type {
|
|
9
|
+
BaseButtonInterface,
|
|
10
|
+
CloseResultInterface,
|
|
11
|
+
} from './common-options.interface';
|
|
12
|
+
import type {
|
|
13
|
+
PromptButtonInterface,
|
|
14
|
+
PromptOptionsInterface,
|
|
15
|
+
PromptResultInterface,
|
|
16
|
+
} from './prompt-options.interface';
|
|
5
17
|
|
|
6
18
|
export type {
|
|
7
19
|
BaseButtonInterface,
|
|
8
20
|
BaseOptionsInterface,
|
|
9
21
|
BaseAndroidOptionsInterface,
|
|
22
|
+
CloseReason,
|
|
23
|
+
CloseResultInterface,
|
|
10
24
|
} from './common-options.interface';
|
|
11
25
|
|
|
12
26
|
export type {
|
|
@@ -15,11 +29,7 @@ export type {
|
|
|
15
29
|
PromptAndroidOptionsInterface,
|
|
16
30
|
PromptOptionsInterface,
|
|
17
31
|
PromptResultInterface,
|
|
18
|
-
|
|
19
|
-
import type { BaseButtonInterface } from './common-options.interface';
|
|
20
|
-
import type {
|
|
21
|
-
PromptOptionsInterface,
|
|
22
|
-
PromptResultInterface,
|
|
32
|
+
PromptValuesInterface,
|
|
23
33
|
} from './prompt-options.interface';
|
|
24
34
|
|
|
25
35
|
export type {
|
|
@@ -27,21 +37,28 @@ export type {
|
|
|
27
37
|
ActionSheetButtonInterface,
|
|
28
38
|
ActionSheetCommonOptionsInterface,
|
|
29
39
|
ActionSheetAndroidOptionsInterface,
|
|
40
|
+
ActionSheetDetent,
|
|
30
41
|
ActionSheetOptionsInterface,
|
|
42
|
+
ActionSheetResultInterface,
|
|
31
43
|
} from './action-sheet-options.interface';
|
|
32
44
|
|
|
33
45
|
const DISMISSED_BY_API = -2;
|
|
34
46
|
|
|
35
47
|
/// The native wire format is index-based; the public API is not. Nothing below
|
|
36
48
|
/// index.tsx knows buttons are described as objects.
|
|
37
|
-
type
|
|
49
|
+
type WireButtons = {
|
|
38
50
|
options: string[];
|
|
39
51
|
cancelButtonIndex?: number;
|
|
40
52
|
destructiveButtonIndices?: number[];
|
|
41
53
|
disabledButtonIndices?: number[];
|
|
42
|
-
|
|
54
|
+
preferredButtonIndex?: number;
|
|
43
55
|
};
|
|
44
56
|
|
|
57
|
+
type WireOptions = Omit<ActionSheetOptionsInterface, 'options' | 'anchor'> &
|
|
58
|
+
WireButtons & {
|
|
59
|
+
anchorRect?: { x: number; y: number; width: number; height: number };
|
|
60
|
+
};
|
|
61
|
+
|
|
45
62
|
const nativeModule = (): Spec =>
|
|
46
63
|
require('./NativeUnifiedActionSheet').default as Spec;
|
|
47
64
|
|
|
@@ -58,20 +75,25 @@ const toWireOptions = ({
|
|
|
58
75
|
...toWireButtons(options),
|
|
59
76
|
});
|
|
60
77
|
|
|
61
|
-
type WirePromptOptions = Omit<
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
78
|
+
type WirePromptOptions = Omit<
|
|
79
|
+
PromptOptionsInterface,
|
|
80
|
+
'options' | 'secureTextEntry'
|
|
81
|
+
> &
|
|
82
|
+
WireButtons & {
|
|
83
|
+
type: NonNullable<PromptOptionsInterface['type']>;
|
|
84
|
+
textRequiredButtonIndices?: number[];
|
|
85
|
+
};
|
|
67
86
|
|
|
68
87
|
/// Same flattening as the sheet: labels plus index sets. Kept generic over the
|
|
69
88
|
/// button shape so the two APIs cannot disagree about what 'cancel' means.
|
|
70
|
-
const toWireButtons = (
|
|
89
|
+
const toWireButtons = (
|
|
90
|
+
buttons: ReadonlyArray<BaseButtonInterface>
|
|
91
|
+
): WireButtons => {
|
|
71
92
|
const labels: string[] = [];
|
|
72
93
|
const destructive: number[] = [];
|
|
73
94
|
const disabled: number[] = [];
|
|
74
95
|
let cancelButtonIndex: number | undefined;
|
|
96
|
+
let preferredButtonIndex: number | undefined;
|
|
75
97
|
|
|
76
98
|
buttons.forEach((button, index) => {
|
|
77
99
|
labels.push(button.label);
|
|
@@ -81,6 +103,9 @@ const toWireButtons = (buttons: ReadonlyArray<BaseButtonInterface>) => {
|
|
|
81
103
|
cancelButtonIndex = index;
|
|
82
104
|
}
|
|
83
105
|
if (button.disabled) disabled.push(index);
|
|
106
|
+
if (button.preferred && preferredButtonIndex == null) {
|
|
107
|
+
preferredButtonIndex = index;
|
|
108
|
+
}
|
|
84
109
|
});
|
|
85
110
|
|
|
86
111
|
return {
|
|
@@ -90,19 +115,68 @@ const toWireButtons = (buttons: ReadonlyArray<BaseButtonInterface>) => {
|
|
|
90
115
|
? {}
|
|
91
116
|
: { destructiveButtonIndices: destructive }),
|
|
92
117
|
...(disabled.length === 0 ? {} : { disabledButtonIndices: disabled }),
|
|
118
|
+
...(preferredButtonIndex == null ? {} : { preferredButtonIndex }),
|
|
93
119
|
};
|
|
94
120
|
};
|
|
95
121
|
|
|
122
|
+
/// Native sides only report an index: DISMISSED_BY_API, the cancel button's
|
|
123
|
+
/// index, -1 (a cancel gesture with no cancel button), or another button.
|
|
124
|
+
const toCloseResult = (
|
|
125
|
+
buttonIndex: number,
|
|
126
|
+
cancelButtonIndex: number | undefined
|
|
127
|
+
): CloseResultInterface => {
|
|
128
|
+
if (buttonIndex === DISMISSED_BY_API) {
|
|
129
|
+
return { reason: 'dismissed', buttonIndex: undefined };
|
|
130
|
+
}
|
|
131
|
+
if (buttonIndex < 0 || buttonIndex === cancelButtonIndex) {
|
|
132
|
+
return { reason: 'cancelled', buttonIndex };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return { reason: 'selected', buttonIndex };
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const DISMISSED: CloseResultInterface = {
|
|
139
|
+
reason: 'dismissed',
|
|
140
|
+
buttonIndex: undefined,
|
|
141
|
+
};
|
|
142
|
+
|
|
96
143
|
const showWithNativeModule = (
|
|
97
144
|
options: WireOptions
|
|
98
|
-
): Promise<
|
|
145
|
+
): Promise<ActionSheetResultInterface> =>
|
|
99
146
|
nativeModule()
|
|
100
147
|
.showActionSheetWithOptions(options)
|
|
101
|
-
// A
|
|
148
|
+
// A native failure reads as a cancellation rather than a rejection.
|
|
149
|
+
.catch(() => options.cancelButtonIndex ?? -1)
|
|
102
150
|
.then((buttonIndex) =>
|
|
103
|
-
buttonIndex
|
|
104
|
-
)
|
|
105
|
-
|
|
151
|
+
toCloseResult(buttonIndex, options.cancelButtonIndex)
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
let warnedMaterialDisabled = false;
|
|
155
|
+
|
|
156
|
+
/// 'bottom' needs Material on Android, which the app opts into at build time.
|
|
157
|
+
/// Without it the sheet falls back to a centered dialog; say so once, in
|
|
158
|
+
/// development, since the fallback alone looks like the option was ignored.
|
|
159
|
+
const warnIfBottomUnavailable = (
|
|
160
|
+
presentationStyle: ActionSheetOptionsInterface['presentationStyle']
|
|
161
|
+
) => {
|
|
162
|
+
if (
|
|
163
|
+
!__DEV__ ||
|
|
164
|
+
warnedMaterialDisabled ||
|
|
165
|
+
Platform.OS !== 'android' ||
|
|
166
|
+
presentationStyle !== 'bottom' ||
|
|
167
|
+
nativeModule().getConstants().isMaterialEnabled
|
|
168
|
+
) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
warnedMaterialDisabled = true;
|
|
173
|
+
console.warn(
|
|
174
|
+
"react-native-unified-action-sheet: presentationStyle 'bottom' needs Material on Android, " +
|
|
175
|
+
'so this sheet falls back to a centered dialog. Set unifiedActionSheet.material=true in ' +
|
|
176
|
+
"android/gradle.properties, or add ['react-native-unified-action-sheet', { material: true }] " +
|
|
177
|
+
'to your Expo plugins, then rebuild the app.'
|
|
178
|
+
);
|
|
179
|
+
};
|
|
106
180
|
|
|
107
181
|
/// The anchor is measured in JS and sent across as a rect, so neither native
|
|
108
182
|
/// module has to resolve a view: a ref's own measureInWindow is the supported
|
|
@@ -129,23 +203,30 @@ const withAnchorRect = (
|
|
|
129
203
|
|
|
130
204
|
export const showActionSheetWithOptions = (
|
|
131
205
|
options: ActionSheetOptionsInterface
|
|
132
|
-
): Promise<
|
|
206
|
+
): Promise<ActionSheetResultInterface> => {
|
|
133
207
|
const wire = toWireOptions(options);
|
|
134
208
|
|
|
135
|
-
// -1 and
|
|
136
|
-
const press = (
|
|
137
|
-
if (buttonIndex != null)
|
|
209
|
+
// -1 and a dismissal index nothing, so optional chaining covers both.
|
|
210
|
+
const press = (result: ActionSheetResultInterface) => {
|
|
211
|
+
if (result.buttonIndex != null) {
|
|
212
|
+
options.options[result.buttonIndex]?.onPress?.();
|
|
213
|
+
}
|
|
138
214
|
|
|
139
|
-
return
|
|
215
|
+
return result;
|
|
140
216
|
};
|
|
141
217
|
|
|
142
218
|
if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
|
|
143
|
-
return Promise.resolve(
|
|
219
|
+
return Promise.resolve(DISMISSED);
|
|
144
220
|
}
|
|
145
221
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
222
|
+
warnIfBottomUnavailable(options.presentationStyle);
|
|
223
|
+
|
|
224
|
+
// A bottom sheet has no anchor, so don't measure one: on iPad a rect would
|
|
225
|
+
// turn the sheet into an arrowed popover pointing at it.
|
|
226
|
+
const anchor =
|
|
227
|
+
options.presentationStyle === 'bottom' ? undefined : options.anchor;
|
|
228
|
+
|
|
229
|
+
return withAnchorRect(wire, anchor).then(showWithNativeModule).then(press);
|
|
149
230
|
};
|
|
150
231
|
|
|
151
232
|
export const dismissActionSheet = (): void => {
|
|
@@ -154,8 +235,8 @@ export const dismissActionSheet = (): void => {
|
|
|
154
235
|
}
|
|
155
236
|
};
|
|
156
237
|
|
|
157
|
-
/// Closes every open sheet, not just the top-most one. Each resolves
|
|
158
|
-
///
|
|
238
|
+
/// Closes every open sheet, not just the top-most one. Each resolves as
|
|
239
|
+
/// 'dismissed', exactly as with dismissActionSheet().
|
|
159
240
|
export const dismissAllActionSheets = (): void => {
|
|
160
241
|
if (Platform.OS === 'ios' || Platform.OS === 'android') {
|
|
161
242
|
nativeModule().dismissAllActionSheets();
|
|
@@ -168,25 +249,49 @@ export const dismissAllActionSheets = (): void => {
|
|
|
168
249
|
/// silent no-op on Android, which is the gap this fills.
|
|
169
250
|
export const showPromptWithOptions = (
|
|
170
251
|
options: PromptOptionsInterface
|
|
171
|
-
): Promise<PromptResultInterface
|
|
172
|
-
const { options: buttons, ...rest } = options;
|
|
173
|
-
const
|
|
252
|
+
): Promise<PromptResultInterface> => {
|
|
253
|
+
const { options: buttons, secureTextEntry, type, ...rest } = options;
|
|
254
|
+
const textRequired = buttons.flatMap(
|
|
255
|
+
(button: PromptButtonInterface, index) =>
|
|
256
|
+
button.requiresText ? [index] : []
|
|
257
|
+
);
|
|
258
|
+
const wire: WirePromptOptions = {
|
|
259
|
+
...rest,
|
|
260
|
+
...toWireButtons(buttons),
|
|
261
|
+
// One field of truth for the native sides: the old boolean maps onto type.
|
|
262
|
+
type: type ?? (secureTextEntry ? 'secure-text' : 'plain-text'),
|
|
263
|
+
...(textRequired.length === 0
|
|
264
|
+
? {}
|
|
265
|
+
: { textRequiredButtonIndices: textRequired }),
|
|
266
|
+
};
|
|
267
|
+
const hasPassword = wire.type === 'login-password';
|
|
174
268
|
|
|
175
269
|
if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
|
|
176
|
-
return Promise.resolve(
|
|
270
|
+
return Promise.resolve({ ...DISMISSED, text: '' });
|
|
177
271
|
}
|
|
178
272
|
|
|
179
273
|
return nativeModule()
|
|
180
274
|
.showPromptWithOptions(wire)
|
|
181
|
-
.
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
275
|
+
.catch(() => ({
|
|
276
|
+
buttonIndex: wire.cancelButtonIndex ?? -1,
|
|
277
|
+
text: '',
|
|
278
|
+
password: '',
|
|
279
|
+
}))
|
|
280
|
+
.then(({ buttonIndex, text, password }): PromptResultInterface => ({
|
|
281
|
+
...toCloseResult(buttonIndex, wire.cancelButtonIndex),
|
|
282
|
+
text,
|
|
283
|
+
// Even a dismissal carries the fields, so a draft is recoverable.
|
|
284
|
+
...(hasPassword ? { password } : {}),
|
|
285
|
+
}))
|
|
186
286
|
.then((result) => {
|
|
187
|
-
// The
|
|
287
|
+
// The values go to the handler, so a caller using onPress alone never
|
|
188
288
|
// has to read the resolved value.
|
|
189
|
-
if (result
|
|
289
|
+
if (result.buttonIndex != null) {
|
|
290
|
+
const { text, password } = result;
|
|
291
|
+
buttons[result.buttonIndex]?.onPress?.(
|
|
292
|
+
hasPassword ? { text, password } : { text }
|
|
293
|
+
);
|
|
294
|
+
}
|
|
190
295
|
|
|
191
296
|
return result;
|
|
192
297
|
});
|
|
@@ -2,17 +2,35 @@ import type {
|
|
|
2
2
|
BaseAndroidOptionsInterface,
|
|
3
3
|
BaseButtonInterface,
|
|
4
4
|
BaseOptionsInterface,
|
|
5
|
+
CloseResultInterface,
|
|
5
6
|
} from './common-options.interface';
|
|
6
7
|
|
|
8
|
+
/// What was in the field(s) when the prompt closed. password is present only
|
|
9
|
+
/// for type 'login-password'.
|
|
10
|
+
export interface PromptValuesInterface {
|
|
11
|
+
text: string;
|
|
12
|
+
password?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
7
15
|
export interface PromptButtonInterface extends BaseButtonInterface {
|
|
8
|
-
onPress?: (
|
|
16
|
+
onPress?: (values: PromptValuesInterface) => void;
|
|
17
|
+
/// Keeps this button disabled while the field is empty (for
|
|
18
|
+
/// 'login-password', while either field is).
|
|
19
|
+
requiresText?: boolean;
|
|
9
20
|
}
|
|
10
21
|
|
|
11
22
|
export interface PromptCommonOptionsInterface extends BaseOptionsInterface {
|
|
12
23
|
options: PromptButtonInterface[];
|
|
24
|
+
/// As in React Native's Alert.prompt. 'login-password' shows a second,
|
|
25
|
+
/// secure field below the first. Defaults to 'plain-text'.
|
|
26
|
+
type?: 'plain-text' | 'secure-text' | 'login-password';
|
|
13
27
|
placeholder?: string;
|
|
28
|
+
/// Placeholder of the password field of a 'login-password' prompt.
|
|
29
|
+
passwordPlaceholder?: string;
|
|
14
30
|
defaultValue?: string;
|
|
15
31
|
keyboardType?: 'default' | 'email-address' | 'numeric' | 'phone-pad' | 'url';
|
|
32
|
+
/// Same as type: 'secure-text'. Kept for compatibility; type wins when both
|
|
33
|
+
/// are set.
|
|
16
34
|
secureTextEntry?: boolean;
|
|
17
35
|
}
|
|
18
36
|
|
|
@@ -21,7 +39,5 @@ export type PromptAndroidOptionsInterface = BaseAndroidOptionsInterface;
|
|
|
21
39
|
export interface PromptOptionsInterface
|
|
22
40
|
extends PromptCommonOptionsInterface, PromptAndroidOptionsInterface {}
|
|
23
41
|
|
|
24
|
-
export
|
|
25
|
-
|
|
26
|
-
text: string;
|
|
27
|
-
}
|
|
42
|
+
export type PromptResultInterface = CloseResultInterface &
|
|
43
|
+
PromptValuesInterface;
|