react-native-fxdialog 1.0.0-beta10 → 1.0.0-beta11

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/FXDialog.ts ADDED
@@ -0,0 +1,461 @@
1
+ import { FXDialogViewProps } from "./FXDialogView";
2
+ import {
3
+ FXDialogType,
4
+ FXDialogAnimationType,
5
+ FXDialogAction,
6
+ FXDialogTitle,
7
+ FXDialogMessage,
8
+ FXDialogCustomContent,
9
+ FXDialogCloseType,
10
+ FXDialogController,
11
+ FXDialogCloseSystemType,
12
+ FXDialogAnimationImpl,
13
+ FXDialogUpdateConfig,
14
+ FXDialogQueueItem,
15
+ FXDialogContent,
16
+ } from "./types";
17
+ import {
18
+ FXDialogStyleInterceptor,
19
+ FXDialogStyleInterceptorSystem,
20
+ } from "./style/FXDialogStyleInterceptor";
21
+ import DialogManager from "./FXDialogManager";
22
+ import { FXDialogAnimation } from "./animation/FXDialogAnimation";
23
+ import { ViewStyle } from "react-native";
24
+ import { logger } from "react-native-fxview";
25
+ import React from "react";
26
+ /**
27
+ * Dialog 构建器
28
+ */
29
+ class FXDialog {
30
+ // region========== 私有属性 ==========
31
+ protected _fxViewId?: string;
32
+ protected _priority: number = 0;
33
+ protected _didShow?: () => void;
34
+ protected _didClose?: (closeType?: FXDialogCloseType) => void;
35
+ // 队列项 调用 show() 时生成,show 之前是 null
36
+ protected _queueItem: FXDialogQueueItem | null = null;
37
+ protected _enqueue: boolean = false;
38
+ protected _viewDefaultAnimation: FXDialogAnimation = new FXDialogAnimation(
39
+ FXDialogAnimationType.Scale,
40
+ 300,
41
+ 200,
42
+ );
43
+ protected _viewConfiguration: FXDialogViewProps = {
44
+ type: FXDialogType.Alert,
45
+ closeOnClickBackground: true,
46
+ contents: [],
47
+ actions: [],
48
+ suspensions: [],
49
+ };
50
+
51
+ protected _styleInterceptor: FXDialogStyleInterceptor =
52
+ FXDialogStyleInterceptorSystem;
53
+ /**
54
+ * 设置弹窗类型
55
+ */
56
+ static alert(): FXDialog {
57
+ return this.createDialog(FXDialogType.Alert);
58
+ }
59
+
60
+ static actionSheet(): FXDialog {
61
+ return this.createDialog(
62
+ FXDialogType.ActionSheet,
63
+ FXDialogAnimationType.SlideUp,
64
+ );
65
+ }
66
+
67
+ static popup(): FXDialog {
68
+ return this.createDialog(FXDialogType.Popup, FXDialogAnimationType.SlideUp);
69
+ }
70
+
71
+ private static createDialog(
72
+ dialogType: FXDialogType,
73
+ animationType?: FXDialogAnimationType,
74
+ ): FXDialog {
75
+ const dialog = new this();
76
+ dialog._viewConfiguration.type = dialogType;
77
+ dialog._viewDefaultAnimation.animationType =
78
+ animationType || FXDialogAnimationType.Scale;
79
+ return dialog;
80
+ }
81
+
82
+ // region========== 配置方法(链式调用) ==========
83
+ /**
84
+ * 添加标题
85
+ */
86
+ addTitle(title: FXDialogTitle): this {
87
+ this._viewConfiguration.contents?.push(title);
88
+ return this;
89
+ }
90
+
91
+ /**
92
+ * 添加消息内容
93
+ */
94
+ addMessage(message: FXDialogMessage): this {
95
+ this._viewConfiguration.contents?.push(message);
96
+ return this;
97
+ }
98
+
99
+ /**
100
+ * 添加按钮
101
+ */
102
+ addAction(action: FXDialogAction): this {
103
+ this._viewConfiguration.actions?.push(action);
104
+ return this;
105
+ }
106
+
107
+ /**
108
+ * 添加自定义视图
109
+ */
110
+ addCustom(view: FXDialogCustomContent): this {
111
+ this._viewConfiguration.contents?.push(view);
112
+ return this;
113
+ }
114
+
115
+ /**
116
+ * 添加自定义挂起视图,内部布局是绝对定位,默认样式 position: "absolute",哪怕外部样式设置了类似 position: "relative",也会被忽略
117
+ * @param suspension 自定义挂起视图
118
+ */
119
+ addSuspension(suspension: React.ReactNode): this {
120
+ this._viewConfiguration.suspensions?.push(suspension);
121
+ return this;
122
+ }
123
+
124
+ /**
125
+ * 添加背景,有默认样式,默认铺满overlay,position: "absolute",传进来的组件带style,会合并默认样式
126
+ * 注意:这里设置style属性最好不要设置布局相关的样式,比如宽高、margin、padding, top/left/right/bottom 等,这些设置不管用,会被默认样式覆盖
127
+ * @param view 自定义背景组件
128
+ */
129
+ appendBackground(view: React.ReactNode): this {
130
+ this._viewConfiguration.appendBackground = view;
131
+ return this;
132
+ }
133
+
134
+ /**
135
+ * 设置点击背景是否关闭
136
+ */
137
+ clickBackgroundClose(enable: boolean): this {
138
+ this._viewConfiguration.closeOnClickBackground = enable;
139
+ return this;
140
+ }
141
+
142
+ /**
143
+ * 设置背景样式
144
+ */
145
+ backgroundStyle(style: ViewStyle): this {
146
+ this._viewConfiguration.backgroundStyle = {
147
+ ...this._viewConfiguration.backgroundStyle,
148
+ ...style,
149
+ };
150
+ return this;
151
+ }
152
+
153
+ /**
154
+ * 添加容器背景,有默认样式,默认铺满container,position: "absolute",传进来的组件带style,会合并默认样式
155
+ * 注意:这里设置style属性最好不要设置布局相关的样式,比如宽高、margin、padding, top/left/right/bottom 等,这些设置不管用,会被默认样式覆盖
156
+ * @param view 自定义容器背景组件
157
+ */
158
+ appendContainer(view: React.ReactNode): this {
159
+ this._viewConfiguration.appendContainer = view;
160
+ return this;
161
+ }
162
+
163
+ /**
164
+ * 设置容器样式
165
+ */
166
+ containerStyle(style: ViewStyle): this {
167
+ this._viewConfiguration.containerStyle = style;
168
+ return this;
169
+ }
170
+
171
+ /**
172
+ * 设置内容区域容器样式 除按钮外的内容区域样式
173
+ */
174
+ contentsContainerStyle(style: ViewStyle): this {
175
+ this._viewConfiguration.contentsContainerStyle = style;
176
+ return this;
177
+ }
178
+
179
+ /**
180
+ * 设置按钮区样式
181
+ */
182
+ actionsContainerStyle(style: ViewStyle): this {
183
+ this._viewConfiguration.actionsContainerStyle = style;
184
+ return this;
185
+ }
186
+
187
+ /**
188
+ * 设置弹窗显示动画时长
189
+ * @param duration 动画时长 ms
190
+ */
191
+ showAnimationDuration(duration: number): this {
192
+ this._viewDefaultAnimation.showDuration = duration;
193
+ return this;
194
+ }
195
+
196
+ /**
197
+ * 设置弹窗关闭动画时长
198
+ * @param duration 动画时长 ms
199
+ */
200
+ closeAnimationDuration(duration: number): this {
201
+ this._viewDefaultAnimation.closeDuration = duration;
202
+ return this;
203
+ }
204
+
205
+ /**
206
+ * 设置动画:支持传入内置类型
207
+ */
208
+ animationType(animation: FXDialogAnimationType): this {
209
+ this._viewDefaultAnimation.animationType = animation;
210
+ return this;
211
+ }
212
+
213
+ /**
214
+ * 添加自定义动画控制器,当设置自定义动画器后, animationType、showAnimationDuration、 closeAnimationDuration 设置将无效
215
+ * @param animator 自定义动画控制器
216
+ */
217
+ animator(animator: FXDialogAnimationImpl): this {
218
+ this._viewConfiguration.animator = animator;
219
+ return this;
220
+ }
221
+ /**
222
+ * 设置弹窗容器最大高度
223
+ */
224
+ containerScrollMaxHeight(maxHeight: number): this {
225
+ this._viewConfiguration.containerScrollMaxHeight = maxHeight;
226
+ return this;
227
+ }
228
+
229
+ /**
230
+ * 设置弹窗需要滚动时按钮区最小高度
231
+ * @param minHeight 最小高度
232
+ */
233
+ actionsScrollMinHeight(minHeight: number): this {
234
+ this._viewConfiguration.actionsScrollMinHeight = minHeight;
235
+ return this;
236
+ }
237
+
238
+ /**
239
+ * 添加按钮区最大高度
240
+ * @param maxHeight 最大高度
241
+ */
242
+ actionsScrollMaxHeight(maxHeight: number): this {
243
+ this._viewConfiguration.actionsScrollMaxHeight = maxHeight;
244
+ return this;
245
+ }
246
+
247
+ /**
248
+ * 是否入队,入队的话会根据优先级排队显示,一定会展示。不入队的满足条件会立马展示,不满足则跳过不会展示
249
+ * @param enqueue 是否入队
250
+ * @returns
251
+ */
252
+ enqueue(enqueue: boolean): this {
253
+ this._enqueue = enqueue;
254
+ return this;
255
+ }
256
+
257
+ /**
258
+ * 设置优先级,优先级会影响对话框的显示顺序
259
+ * - 对于入队的对话框:优先级高的会先显示
260
+ * - 对于不入队的对话框:优先级高的会替换优先级低的
261
+ * @param priority 优先级数值,越大优先级越高
262
+ * @returns
263
+ */
264
+ priority(priority: number): this {
265
+ this._priority = priority;
266
+ return this;
267
+ }
268
+
269
+ /**
270
+ * 设置显示回调
271
+ */
272
+ didShow(callback: () => void): this {
273
+ this._didShow = callback;
274
+ return this;
275
+ }
276
+
277
+ /**
278
+ * 设置关闭回调
279
+ */
280
+ didClose(callback: (closeType?: FXDialogCloseType) => void): this {
281
+ this._didClose = callback;
282
+ return this;
283
+ }
284
+
285
+ /**
286
+ * 更新弹窗内容
287
+ * @param updates 要更新的内容
288
+ */
289
+ protected update(updates: FXDialogUpdateConfig) {
290
+ try {
291
+ logger.log("Dialog update", this._fxViewId);
292
+ if (this._queueItem) {
293
+ // 通过引用调用DialogView的update方法(只作用于弹窗层)
294
+ const dialogViewRef = this._queueItem.dialogViewRef;
295
+ logger.log("[Dialog] update", this._queueItem.dialogViewRef);
296
+ if (dialogViewRef && dialogViewRef.current) {
297
+ dialogViewRef.current.update(updates);
298
+ } else {
299
+ logger.warn("[Dialog] Cannot update: DialogView ref not available");
300
+ }
301
+ } else {
302
+ logger.warn("[Dialog] Cannot update: dialog not shown yet");
303
+ }
304
+ } catch (error) {
305
+ logger.error("[Dialog] Failed to update dialog", error);
306
+ }
307
+ }
308
+
309
+ /**
310
+ * 更新弹窗内容
311
+ * @param update 要更新的内容,通过id匹配
312
+ */
313
+ protected updateContent(update: FXDialogContent) {
314
+ this.update({
315
+ contents: [update],
316
+ });
317
+ }
318
+
319
+ /**
320
+ * 更新操作按钮
321
+ * @param actions 要更新的操作按钮,通过id匹配
322
+ */
323
+ protected updateAction(update: FXDialogAction) {
324
+ this.update({
325
+ actions: [update],
326
+ });
327
+ }
328
+
329
+ /**
330
+ * 更新背景样式
331
+ * @param style 要更新的背景样式
332
+ */
333
+ protected updateBackgroundStyle(style: ViewStyle) {
334
+ this.update({
335
+ backgroundStyle: style,
336
+ });
337
+ }
338
+
339
+ /**
340
+ * 更新弹窗容器样式
341
+ * @param style 要更新的弹窗容器样式
342
+ */
343
+ protected updateContainerStyle(style: ViewStyle) {
344
+ this.update({
345
+ containerStyle: style,
346
+ });
347
+ }
348
+
349
+ /**
350
+ * 更新弹窗内容容器样式
351
+ * @param style 要更新的弹窗内容容器样式
352
+ */
353
+ protected updateContentsContainerStyle(style: ViewStyle) {
354
+ this.update({
355
+ contentsContainerStyle: style,
356
+ });
357
+ }
358
+
359
+ /**
360
+ * 更新弹窗操作按钮容器样式
361
+ * @param style 要更新的弹窗操作按钮容器样式
362
+ */
363
+ protected updateActionsContainerStyle(style: ViewStyle) {
364
+ this.update({
365
+ actionsContainerStyle: style,
366
+ });
367
+ }
368
+
369
+ // region========== 显示方法 ==========
370
+
371
+ /**
372
+ * 显示弹窗
373
+ */
374
+ show(fxViewId?: string): FXDialogController | null {
375
+ try {
376
+ if (!this._viewConfiguration.animator) {
377
+ this._viewConfiguration.animator = this._viewDefaultAnimation;
378
+ }
379
+ if (this._styleInterceptor) {
380
+ this._viewConfiguration = this._styleInterceptor.intercept(
381
+ this._viewConfiguration,
382
+ );
383
+ }
384
+ logger.warn(
385
+ "Dialog show",
386
+ this._styleInterceptor,
387
+ this._viewConfiguration,
388
+ );
389
+ this._queueItem = DialogManager.getInstance().show({
390
+ fxViewId: fxViewId,
391
+ priority: this._priority,
392
+ enqueue: this._enqueue,
393
+ dialogProps: this._viewConfiguration,
394
+ didShow: this._didShow,
395
+ didClose: this._didClose,
396
+ });
397
+ // 关键修复:检查show操作是否成功
398
+ if (!this._queueItem) {
399
+ logger.error(
400
+ "[Dialog] Failed to show dialog: DialogManager.show() returned null",
401
+ );
402
+ return null;
403
+ }
404
+ this._fxViewId = this._queueItem.fxViewId;
405
+ return {
406
+ close: this.close.bind(this),
407
+ update: this.update.bind(this),
408
+ updateContent: this.updateContent.bind(this),
409
+ updateAction: this.updateAction.bind(this),
410
+ updateBackgroundStyle: this.updateBackgroundStyle.bind(this),
411
+ updateContainerStyle: this.updateContainerStyle.bind(this),
412
+ updateContentContainerStyle:
413
+ this.updateContentsContainerStyle.bind(this),
414
+ updateActionsContainerStyle:
415
+ this.updateActionsContainerStyle.bind(this),
416
+ fxViewId: () => this._fxViewId,
417
+ };
418
+ } catch (error) {
419
+ logger.error("[Dialog] Failed to show dialog", error);
420
+ return null;
421
+ }
422
+ }
423
+ /**
424
+ * 触发关闭,关闭弹窗, 转发给 DialogManager
425
+ */
426
+ protected close(closeType?: FXDialogCloseType) {
427
+ logger.log("Dialog close", this._fxViewId);
428
+ if (this._queueItem) {
429
+ DialogManager.getInstance().close(
430
+ this._queueItem,
431
+ closeType || FXDialogCloseSystemType.Custom,
432
+ );
433
+ }
434
+ }
435
+
436
+ /**
437
+ * 静态函数关闭弹窗,关闭的是最近展示出来的那个。触发关闭弹窗,并不是关闭完成。转发给 DialogManager
438
+ * @param fxViewId 可选,指定要关闭的弹窗的 fxViewId
439
+ */
440
+ static close(fxViewId?: string, closeType?: FXDialogCloseType) {
441
+ try {
442
+ logger.log("Dialog static close", fxViewId);
443
+ DialogManager.getInstance().close(
444
+ fxViewId,
445
+ closeType || FXDialogCloseSystemType.Custom,
446
+ );
447
+ } catch (error) {
448
+ logger.error("[Dialog] Failed to close dialog", error);
449
+ }
450
+ }
451
+ /**
452
+ * 静态函数某个视图上的关闭所有弹窗
453
+ * @param fxViewId 弹窗视图的 fxViewId
454
+ */
455
+ static clearAll(fxViewId: string) {
456
+ logger.log("Dialog clearAll");
457
+ DialogManager.getInstance().clearViewController(fxViewId);
458
+ }
459
+ }
460
+
461
+ export default FXDialog;
@@ -0,0 +1,95 @@
1
+ import { FXManager, logger } from "react-native-fxview";
2
+ import { FXDialogViewController } from "./FXDialogViewController";
3
+ import { FXDialogQueueItem, FXDialogShowEntry } from "./types";
4
+
5
+ /**
6
+ * Dialog 管理器
7
+ */
8
+ class DialogManager {
9
+ private static instance: DialogManager;
10
+ private viewControllerMap: Map<string, FXDialogViewController> = new Map();
11
+ static getInstance(): DialogManager {
12
+ if (!DialogManager.instance) {
13
+ DialogManager.instance = new DialogManager();
14
+ }
15
+ return DialogManager.instance;
16
+ }
17
+
18
+ show(entry: FXDialogShowEntry): FXDialogQueueItem | null {
19
+ try {
20
+ const fxViewId = entry.fxViewId || FXManager.getLatestFXViewId();
21
+ if (!fxViewId) {
22
+ logger.warn("[DialogManager] No available FXView");
23
+ return null;
24
+ }
25
+ const viewController = this.getOrCreateViewController(fxViewId);
26
+ return viewController.show(entry);
27
+ } catch (error) {
28
+ logger.error("[DialogManager] Failed to show dialog", error);
29
+ return null;
30
+ }
31
+ }
32
+
33
+ close(param?: string | FXDialogQueueItem, closeType?: string): void {
34
+ try {
35
+ if (!param || typeof param === "string") {
36
+ const fxViewId = param || FXManager.getLatestFXViewId();
37
+ if (!fxViewId) {
38
+ logger.warn("[DialogManager] No available FXView");
39
+ return;
40
+ }
41
+ const viewController = this.viewControllerMap.get(fxViewId);
42
+ if (!viewController) {
43
+ logger.warn(
44
+ `[DialogManager] No viewController found for fxViewId: ${fxViewId}`,
45
+ );
46
+ return;
47
+ }
48
+ viewController.close(undefined, closeType);
49
+ } else {
50
+ const viewController = this.viewControllerMap.get(param.fxViewId);
51
+ if (!viewController) {
52
+ logger.warn(
53
+ `[DialogManager] No viewController found for fxViewId: ${param.fxViewId}`,
54
+ );
55
+ return;
56
+ }
57
+ viewController.close(param, closeType);
58
+ }
59
+ } catch (error) {
60
+ logger.error("[DialogManager] Failed to close dialog", error);
61
+ }
62
+ }
63
+
64
+ clearViewController(fxViewId: string): void {
65
+ const viewController = this.viewControllerMap.get(fxViewId);
66
+ viewController?.clear();
67
+ this.viewControllerMap.delete(fxViewId);
68
+ }
69
+
70
+ getDebugInfo(fxViewId?: string): object {
71
+ if (fxViewId) {
72
+ const viewController = this.viewControllerMap.get(fxViewId);
73
+ return viewController?.getDebugInfo() || {};
74
+ }
75
+
76
+ const allInfo: Record<string, object> = {};
77
+ this.viewControllerMap.forEach((vc, id) => {
78
+ allInfo[id] = vc.getDebugInfo();
79
+ });
80
+ return allInfo;
81
+ }
82
+
83
+ private getOrCreateViewController(fxViewId: string): FXDialogViewController {
84
+ let viewController = this.viewControllerMap.get(fxViewId);
85
+
86
+ if (!viewController) {
87
+ viewController = new FXDialogViewController(fxViewId);
88
+ this.viewControllerMap.set(fxViewId, viewController);
89
+ }
90
+
91
+ return viewController;
92
+ }
93
+ }
94
+
95
+ export default DialogManager;