@unif/react-native-design 0.24.0 → 0.25.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/jest-preset.js +9 -1
- package/lib/module/components/ui/Confirm/ConfirmHost.js +7 -12
- package/lib/module/components/ui/Confirm/ConfirmHost.js.map +1 -1
- package/lib/module/components/ui/Confirm/confirm.js +3 -2
- package/lib/module/components/ui/Confirm/confirm.js.map +1 -1
- package/lib/module/components/ui/Confirm/store.js +58 -8
- package/lib/module/components/ui/Confirm/store.js.map +1 -1
- package/lib/module/components/ui/Toast/ToastHost.js +20 -2
- package/lib/module/components/ui/Toast/ToastHost.js.map +1 -1
- package/lib/module/components/ui/Toast/ToastHost.web.js +11 -1
- package/lib/module/components/ui/Toast/ToastHost.web.js.map +1 -1
- package/lib/module/components/ui/Toast/store.js +95 -12
- package/lib/module/components/ui/Toast/store.js.map +1 -1
- package/lib/module/components/ui/Toast/toast.js +3 -2
- package/lib/module/components/ui/Toast/toast.js.map +1 -1
- package/lib/typescript/src/components/ui/Confirm/ConfirmHost.d.ts +7 -5
- package/lib/typescript/src/components/ui/Confirm/ConfirmHost.d.ts.map +1 -1
- package/lib/typescript/src/components/ui/Confirm/confirm.d.ts +4 -3
- package/lib/typescript/src/components/ui/Confirm/confirm.d.ts.map +1 -1
- package/lib/typescript/src/components/ui/Confirm/store.d.ts +13 -4
- package/lib/typescript/src/components/ui/Confirm/store.d.ts.map +1 -1
- package/lib/typescript/src/components/ui/Toast/ToastHost.d.ts +5 -1
- package/lib/typescript/src/components/ui/Toast/ToastHost.d.ts.map +1 -1
- package/lib/typescript/src/components/ui/Toast/ToastHost.web.d.ts +1 -0
- package/lib/typescript/src/components/ui/Toast/ToastHost.web.d.ts.map +1 -1
- package/lib/typescript/src/components/ui/Toast/store.d.ts +25 -1
- package/lib/typescript/src/components/ui/Toast/store.d.ts.map +1 -1
- package/lib/typescript/src/components/ui/Toast/toast.d.ts +4 -3
- package/lib/typescript/src/components/ui/Toast/toast.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/ui/Confirm/ConfirmHost.tsx +8 -14
- package/src/components/ui/Confirm/confirm.ts +3 -2
- package/src/components/ui/Confirm/store.ts +61 -13
- package/src/components/ui/Toast/ToastHost.tsx +20 -2
- package/src/components/ui/Toast/ToastHost.web.tsx +11 -1
- package/src/components/ui/Toast/store.ts +112 -16
- package/src/components/ui/Toast/toast.ts +3 -2
|
@@ -17,6 +17,18 @@ import type { ToastEntry } from './types';
|
|
|
17
17
|
*
|
|
18
18
|
* `leaseId` 单调递增,同一 entry 被重新投递(Host 重挂)也会拿到新的 leaseId ——
|
|
19
19
|
* 仅比较 entry.id 不足以区分「同一条消息的两次投递」。
|
|
20
|
+
*
|
|
21
|
+
* **栈式 owner**(与 Confirm 同构):后挂载的 Host 接管投递,前任入栈挂起并收到 clear,
|
|
22
|
+
* 接管者卸载后自动归还。RN `Modal` 是独立 native window,根 Host 渲染的 toast 会被
|
|
23
|
+
* Modal 物理盖住,只有「Modal 内自挂一个 Host」能显示 —— 旧的 first-wins 把这条唯一
|
|
24
|
+
* 正解判成恒惰性死码。
|
|
25
|
+
*
|
|
26
|
+
* 两个方向对在途 toast 的处理**刻意不同**:
|
|
27
|
+
* - **接管**(有人挂到我上面)→ 在途 toast **丢弃**。它属于「已经被盖住的过去」,
|
|
28
|
+
* 搬到接管者身上重放没有意义。
|
|
29
|
+
* - **归还**(接管者卸载、我恢复)→ 在途 toast **整条交回、立即重投**。
|
|
30
|
+
* 「Modal 内操作成功 → toast → 关窗」是最常见的一条路径,那条 toast 刚发出、用户
|
|
31
|
+
* 一眼都还没看到;丢掉的话比不挂内层 Host 还糟。不追剩余时长,重投 = 完整重播一遍。
|
|
20
32
|
*/
|
|
21
33
|
|
|
22
34
|
export type ToastDelivery = {
|
|
@@ -27,27 +39,49 @@ export type ToastDelivery = {
|
|
|
27
39
|
|
|
28
40
|
export type ToastSubscriber = (delivery: ToastDelivery) => void;
|
|
29
41
|
|
|
42
|
+
/**
|
|
43
|
+
* owner 被接管 / 归还时的撤回通知 —— 对应 Confirm 的 `clear` 事件。
|
|
44
|
+
*
|
|
45
|
+
* 没有它,挂起中的 Host 会把最后一条 toast **冻在屏幕上**:store 已丢弃那次投递,
|
|
46
|
+
* Host 自己的退场 timer 过不了 `isCurrent` CAS,永远走不到清 UI 那一步。
|
|
47
|
+
*/
|
|
48
|
+
export type ToastClearSubscriber = () => void;
|
|
49
|
+
|
|
30
50
|
export type ToastHostLease = {
|
|
31
51
|
readonly ownerToken: symbol;
|
|
52
|
+
/** Host 卸载。幂等:重复调用、或本 owner 已被摘掉,都是无副作用的 no-op。 */
|
|
32
53
|
release(): void;
|
|
33
54
|
};
|
|
34
55
|
|
|
35
56
|
export type ToastStore = {
|
|
36
57
|
publish(entry: ToastEntry): void;
|
|
37
|
-
|
|
58
|
+
/**
|
|
59
|
+
* 挂载 Host。已有 owner 时走接管(不再拒绝);返回 `null` 只剩一种含义 ——
|
|
60
|
+
* 补投 pending 时 subscriber 抛错,这个 owner 当场就被作废了。
|
|
61
|
+
*/
|
|
62
|
+
registerHost(
|
|
63
|
+
subscriber: ToastSubscriber,
|
|
64
|
+
onClear?: ToastClearSubscriber
|
|
65
|
+
): ToastHostLease | null;
|
|
38
66
|
complete(ownerToken: symbol, leaseId: number, entryId: number): boolean;
|
|
39
67
|
isCurrent(delivery: ToastDelivery): boolean;
|
|
40
68
|
pendingEntry(): ToastEntry | null;
|
|
41
69
|
currentDelivery(): ToastDelivery | null;
|
|
42
70
|
};
|
|
43
71
|
|
|
44
|
-
type Owner = {
|
|
72
|
+
type Owner = {
|
|
73
|
+
token: symbol;
|
|
74
|
+
subscriber: ToastSubscriber | null;
|
|
75
|
+
onClear: ToastClearSubscriber | null;
|
|
76
|
+
};
|
|
45
77
|
|
|
46
78
|
export function createToastStore(log: Logger): ToastStore {
|
|
47
79
|
let pending: ToastEntry | null = null;
|
|
48
80
|
let delivered: ToastDelivery | null = null;
|
|
49
81
|
let owner: Owner | null = null;
|
|
50
82
|
let leaseCounter = 0;
|
|
83
|
+
/** 被接管而挂起的前任,栈顶 = 最近一个。当前 owner 卸载时从这里恢复。 */
|
|
84
|
+
const suspended: Owner[] = [];
|
|
51
85
|
|
|
52
86
|
function beginDelivery(entry: ToastEntry, capturedOwner: Owner): void {
|
|
53
87
|
const delivery: ToastDelivery = {
|
|
@@ -85,30 +119,92 @@ export function createToastStore(log: Logger): ToastStore {
|
|
|
85
119
|
beginDelivery(entry, current);
|
|
86
120
|
}
|
|
87
121
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
122
|
+
/** 把前任降为挂起态:丢弃它的在途投递,并通知它收起屏幕上那条 toast。 */
|
|
123
|
+
function suspendOwner(target: Owner): void {
|
|
124
|
+
// 不回退到 pending —— 否则接管者一挂上就把前任那条补投一遍(跨 owner 重放)。
|
|
125
|
+
if (delivered?.ownerToken === target.token) delivered = null;
|
|
126
|
+
const notify = target.onClear;
|
|
127
|
+
if (!notify) return;
|
|
128
|
+
try {
|
|
129
|
+
notify();
|
|
130
|
+
} catch (error) {
|
|
131
|
+
log.error('ToastHost clear 回调抛错', error);
|
|
132
|
+
// 坏 Host 就地作废:留在栈里也只会在恢复时接着抛。
|
|
133
|
+
target.subscriber = null;
|
|
134
|
+
target.onClear = null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** 弹出最近一个仍可用的挂起 owner;抛错被作废的(subscriber 已置空)直接丢弃。 */
|
|
139
|
+
function popSuspended(): Owner | null {
|
|
140
|
+
while (suspended.length > 0) {
|
|
141
|
+
const next = suspended.pop();
|
|
142
|
+
if (next?.subscriber) return next;
|
|
94
143
|
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** 刚上任的 owner 在首投里抛错被作废:继续往下弹,别把栈里剩下的人一起埋掉。 */
|
|
148
|
+
function resumeAfterFailedInstall(): void {
|
|
149
|
+
const fallback = popSuspended();
|
|
150
|
+
if (fallback) owner = fallback;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function registerHost(
|
|
154
|
+
subscriber: ToastSubscriber,
|
|
155
|
+
onClear?: ToastClearSubscriber
|
|
156
|
+
): ToastHostLease | null {
|
|
95
157
|
const token = Symbol('ToastHostOwner');
|
|
96
|
-
const self: Owner = { token, subscriber };
|
|
158
|
+
const self: Owner = { token, subscriber, onClear: onClear ?? null };
|
|
159
|
+
const previous = owner;
|
|
160
|
+
// 先换 owner 再通知前任:clear 回调里同步 publish() 的那条应当投给**接管者**,
|
|
161
|
+
// 投给正在退场的前任会立刻变成一条冻在屏幕上的孤儿 toast。
|
|
162
|
+
//(Confirm 那侧顺序相反 —— 它的 clear 由 settle 发给当时的 owner,必须先排空。)
|
|
97
163
|
owner = self;
|
|
164
|
+
if (previous) {
|
|
165
|
+
suspendOwner(previous);
|
|
166
|
+
// clear 回调抛错作废的前任不入栈,免得恢复出一个死订阅。
|
|
167
|
+
if (previous.subscriber) suspended.push(previous);
|
|
168
|
+
}
|
|
98
169
|
if (pending) beginDelivery(pending, self);
|
|
99
|
-
// 首投期间 subscriber 抛错会把 owner 作废 —— 此时不能发出可用的 lease
|
|
100
|
-
|
|
170
|
+
// 首投期间 subscriber 抛错会把 owner 作废 —— 此时不能发出可用的 lease,
|
|
171
|
+
// 并且要立刻把刚挂起的前任放回 owner,否则栈里的人再也醒不过来。
|
|
172
|
+
if (owner?.token !== token) {
|
|
173
|
+
resumeAfterFailedInstall();
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
101
176
|
return {
|
|
102
177
|
ownerToken: token,
|
|
103
178
|
release(): void {
|
|
179
|
+
const index = suspended.indexOf(self);
|
|
180
|
+
if (index >= 0) {
|
|
181
|
+
// 乱序卸载(父 Modal 先于内层 Host 卸载):挂起者的在途投递在被接管时已丢弃,
|
|
182
|
+
// 这里只把它摘出栈,绝不能碰当前 owner。
|
|
183
|
+
suspended.splice(index, 1);
|
|
184
|
+
self.subscriber = null;
|
|
185
|
+
self.onClear = null;
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
104
188
|
if (owner?.token !== token) return;
|
|
105
189
|
self.subscriber = null;
|
|
190
|
+
self.onClear = null;
|
|
106
191
|
owner = null;
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
192
|
+
const mine = delivered?.ownerToken === token ? delivered : null;
|
|
193
|
+
if (mine) delivered = null;
|
|
194
|
+
// 归还方向**不丢**在途 toast —— 与接管方向相反,这条区分是有实证的:
|
|
195
|
+
// 「Modal 内操作成功 → toast → setVisible(false)」是最常见的一条路径,
|
|
196
|
+
// 内层 Host 跟着 Modal 一起卸载,这条 toast 刚发出、用户一眼都没看到。
|
|
197
|
+
// 接管方向丢弃是因为前任那条属于「已经被盖住的过去」;归还方向是把刚发生的
|
|
198
|
+
// 事情**交回上一层**,不是跨 owner 重放。整条交回、不追剩余时长 —— 这正是
|
|
199
|
+
// 既有的「owner A 卸载后 B 拿到同一 entry、新 leaseId」形态。
|
|
200
|
+
if (mine && !pending) pending = mine.entry;
|
|
201
|
+
const successor = popSuspended();
|
|
202
|
+
if (!successor) return;
|
|
203
|
+
owner = successor;
|
|
204
|
+
// 有人接手就当场重投(新 ownerToken + 新 leaseId,前任的迟到回调仍认不出它);
|
|
205
|
+
// 没人接手时它就停在 pending,等下一个挂上来的 Host 补投 —— 既有语义不变。
|
|
206
|
+
if (pending) beginDelivery(pending, successor);
|
|
207
|
+
if (owner?.token !== successor.token) resumeAfterFailedInstall();
|
|
112
208
|
},
|
|
113
209
|
};
|
|
114
210
|
}
|
|
@@ -56,7 +56,8 @@ function emit(input: ToastInput, kind: ToastKind = 'info') {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
|
-
* 命令式 toast API。在 app
|
|
59
|
+
* 命令式 toast API。在 app 根附近挂一个 `<ToastHost />` 即可;需要在 `Modal` 里显示
|
|
60
|
+
* 时可在 Modal 内再挂一个,它会接管、关闭后自动归还。
|
|
60
61
|
*
|
|
61
62
|
* toast('已切换到日报');
|
|
62
63
|
* toast.success('订单提交成功');
|
|
@@ -69,7 +70,7 @@ export const toast = Object.assign((input: ToastInput) => emit(input, 'info'), {
|
|
|
69
70
|
error: (input: ToastInput) => emit(input, 'error'),
|
|
70
71
|
});
|
|
71
72
|
|
|
72
|
-
/** 内部:ToastHost
|
|
73
|
+
/** 内部:ToastHost 挂载时接管 owner;前任入栈挂起,本 lease 释放时归还。 */
|
|
73
74
|
export const registerToastHost = store.registerHost;
|
|
74
75
|
|
|
75
76
|
/** 内部:三重身份 CAS —— owner token + leaseId + entry id 全对才算完成。 */
|