@lobehub/ui 5.25.0 → 5.26.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/es/Image/Image.mjs +138 -37
- package/es/Image/Image.mjs.map +1 -1
- package/es/Image/PreviewGroup.d.mts +1 -0
- package/es/Image/PreviewGroup.mjs +33 -10
- package/es/Image/PreviewGroup.mjs.map +1 -1
- package/es/Image/index.d.mts +2 -2
- package/es/Image/index.mjs +1 -1
- package/es/Image/style.mjs +126 -71
- package/es/Image/style.mjs.map +1 -1
- package/es/Image/type.d.mts +14 -17
- package/es/Image/viewer/GalleryNav.mjs +44 -0
- package/es/Image/viewer/GalleryNav.mjs.map +1 -0
- package/es/Image/viewer/ImageViewer.mjs +275 -0
- package/es/Image/viewer/ImageViewer.mjs.map +1 -0
- package/es/Image/viewer/PreviewOutlet.mjs +19 -0
- package/es/Image/viewer/PreviewOutlet.mjs.map +1 -0
- package/es/Image/viewer/Toolbar.mjs +184 -0
- package/es/Image/viewer/Toolbar.mjs.map +1 -0
- package/es/Image/viewer/ViewerChrome.mjs +54 -0
- package/es/Image/viewer/ViewerChrome.mjs.map +1 -0
- package/es/Image/viewer/geometry.mjs +85 -0
- package/es/Image/viewer/geometry.mjs.map +1 -0
- package/es/Image/viewer/registry.d.mts +2 -0
- package/es/Image/viewer/registry.mjs +52 -0
- package/es/Image/viewer/registry.mjs.map +1 -0
- package/es/Image/viewer/useChromeIdle.mjs +72 -0
- package/es/Image/viewer/useChromeIdle.mjs.map +1 -0
- package/es/Image/viewer/useFinalFocus.mjs +10 -0
- package/es/Image/viewer/useFinalFocus.mjs.map +1 -0
- package/es/Image/viewer/useFlipTransition.mjs +321 -0
- package/es/Image/viewer/useFlipTransition.mjs.map +1 -0
- package/es/Image/viewer/useGalleryNav.mjs +89 -0
- package/es/Image/viewer/useGalleryNav.mjs.map +1 -0
- package/es/Image/viewer/useRefitTransition.mjs +47 -0
- package/es/Image/viewer/useRefitTransition.mjs.map +1 -0
- package/es/Image/viewer/useViewerGestures.mjs +148 -0
- package/es/Image/viewer/useViewerGestures.mjs.map +1 -0
- package/es/Image/viewer/useZoomPan.mjs +346 -0
- package/es/Image/viewer/useZoomPan.mjs.map +1 -0
- package/es/base-ui/Toast/hostGuard.mjs +26 -0
- package/es/base-ui/Toast/hostGuard.mjs.map +1 -0
- package/es/base-ui/Toast/imperative.mjs +43 -20
- package/es/base-ui/Toast/imperative.mjs.map +1 -1
- package/es/base-ui/Toast/pendingQueue.mjs +30 -0
- package/es/base-ui/Toast/pendingQueue.mjs.map +1 -0
- package/es/base-ui/index.mjs +1 -1
- package/es/i18n/resources/en/image.d.mts +5 -0
- package/es/i18n/resources/en/image.mjs +6 -1
- package/es/i18n/resources/en/image.mjs.map +1 -1
- package/es/i18n/resources/zhCn/image.d.mts +5 -0
- package/es/i18n/resources/zhCn/image.mjs +6 -1
- package/es/i18n/resources/zhCn/image.mjs.map +1 -1
- package/es/index.mjs +8 -8
- package/package.json +1 -1
- package/es/Image/components/Preview.mjs +0 -23
- package/es/Image/components/Preview.mjs.map +0 -1
- package/es/Image/components/Toolbar.mjs +0 -129
- package/es/Image/components/Toolbar.mjs.map +0 -1
- package/es/Image/components/usePreview.mjs +0 -58
- package/es/Image/components/usePreview.mjs.map +0 -1
- package/es/Image/components/usePreviewGroup.mjs +0 -57
- package/es/Image/components/usePreviewGroup.mjs.map +0 -1
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { anchoredZoom, clampPan, clampScale, computeFit, doubleClickTarget, normalizeRotation, panBounds, rubberBand, wheelZoomFactor } from "./geometry.mjs";
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
+
import { animate, motionValue } from "motion/react";
|
|
4
|
+
//#region src/Image/viewer/useZoomPan.ts
|
|
5
|
+
const WHEEL_CLOSE_THRESHOLD = 100;
|
|
6
|
+
const WHEEL_IDLE_MS = 300;
|
|
7
|
+
const ZOOM_STEP = 1.5;
|
|
8
|
+
const RESET_TRANSITION = {
|
|
9
|
+
damping: 30,
|
|
10
|
+
stiffness: 300,
|
|
11
|
+
type: "spring"
|
|
12
|
+
};
|
|
13
|
+
const CLEAN_EPSILON = .01;
|
|
14
|
+
const useZoomPan = ({ natural, viewport, maxScale, onCloseRequest, isClosing, fillViewport }) => {
|
|
15
|
+
const [{ scale, x, y, rotate, flipX, flipY }] = useState(() => ({
|
|
16
|
+
flipX: motionValue(false),
|
|
17
|
+
flipY: motionValue(false),
|
|
18
|
+
rotate: motionValue(0),
|
|
19
|
+
scale: motionValue(1),
|
|
20
|
+
x: motionValue(0),
|
|
21
|
+
y: motionValue(0)
|
|
22
|
+
}));
|
|
23
|
+
const naturalRef = useRef(natural);
|
|
24
|
+
const viewportRef = useRef(viewport);
|
|
25
|
+
const maxScaleRef = useRef(maxScale ?? 8);
|
|
26
|
+
maxScaleRef.current = maxScale ?? 8;
|
|
27
|
+
const fillViewportRef = useRef(fillViewport ?? false);
|
|
28
|
+
fillViewportRef.current = fillViewport ?? false;
|
|
29
|
+
const onCloseRequestRef = useRef(onCloseRequest);
|
|
30
|
+
onCloseRequestRef.current = onCloseRequest;
|
|
31
|
+
const isClosingRef = useRef(isClosing);
|
|
32
|
+
isClosingRef.current = isClosing;
|
|
33
|
+
const closeAccumRef = useRef(0);
|
|
34
|
+
const disarmedRef = useRef(false);
|
|
35
|
+
const idleTimerRef = useRef(null);
|
|
36
|
+
const resetCloseTracking = useCallback(() => {
|
|
37
|
+
if (idleTimerRef.current !== null) {
|
|
38
|
+
clearTimeout(idleTimerRef.current);
|
|
39
|
+
idleTimerRef.current = null;
|
|
40
|
+
}
|
|
41
|
+
closeAccumRef.current = 0;
|
|
42
|
+
disarmedRef.current = false;
|
|
43
|
+
}, []);
|
|
44
|
+
const closing = useCallback(() => isClosingRef.current?.() ?? false, []);
|
|
45
|
+
const isCleanState = useCallback(() => Math.abs(scale.get() - 1) < CLEAN_EPSILON && Math.abs(rotate.get()) < CLEAN_EPSILON && !flipX.get() && !flipY.get(), [
|
|
46
|
+
flipX,
|
|
47
|
+
flipY,
|
|
48
|
+
rotate,
|
|
49
|
+
scale
|
|
50
|
+
]);
|
|
51
|
+
const computeDerived = useCallback(() => {
|
|
52
|
+
const currentScale = scale.get();
|
|
53
|
+
return {
|
|
54
|
+
canZoomIn: currentScale < maxScaleRef.current,
|
|
55
|
+
canZoomOut: currentScale > 1,
|
|
56
|
+
isClean: isCleanState(),
|
|
57
|
+
isZoomed: currentScale > 1,
|
|
58
|
+
rotation: normalizeRotation(rotate.get())
|
|
59
|
+
};
|
|
60
|
+
}, [
|
|
61
|
+
isCleanState,
|
|
62
|
+
rotate,
|
|
63
|
+
scale
|
|
64
|
+
]);
|
|
65
|
+
const [derived, setDerived] = useState(computeDerived);
|
|
66
|
+
const syncDerived = useCallback(() => {
|
|
67
|
+
setDerived((prev) => {
|
|
68
|
+
const next = computeDerived();
|
|
69
|
+
if (prev.canZoomIn === next.canZoomIn && prev.canZoomOut === next.canZoomOut && prev.isClean === next.isClean && prev.isZoomed === next.isZoomed && prev.rotation === next.rotation) return prev;
|
|
70
|
+
return next;
|
|
71
|
+
});
|
|
72
|
+
}, [computeDerived]);
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
const unsubscribers = [
|
|
75
|
+
scale,
|
|
76
|
+
rotate,
|
|
77
|
+
flipX,
|
|
78
|
+
flipY
|
|
79
|
+
].map((value) => value.on("change", syncDerived));
|
|
80
|
+
syncDerived();
|
|
81
|
+
return () => {
|
|
82
|
+
for (const unsubscribe of unsubscribers) unsubscribe();
|
|
83
|
+
};
|
|
84
|
+
}, [
|
|
85
|
+
flipX,
|
|
86
|
+
flipY,
|
|
87
|
+
rotate,
|
|
88
|
+
scale,
|
|
89
|
+
syncDerived
|
|
90
|
+
]);
|
|
91
|
+
const getFitRect = useCallback(() => computeFit(naturalRef.current, viewportRef.current, normalizeRotation(rotate.get()), fillViewportRef.current), [rotate]);
|
|
92
|
+
const applyTransform = useCallback((next, fitRect) => {
|
|
93
|
+
const clamped = clampPan(next, fitRect, viewportRef.current);
|
|
94
|
+
scale.jump(next.scale);
|
|
95
|
+
x.jump(clamped.x);
|
|
96
|
+
y.jump(clamped.y);
|
|
97
|
+
}, [
|
|
98
|
+
scale,
|
|
99
|
+
x,
|
|
100
|
+
y
|
|
101
|
+
]);
|
|
102
|
+
const animateTransform = useCallback((next, fitRect) => {
|
|
103
|
+
const clamped = clampPan(next, fitRect, viewportRef.current);
|
|
104
|
+
animate(scale, next.scale, RESET_TRANSITION);
|
|
105
|
+
animate(x, clamped.x, RESET_TRANSITION);
|
|
106
|
+
animate(y, clamped.y, RESET_TRANSITION);
|
|
107
|
+
}, [
|
|
108
|
+
scale,
|
|
109
|
+
x,
|
|
110
|
+
y
|
|
111
|
+
]);
|
|
112
|
+
const zoomBy = useCallback((factor) => {
|
|
113
|
+
if (closing()) return;
|
|
114
|
+
const currentScale = scale.get();
|
|
115
|
+
const targetScale = clampScale(currentScale * factor, maxScaleRef.current);
|
|
116
|
+
const fitRect = getFitRect();
|
|
117
|
+
const anchor = {
|
|
118
|
+
x: viewportRef.current.width / 2,
|
|
119
|
+
y: viewportRef.current.height / 2
|
|
120
|
+
};
|
|
121
|
+
const next = anchoredZoom({
|
|
122
|
+
scale: currentScale,
|
|
123
|
+
x: x.get(),
|
|
124
|
+
y: y.get()
|
|
125
|
+
}, targetScale, anchor, fitRect);
|
|
126
|
+
animateTransform(next, fitRect);
|
|
127
|
+
}, [
|
|
128
|
+
animateTransform,
|
|
129
|
+
closing,
|
|
130
|
+
getFitRect,
|
|
131
|
+
scale,
|
|
132
|
+
x,
|
|
133
|
+
y
|
|
134
|
+
]);
|
|
135
|
+
const zoomIn = useCallback(() => zoomBy(ZOOM_STEP), [zoomBy]);
|
|
136
|
+
const zoomOut = useCallback(() => zoomBy(1 / ZOOM_STEP), [zoomBy]);
|
|
137
|
+
const handleDoubleClick = useCallback((point) => {
|
|
138
|
+
if (closing()) return;
|
|
139
|
+
const currentScale = scale.get();
|
|
140
|
+
const fitRect = getFitRect();
|
|
141
|
+
const target = doubleClickTarget(currentScale, naturalRef.current, fitRect, normalizeRotation(rotate.get()));
|
|
142
|
+
const targetScale = clampScale(target, maxScaleRef.current);
|
|
143
|
+
const next = anchoredZoom({
|
|
144
|
+
scale: currentScale,
|
|
145
|
+
x: x.get(),
|
|
146
|
+
y: y.get()
|
|
147
|
+
}, targetScale, point, fitRect);
|
|
148
|
+
animateTransform(next, fitRect);
|
|
149
|
+
}, [
|
|
150
|
+
animateTransform,
|
|
151
|
+
closing,
|
|
152
|
+
getFitRect,
|
|
153
|
+
rotate,
|
|
154
|
+
scale,
|
|
155
|
+
x,
|
|
156
|
+
y
|
|
157
|
+
]);
|
|
158
|
+
const handleWheel = useCallback((event) => {
|
|
159
|
+
event.preventDefault?.();
|
|
160
|
+
if (closing()) return;
|
|
161
|
+
if (idleTimerRef.current !== null) clearTimeout(idleTimerRef.current);
|
|
162
|
+
idleTimerRef.current = setTimeout(() => {
|
|
163
|
+
idleTimerRef.current = null;
|
|
164
|
+
closeAccumRef.current = 0;
|
|
165
|
+
disarmedRef.current = false;
|
|
166
|
+
}, WHEEL_IDLE_MS);
|
|
167
|
+
const currentScale = scale.get();
|
|
168
|
+
if (!!(event.ctrlKey || event.metaKey) || currentScale > 1) {
|
|
169
|
+
disarmedRef.current = true;
|
|
170
|
+
closeAccumRef.current = 0;
|
|
171
|
+
const rawTarget = currentScale * wheelZoomFactor(event.deltaY);
|
|
172
|
+
if (rawTarget <= 1) {
|
|
173
|
+
animate(scale, 1, RESET_TRANSITION);
|
|
174
|
+
animate(x, 0, RESET_TRANSITION);
|
|
175
|
+
animate(y, 0, RESET_TRANSITION);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const targetScale = clampScale(rawTarget, maxScaleRef.current);
|
|
179
|
+
const fitRect = getFitRect();
|
|
180
|
+
const anchor = {
|
|
181
|
+
x: event.clientX,
|
|
182
|
+
y: event.clientY
|
|
183
|
+
};
|
|
184
|
+
const next = anchoredZoom({
|
|
185
|
+
scale: currentScale,
|
|
186
|
+
x: x.get(),
|
|
187
|
+
y: y.get()
|
|
188
|
+
}, targetScale, anchor, fitRect);
|
|
189
|
+
applyTransform(next, fitRect);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (disarmedRef.current || !isCleanState()) return;
|
|
193
|
+
closeAccumRef.current += Math.abs(event.deltaY);
|
|
194
|
+
if (closeAccumRef.current >= WHEEL_CLOSE_THRESHOLD) {
|
|
195
|
+
closeAccumRef.current = 0;
|
|
196
|
+
onCloseRequestRef.current?.();
|
|
197
|
+
}
|
|
198
|
+
}, [
|
|
199
|
+
applyTransform,
|
|
200
|
+
closing,
|
|
201
|
+
getFitRect,
|
|
202
|
+
isCleanState,
|
|
203
|
+
scale,
|
|
204
|
+
x,
|
|
205
|
+
y
|
|
206
|
+
]);
|
|
207
|
+
const rotateBy = useCallback((delta) => {
|
|
208
|
+
if (closing()) return;
|
|
209
|
+
rotate.jump(normalizeRotation(rotate.get() + delta));
|
|
210
|
+
scale.jump(1);
|
|
211
|
+
x.jump(0);
|
|
212
|
+
y.jump(0);
|
|
213
|
+
}, [
|
|
214
|
+
closing,
|
|
215
|
+
rotate,
|
|
216
|
+
scale,
|
|
217
|
+
x,
|
|
218
|
+
y
|
|
219
|
+
]);
|
|
220
|
+
const rotateLeft = useCallback(() => rotateBy(-90), [rotateBy]);
|
|
221
|
+
const rotateRight = useCallback(() => rotateBy(90), [rotateBy]);
|
|
222
|
+
const flipHorizontal = useCallback(() => {
|
|
223
|
+
if (closing()) return;
|
|
224
|
+
flipX.set(!flipX.get());
|
|
225
|
+
}, [closing, flipX]);
|
|
226
|
+
const flipVertical = useCallback(() => {
|
|
227
|
+
if (closing()) return;
|
|
228
|
+
flipY.set(!flipY.get());
|
|
229
|
+
}, [closing, flipY]);
|
|
230
|
+
const reset = useCallback(() => {
|
|
231
|
+
if (closing()) return;
|
|
232
|
+
animate(scale, 1, RESET_TRANSITION);
|
|
233
|
+
animate(x, 0, RESET_TRANSITION);
|
|
234
|
+
animate(y, 0, RESET_TRANSITION);
|
|
235
|
+
animate(rotate, 0, RESET_TRANSITION);
|
|
236
|
+
flipX.set(false);
|
|
237
|
+
flipY.set(false);
|
|
238
|
+
}, [
|
|
239
|
+
closing,
|
|
240
|
+
flipX,
|
|
241
|
+
flipY,
|
|
242
|
+
rotate,
|
|
243
|
+
scale,
|
|
244
|
+
x,
|
|
245
|
+
y
|
|
246
|
+
]);
|
|
247
|
+
const dragBy = useCallback((delta) => {
|
|
248
|
+
if (closing()) return;
|
|
249
|
+
const fitRect = getFitRect();
|
|
250
|
+
const bounds = panBounds({
|
|
251
|
+
scale: scale.get(),
|
|
252
|
+
x: 0,
|
|
253
|
+
y: 0
|
|
254
|
+
}, fitRect, viewportRef.current);
|
|
255
|
+
x.set(rubberBand(x.get() + delta.x, bounds.x.min, bounds.x.max));
|
|
256
|
+
y.set(rubberBand(y.get() + delta.y, bounds.y.min, bounds.y.max));
|
|
257
|
+
}, [
|
|
258
|
+
closing,
|
|
259
|
+
getFitRect,
|
|
260
|
+
scale,
|
|
261
|
+
x,
|
|
262
|
+
y
|
|
263
|
+
]);
|
|
264
|
+
const dragEnd = useCallback(() => {
|
|
265
|
+
if (closing()) return;
|
|
266
|
+
const fitRect = getFitRect();
|
|
267
|
+
const clamped = clampPan({
|
|
268
|
+
scale: scale.get(),
|
|
269
|
+
x: x.get(),
|
|
270
|
+
y: y.get()
|
|
271
|
+
}, fitRect, viewportRef.current);
|
|
272
|
+
animate(x, clamped.x, RESET_TRANSITION);
|
|
273
|
+
animate(y, clamped.y, RESET_TRANSITION);
|
|
274
|
+
}, [
|
|
275
|
+
closing,
|
|
276
|
+
getFitRect,
|
|
277
|
+
scale,
|
|
278
|
+
x,
|
|
279
|
+
y
|
|
280
|
+
]);
|
|
281
|
+
const escIntent = useCallback(() => isCleanState() ? "close" : "reset", [isCleanState]);
|
|
282
|
+
const setViewport = useCallback((next) => {
|
|
283
|
+
viewportRef.current = next;
|
|
284
|
+
const fitRect = computeFit(naturalRef.current, next, normalizeRotation(rotate.get()), fillViewportRef.current);
|
|
285
|
+
const clamped = clampPan({
|
|
286
|
+
scale: scale.get(),
|
|
287
|
+
x: x.get(),
|
|
288
|
+
y: y.get()
|
|
289
|
+
}, fitRect, next);
|
|
290
|
+
x.set(clamped.x);
|
|
291
|
+
y.set(clamped.y);
|
|
292
|
+
}, [
|
|
293
|
+
rotate,
|
|
294
|
+
scale,
|
|
295
|
+
x,
|
|
296
|
+
y
|
|
297
|
+
]);
|
|
298
|
+
const setNatural = useCallback((next) => {
|
|
299
|
+
naturalRef.current = next;
|
|
300
|
+
const fitRect = computeFit(next, viewportRef.current, normalizeRotation(rotate.get()), fillViewportRef.current);
|
|
301
|
+
const clamped = clampPan({
|
|
302
|
+
scale: scale.get(),
|
|
303
|
+
x: x.get(),
|
|
304
|
+
y: y.get()
|
|
305
|
+
}, fitRect, viewportRef.current);
|
|
306
|
+
x.set(clamped.x);
|
|
307
|
+
y.set(clamped.y);
|
|
308
|
+
}, [
|
|
309
|
+
rotate,
|
|
310
|
+
scale,
|
|
311
|
+
x,
|
|
312
|
+
y
|
|
313
|
+
]);
|
|
314
|
+
return {
|
|
315
|
+
canZoomIn: derived.canZoomIn,
|
|
316
|
+
canZoomOut: derived.canZoomOut,
|
|
317
|
+
dragBy,
|
|
318
|
+
dragEnd,
|
|
319
|
+
escIntent,
|
|
320
|
+
flipHorizontal,
|
|
321
|
+
flipVertical,
|
|
322
|
+
flipX,
|
|
323
|
+
flipY,
|
|
324
|
+
handleDoubleClick,
|
|
325
|
+
handleWheel,
|
|
326
|
+
isClean: derived.isClean,
|
|
327
|
+
isZoomed: derived.isZoomed,
|
|
328
|
+
reset,
|
|
329
|
+
resetCloseTracking,
|
|
330
|
+
rotate,
|
|
331
|
+
rotateLeft,
|
|
332
|
+
rotateRight,
|
|
333
|
+
rotation: derived.rotation,
|
|
334
|
+
scale,
|
|
335
|
+
setNatural,
|
|
336
|
+
setViewport,
|
|
337
|
+
x,
|
|
338
|
+
y,
|
|
339
|
+
zoomIn,
|
|
340
|
+
zoomOut
|
|
341
|
+
};
|
|
342
|
+
};
|
|
343
|
+
//#endregion
|
|
344
|
+
export { useZoomPan };
|
|
345
|
+
|
|
346
|
+
//# sourceMappingURL=useZoomPan.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useZoomPan.mjs","names":[],"sources":["../../../src/Image/viewer/useZoomPan.ts"],"sourcesContent":["import type { MotionValue } from 'motion/react';\nimport { animate, motionValue } from 'motion/react';\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nimport type { Point, Rect, Rotation, Size, TransformState } from './geometry';\nimport {\n anchoredZoom,\n clampPan,\n clampScale,\n computeFit,\n DEFAULT_MAX_SCALE,\n doubleClickTarget,\n normalizeRotation,\n panBounds,\n rubberBand,\n wheelZoomFactor,\n} from './geometry';\n\nconst WHEEL_CLOSE_THRESHOLD = 100;\nconst WHEEL_IDLE_MS = 300;\nconst ZOOM_STEP = 1.5;\nconst RESET_TRANSITION = { damping: 30, stiffness: 300, type: 'spring' as const };\nconst CLEAN_EPSILON = 0.01;\n\nexport interface WheelLikeEvent {\n clientX: number;\n clientY: number;\n ctrlKey?: boolean;\n deltaY: number;\n metaKey?: boolean;\n preventDefault?: () => void;\n}\n\nexport interface UseZoomPanOptions {\n fillViewport?: boolean;\n isClosing?: () => boolean;\n maxScale?: number;\n natural: Size;\n onCloseRequest?: () => void;\n viewport: Size;\n}\n\nexport interface UseZoomPanResult {\n canZoomIn: boolean;\n canZoomOut: boolean;\n dragBy: (delta: Point) => void;\n dragEnd: () => void;\n escIntent: () => 'reset' | 'close';\n flipHorizontal: () => void;\n flipVertical: () => void;\n flipX: MotionValue<boolean>;\n flipY: MotionValue<boolean>;\n handleDoubleClick: (point: Point) => void;\n handleWheel: (event: WheelLikeEvent) => void;\n isClean: boolean;\n isZoomed: boolean;\n reset: () => void;\n resetCloseTracking: () => void;\n rotate: MotionValue<number>;\n rotateLeft: () => void;\n rotateRight: () => void;\n rotation: Rotation;\n scale: MotionValue<number>;\n setNatural: (natural: Size) => void;\n setViewport: (viewport: Size) => void;\n x: MotionValue<number>;\n y: MotionValue<number>;\n zoomIn: () => void;\n zoomOut: () => void;\n}\n\ninterface DerivedState {\n canZoomIn: boolean;\n canZoomOut: boolean;\n isClean: boolean;\n isZoomed: boolean;\n rotation: Rotation;\n}\n\nexport const useZoomPan = ({\n natural,\n viewport,\n maxScale,\n onCloseRequest,\n isClosing,\n fillViewport,\n}: UseZoomPanOptions): UseZoomPanResult => {\n const [{ scale, x, y, rotate, flipX, flipY }] = useState(() => ({\n flipX: motionValue(false),\n flipY: motionValue(false),\n rotate: motionValue(0),\n scale: motionValue(1),\n x: motionValue(0),\n y: motionValue(0),\n }));\n\n const naturalRef = useRef(natural);\n const viewportRef = useRef(viewport);\n const maxScaleRef = useRef(maxScale ?? DEFAULT_MAX_SCALE);\n maxScaleRef.current = maxScale ?? DEFAULT_MAX_SCALE;\n const fillViewportRef = useRef(fillViewport ?? false);\n fillViewportRef.current = fillViewport ?? false;\n\n const onCloseRequestRef = useRef(onCloseRequest);\n onCloseRequestRef.current = onCloseRequest;\n\n const isClosingRef = useRef(isClosing);\n isClosingRef.current = isClosing;\n\n const closeAccumRef = useRef(0);\n const disarmedRef = useRef(false);\n const idleTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const resetCloseTracking = useCallback(() => {\n if (idleTimerRef.current !== null) {\n clearTimeout(idleTimerRef.current);\n idleTimerRef.current = null;\n }\n closeAccumRef.current = 0;\n disarmedRef.current = false;\n }, []);\n\n // The close FLIP spring animates these same scale/x/y values. Any of\n // these entry points still firing during the ~300ms close window would\n // either fight that spring visually or, for wheel/reset specifically,\n // race escIntent()/isCleanState() into cancelling the close outright.\n const closing = useCallback(() => isClosingRef.current?.() ?? false, []);\n\n const isCleanState = useCallback(\n () =>\n Math.abs(scale.get() - 1) < CLEAN_EPSILON &&\n Math.abs(rotate.get()) < CLEAN_EPSILON &&\n !flipX.get() &&\n !flipY.get(),\n [flipX, flipY, rotate, scale],\n );\n\n const computeDerived = useCallback((): DerivedState => {\n const currentScale = scale.get();\n return {\n canZoomIn: currentScale < maxScaleRef.current,\n canZoomOut: currentScale > 1,\n isClean: isCleanState(),\n isZoomed: currentScale > 1,\n rotation: normalizeRotation(rotate.get()),\n };\n }, [isCleanState, rotate, scale]);\n\n const [derived, setDerived] = useState<DerivedState>(computeDerived);\n\n const syncDerived = useCallback(() => {\n setDerived((prev) => {\n const next = computeDerived();\n if (\n prev.canZoomIn === next.canZoomIn &&\n prev.canZoomOut === next.canZoomOut &&\n prev.isClean === next.isClean &&\n prev.isZoomed === next.isZoomed &&\n prev.rotation === next.rotation\n ) {\n return prev;\n }\n return next;\n });\n }, [computeDerived]);\n\n useEffect(() => {\n const unsubscribers = [scale, rotate, flipX, flipY].map((value) =>\n value.on('change', syncDerived),\n );\n syncDerived();\n return () => {\n for (const unsubscribe of unsubscribers) unsubscribe();\n };\n }, [flipX, flipY, rotate, scale, syncDerived]);\n\n const getFitRect = useCallback(\n () =>\n computeFit(\n naturalRef.current,\n viewportRef.current,\n normalizeRotation(rotate.get()),\n fillViewportRef.current,\n ),\n [rotate],\n );\n\n const applyTransform = useCallback(\n (next: TransformState, fitRect: Rect) => {\n const clamped = clampPan(next, fitRect, viewportRef.current);\n // .jump(), not .set(): a surviving spring on these same values (e.g. a\n // dragEnd clamp-back or wheel snap-back still in flight) would otherwise\n // keep animating toward its own target and silently overwrite this\n // instant write on its next tick — motion's set() never stops an\n // active animation, only jump() does.\n scale.jump(next.scale);\n x.jump(clamped.x);\n y.jump(clamped.y);\n },\n [scale, x, y],\n );\n\n // Wheel/pinch stays on the instant applyTransform for 1:1 gesture tracking;\n // stepped inputs (toolbar/keyboard zoom, double-click) animate instead.\n const animateTransform = useCallback(\n (next: TransformState, fitRect: Rect) => {\n const clamped = clampPan(next, fitRect, viewportRef.current);\n animate(scale, next.scale, RESET_TRANSITION);\n animate(x, clamped.x, RESET_TRANSITION);\n animate(y, clamped.y, RESET_TRANSITION);\n },\n [scale, x, y],\n );\n\n const zoomBy = useCallback(\n (factor: number) => {\n if (closing()) return;\n const currentScale = scale.get();\n const targetScale = clampScale(currentScale * factor, maxScaleRef.current);\n const fitRect = getFitRect();\n const anchor: Point = { x: viewportRef.current.width / 2, y: viewportRef.current.height / 2 };\n const next = anchoredZoom(\n { scale: currentScale, x: x.get(), y: y.get() },\n targetScale,\n anchor,\n fitRect,\n );\n animateTransform(next, fitRect);\n },\n [animateTransform, closing, getFitRect, scale, x, y],\n );\n\n const zoomIn = useCallback(() => zoomBy(ZOOM_STEP), [zoomBy]);\n const zoomOut = useCallback(() => zoomBy(1 / ZOOM_STEP), [zoomBy]);\n\n const handleDoubleClick = useCallback(\n (point: Point) => {\n if (closing()) return;\n const currentScale = scale.get();\n const fitRect = getFitRect();\n const target = doubleClickTarget(\n currentScale,\n naturalRef.current,\n fitRect,\n normalizeRotation(rotate.get()),\n );\n const targetScale = clampScale(target, maxScaleRef.current);\n const next = anchoredZoom(\n { scale: currentScale, x: x.get(), y: y.get() },\n targetScale,\n point,\n fitRect,\n );\n animateTransform(next, fitRect);\n },\n [animateTransform, closing, getFitRect, rotate, scale, x, y],\n );\n\n const handleWheel = useCallback(\n (event: WheelLikeEvent) => {\n event.preventDefault?.();\n if (closing()) return;\n\n if (idleTimerRef.current !== null) clearTimeout(idleTimerRef.current);\n idleTimerRef.current = setTimeout(() => {\n idleTimerRef.current = null;\n closeAccumRef.current = 0;\n disarmedRef.current = false;\n }, WHEEL_IDLE_MS);\n\n const currentScale = scale.get();\n const isPinch = !!(event.ctrlKey || event.metaKey);\n const zoomed = currentScale > 1;\n\n if (isPinch || zoomed) {\n disarmedRef.current = true;\n closeAccumRef.current = 0;\n\n const rawTarget = currentScale * wheelZoomFactor(event.deltaY);\n\n if (rawTarget <= 1) {\n animate(scale, 1, RESET_TRANSITION);\n animate(x, 0, RESET_TRANSITION);\n animate(y, 0, RESET_TRANSITION);\n return;\n }\n\n const targetScale = clampScale(rawTarget, maxScaleRef.current);\n const fitRect = getFitRect();\n const anchor: Point = { x: event.clientX, y: event.clientY };\n const next = anchoredZoom(\n { scale: currentScale, x: x.get(), y: y.get() },\n targetScale,\n anchor,\n fitRect,\n );\n applyTransform(next, fitRect);\n return;\n }\n\n if (disarmedRef.current || !isCleanState()) return;\n\n closeAccumRef.current += Math.abs(event.deltaY);\n if (closeAccumRef.current >= WHEEL_CLOSE_THRESHOLD) {\n closeAccumRef.current = 0;\n onCloseRequestRef.current?.();\n }\n },\n [applyTransform, closing, getFitRect, isCleanState, scale, x, y],\n );\n\n const rotateBy = useCallback(\n (delta: 90 | -90) => {\n if (closing()) return;\n rotate.jump(normalizeRotation(rotate.get() + delta));\n scale.jump(1);\n x.jump(0);\n y.jump(0);\n },\n [closing, rotate, scale, x, y],\n );\n\n const rotateLeft = useCallback(() => rotateBy(-90), [rotateBy]);\n const rotateRight = useCallback(() => rotateBy(90), [rotateBy]);\n\n const flipHorizontal = useCallback(() => {\n if (closing()) return;\n flipX.set(!flipX.get());\n }, [closing, flipX]);\n\n const flipVertical = useCallback(() => {\n if (closing()) return;\n flipY.set(!flipY.get());\n }, [closing, flipY]);\n\n const reset = useCallback(() => {\n if (closing()) return;\n animate(scale, 1, RESET_TRANSITION);\n animate(x, 0, RESET_TRANSITION);\n animate(y, 0, RESET_TRANSITION);\n animate(rotate, 0, RESET_TRANSITION);\n flipX.set(false);\n flipY.set(false);\n }, [closing, flipX, flipY, rotate, scale, x, y]);\n\n const dragBy = useCallback(\n (delta: Point) => {\n // A pointer can still be down when close starts (e.g. pinch to just\n // over 1x — inside isClean's epsilon, so Esc/wheel reads 'close' —\n // while the same gesture is still panning). Gated for consistency\n // with the other entry points even though .set() here can't cancel\n // the close spring outright, only cause a self-correcting flicker.\n if (closing()) return;\n const fitRect = getFitRect();\n const bounds = panBounds({ scale: scale.get(), x: 0, y: 0 }, fitRect, viewportRef.current);\n x.set(rubberBand(x.get() + delta.x, bounds.x.min, bounds.x.max));\n y.set(rubberBand(y.get() + delta.y, bounds.y.min, bounds.y.max));\n },\n [closing, getFitRect, scale, x, y],\n );\n\n const dragEnd = useCallback(() => {\n if (closing()) return;\n const fitRect = getFitRect();\n const clamped = clampPan(\n { scale: scale.get(), x: x.get(), y: y.get() },\n fitRect,\n viewportRef.current,\n );\n animate(x, clamped.x, RESET_TRANSITION);\n animate(y, clamped.y, RESET_TRANSITION);\n }, [closing, getFitRect, scale, x, y]);\n\n const escIntent = useCallback(\n (): 'reset' | 'close' => (isCleanState() ? 'close' : 'reset'),\n [isCleanState],\n );\n\n const setViewport = useCallback(\n (next: Size) => {\n viewportRef.current = next;\n const fitRect = computeFit(\n naturalRef.current,\n next,\n normalizeRotation(rotate.get()),\n fillViewportRef.current,\n );\n const clamped = clampPan({ scale: scale.get(), x: x.get(), y: y.get() }, fitRect, next);\n x.set(clamped.x);\n y.set(clamped.y);\n },\n [rotate, scale, x, y],\n );\n\n const setNatural = useCallback(\n (next: Size) => {\n naturalRef.current = next;\n const fitRect = computeFit(\n next,\n viewportRef.current,\n normalizeRotation(rotate.get()),\n fillViewportRef.current,\n );\n const clamped = clampPan(\n { scale: scale.get(), x: x.get(), y: y.get() },\n fitRect,\n viewportRef.current,\n );\n x.set(clamped.x);\n y.set(clamped.y);\n },\n [rotate, scale, x, y],\n );\n\n return {\n canZoomIn: derived.canZoomIn,\n canZoomOut: derived.canZoomOut,\n dragBy,\n dragEnd,\n escIntent,\n flipHorizontal,\n flipVertical,\n flipX,\n flipY,\n handleDoubleClick,\n handleWheel,\n isClean: derived.isClean,\n isZoomed: derived.isZoomed,\n reset,\n resetCloseTracking,\n rotate,\n rotateLeft,\n rotateRight,\n rotation: derived.rotation,\n scale,\n setNatural,\n setViewport,\n x,\n y,\n zoomIn,\n zoomOut,\n };\n};\n\nexport type { Point, Rotation, Size } from './geometry';\n"],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/base-ui/Toast/hostGuard.ts
|
|
2
|
+
const hostOrder = [];
|
|
3
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
4
|
+
const emit = () => {
|
|
5
|
+
for (const listener of listeners) listener();
|
|
6
|
+
};
|
|
7
|
+
const registerToastHost = (id) => {
|
|
8
|
+
hostOrder.push(id);
|
|
9
|
+
emit();
|
|
10
|
+
return () => {
|
|
11
|
+
const index = hostOrder.indexOf(id);
|
|
12
|
+
if (index !== -1) hostOrder.splice(index, 1);
|
|
13
|
+
emit();
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
const isActiveToastHost = (id) => hostOrder[0] === id;
|
|
17
|
+
const subscribeToastHost = (listener) => {
|
|
18
|
+
listeners.add(listener);
|
|
19
|
+
return () => {
|
|
20
|
+
listeners.delete(listener);
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
//#endregion
|
|
24
|
+
export { isActiveToastHost, registerToastHost, subscribeToastHost };
|
|
25
|
+
|
|
26
|
+
//# sourceMappingURL=hostGuard.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hostGuard.mjs","names":[],"sources":["../../../src/base-ui/Toast/hostGuard.ts"],"sourcesContent":["const hostOrder: string[] = [];\nconst listeners = new Set<() => void>();\n\nconst emit = (): void => {\n for (const listener of listeners) listener();\n};\n\nexport const registerToastHost = (id: string): (() => void) => {\n hostOrder.push(id);\n emit();\n return () => {\n const index = hostOrder.indexOf(id);\n if (index !== -1) hostOrder.splice(index, 1);\n emit();\n };\n};\n\nexport const isActiveToastHost = (id: string): boolean => hostOrder[0] === id;\n\nexport const subscribeToastHost = (listener: () => void): (() => void) => {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n};\n\nexport const __resetToastHostRegistryForTests = (): void => {\n hostOrder.length = 0;\n};\n"],"mappings":";AAAA,MAAM,YAAsB,CAAC;AAC7B,MAAM,4BAAY,IAAI,IAAgB;AAEtC,MAAM,aAAmB;CACvB,KAAK,MAAM,YAAY,WAAW,SAAS;AAC7C;AAEA,MAAa,qBAAqB,OAA6B;CAC7D,UAAU,KAAK,EAAE;CACjB,KAAK;CACL,aAAa;EACX,MAAM,QAAQ,UAAU,QAAQ,EAAE;EAClC,IAAI,UAAU,IAAI,UAAU,OAAO,OAAO,CAAC;EAC3C,KAAK;CACP;AACF;AAEA,MAAa,qBAAqB,OAAwB,UAAU,OAAO;AAE3E,MAAa,sBAAsB,aAAuC;CACxE,UAAU,IAAI,QAAQ;CACtB,aAAa;EACX,UAAU,OAAO,QAAQ;CAC3B;AACF"}
|
|
@@ -3,9 +3,11 @@ import { useIsClient } from "../../hooks/useIsClient.mjs";
|
|
|
3
3
|
import { useAppElement } from "../../ThemeProvider/AppElementContext.mjs";
|
|
4
4
|
import { acquireLayerZIndex } from "../zIndex/manager.mjs";
|
|
5
5
|
import { ToastContext } from "./context.mjs";
|
|
6
|
+
import { isActiveToastHost, registerToastHost, subscribeToastHost } from "./hostGuard.mjs";
|
|
7
|
+
import { markToastHostNotReady, markToastHostReady, runWhenToastHostReady } from "./pendingQueue.mjs";
|
|
6
8
|
import { viewportVariants } from "./style.mjs";
|
|
7
9
|
import ToastItem from "./Toast.mjs";
|
|
8
|
-
import { memo, useEffect, useState } from "react";
|
|
10
|
+
import { memo, useEffect, useId, useState, useSyncExternalStore } from "react";
|
|
9
11
|
import { jsx } from "react/jsx-runtime";
|
|
10
12
|
import { cx } from "antd-style";
|
|
11
13
|
import { Toast } from "@base-ui/react/toast";
|
|
@@ -41,6 +43,8 @@ const activeToastIds = {
|
|
|
41
43
|
"top-right": /* @__PURE__ */ new Set()
|
|
42
44
|
};
|
|
43
45
|
const getManager = (position) => toastManagers[position];
|
|
46
|
+
let toastIdCounter = 0;
|
|
47
|
+
const generateToastId = () => `toast-${Date.now().toString(36)}-${(toastIdCounter++).toString(36)}`;
|
|
44
48
|
const normalizeOptions = (optionsOrMessage, type) => {
|
|
45
49
|
if (typeof optionsOrMessage === "string") return {
|
|
46
50
|
description: optionsOrMessage,
|
|
@@ -52,43 +56,49 @@ const normalizeOptions = (optionsOrMessage, type) => {
|
|
|
52
56
|
};
|
|
53
57
|
};
|
|
54
58
|
const createToastInstance = (id, position) => ({
|
|
55
|
-
close: () => getManager(position).close(id),
|
|
59
|
+
close: () => runWhenToastHostReady(() => getManager(position).close(id)),
|
|
56
60
|
id,
|
|
57
61
|
update: (options) => {
|
|
58
|
-
getManager(position).update(id, {
|
|
62
|
+
runWhenToastHostReady(() => getManager(position).update(id, {
|
|
59
63
|
data: options,
|
|
60
64
|
description: options.description,
|
|
61
65
|
title: options.title
|
|
62
|
-
});
|
|
66
|
+
}));
|
|
63
67
|
}
|
|
64
68
|
});
|
|
65
69
|
const addToast = (options) => {
|
|
66
70
|
const position = options.placement ?? globalState.position;
|
|
67
71
|
const manager = getManager(position);
|
|
68
72
|
const onRemove = options.onRemove;
|
|
69
|
-
const id =
|
|
70
|
-
data: options,
|
|
71
|
-
description: options.description,
|
|
72
|
-
onClose: options.onClose,
|
|
73
|
-
onRemove: () => {
|
|
74
|
-
activeToastIds[position].delete(id);
|
|
75
|
-
onRemove?.();
|
|
76
|
-
},
|
|
77
|
-
timeout: options.duration ?? globalState.duration,
|
|
78
|
-
title: options.title
|
|
79
|
-
});
|
|
73
|
+
const id = generateToastId();
|
|
80
74
|
activeToastIds[position].add(id);
|
|
75
|
+
runWhenToastHostReady(() => {
|
|
76
|
+
manager.add({
|
|
77
|
+
id,
|
|
78
|
+
data: options,
|
|
79
|
+
description: options.description,
|
|
80
|
+
onClose: options.onClose,
|
|
81
|
+
onRemove: () => {
|
|
82
|
+
activeToastIds[position].delete(id);
|
|
83
|
+
onRemove?.();
|
|
84
|
+
},
|
|
85
|
+
timeout: options.duration ?? globalState.duration,
|
|
86
|
+
title: options.title
|
|
87
|
+
});
|
|
88
|
+
});
|
|
81
89
|
return createToastInstance(id, position);
|
|
82
90
|
};
|
|
83
91
|
const dismissToast = (id) => {
|
|
84
92
|
if (id) for (const [position, manager] of Object.entries(toastManagers)) {
|
|
85
93
|
activeToastIds[position].delete(id);
|
|
86
|
-
manager.close(id);
|
|
94
|
+
runWhenToastHostReady(() => manager.close(id));
|
|
87
95
|
}
|
|
88
96
|
else for (const [position, manager] of Object.entries(toastManagers)) {
|
|
89
97
|
const ids = Array.from(activeToastIds[position]);
|
|
90
|
-
for (const toastId of ids) manager.close(toastId);
|
|
91
98
|
activeToastIds[position].clear();
|
|
99
|
+
runWhenToastHostReady(() => {
|
|
100
|
+
for (const toastId of ids) manager.close(toastId);
|
|
101
|
+
});
|
|
92
102
|
}
|
|
93
103
|
};
|
|
94
104
|
const createSuccessToast = (optionsOrMessage) => {
|
|
@@ -169,7 +179,11 @@ const ToastHost = memo(({ root, className, duration = 5e3, limit = 5, position =
|
|
|
169
179
|
const isClient = useIsClient();
|
|
170
180
|
const appElement = useAppElement();
|
|
171
181
|
const [viewportZIndex, setViewportZIndex] = useState(void 0);
|
|
182
|
+
const hostId = useId();
|
|
183
|
+
useEffect(() => registerToastHost(hostId), [hostId]);
|
|
184
|
+
const isActive = useSyncExternalStore(subscribeToastHost, () => isActiveToastHost(hostId), () => false);
|
|
172
185
|
useEffect(() => {
|
|
186
|
+
if (!isActive) return;
|
|
173
187
|
globalState = {
|
|
174
188
|
duration,
|
|
175
189
|
limit,
|
|
@@ -180,12 +194,21 @@ const ToastHost = memo(({ root, className, duration = 5e3, limit = 5, position =
|
|
|
180
194
|
duration,
|
|
181
195
|
limit,
|
|
182
196
|
position,
|
|
183
|
-
swipeDirection
|
|
197
|
+
swipeDirection,
|
|
198
|
+
isActive
|
|
184
199
|
]);
|
|
185
200
|
useEffect(() => {
|
|
201
|
+
if (!isActive) return;
|
|
186
202
|
setViewportZIndex(acquireLayerZIndex("toast"));
|
|
187
|
-
}, []);
|
|
188
|
-
|
|
203
|
+
}, [isActive]);
|
|
204
|
+
useEffect(() => {
|
|
205
|
+
if (!isActive || !isClient) return void 0;
|
|
206
|
+
markToastHostReady();
|
|
207
|
+
return () => {
|
|
208
|
+
markToastHostNotReady();
|
|
209
|
+
};
|
|
210
|
+
}, [isActive, isClient]);
|
|
211
|
+
if (!isClient || !isActive) return null;
|
|
189
212
|
const container = root ?? appElement ?? document.body;
|
|
190
213
|
return ALL_POSITIONS.map((pos) => /* @__PURE__ */ jsx(ToastContext, {
|
|
191
214
|
value: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"imperative.mjs","names":["BaseToast"],"sources":["../../../src/base-ui/Toast/imperative.tsx"],"sourcesContent":["'use client';\n\nimport { Toast as BaseToast } from '@base-ui/react/toast';\nimport { cx } from 'antd-style';\nimport { memo, useEffect, useState } from 'react';\n\nimport { useIsClient } from '@/hooks/useIsClient';\nimport { useAppElement } from '@/ThemeProvider';\n\nimport { acquireLayerZIndex } from '../zIndex';\nimport { ToastContext } from './context';\nimport { viewportVariants } from './style';\nimport ToastItem from './Toast';\nimport {\n type ToastAPI,\n type ToastInstance,\n type ToastOptions,\n type ToastPosition,\n type ToastPromiseOptions,\n type ToastType,\n} from './type';\n\n// All possible positions\nconst ALL_POSITIONS: ToastPosition[] = [\n 'top',\n 'top-left',\n 'top-right',\n 'bottom',\n 'bottom-left',\n 'bottom-right',\n];\n\n// Global state management\ninterface ToastState {\n duration: number;\n limit: number;\n position: ToastPosition;\n swipeDirection: ('left' | 'right' | 'up' | 'down') | ('left' | 'right' | 'up' | 'down')[];\n}\n\nlet globalState: ToastState = {\n duration: 5000,\n limit: 5,\n position: 'bottom-right',\n swipeDirection: ['down', 'right'],\n};\n\n// Toast managers for each position\nconst toastManagers: Record<ToastPosition, ReturnType<typeof BaseToast.createToastManager>> = {\n 'bottom': BaseToast.createToastManager(),\n 'bottom-left': BaseToast.createToastManager(),\n 'bottom-right': BaseToast.createToastManager(),\n 'top': BaseToast.createToastManager(),\n 'top-left': BaseToast.createToastManager(),\n 'top-right': BaseToast.createToastManager(),\n};\n\nconst activeToastIds: Record<ToastPosition, Set<string>> = {\n 'bottom': new Set(),\n 'bottom-left': new Set(),\n 'bottom-right': new Set(),\n 'top': new Set(),\n 'top-left': new Set(),\n 'top-right': new Set(),\n};\n\nconst getManager = (position: ToastPosition) => toastManagers[position];\n\nconst normalizeOptions = (\n optionsOrMessage: Omit<ToastOptions, 'type'> | string,\n type: ToastType,\n): ToastOptions => {\n if (typeof optionsOrMessage === 'string') {\n return {\n description: optionsOrMessage,\n type,\n };\n }\n return {\n ...optionsOrMessage,\n type,\n };\n};\n\nconst createToastInstance = (id: string, position: ToastPosition): ToastInstance => ({\n close: () => getManager(position).close(id),\n id,\n update: (options) => {\n getManager(position).update(id, {\n data: options,\n description: options.description,\n title: options.title,\n });\n },\n});\n\nconst addToast = (options: ToastOptions): ToastInstance => {\n const position = options.placement ?? globalState.position;\n const manager = getManager(position);\n const onRemove = options.onRemove;\n const id = manager.add({\n data: options,\n description: options.description,\n onClose: options.onClose,\n onRemove: () => {\n activeToastIds[position].delete(id);\n onRemove?.();\n },\n timeout: options.duration ?? globalState.duration,\n title: options.title,\n });\n activeToastIds[position].add(id);\n return createToastInstance(id, position);\n};\n\nconst dismissToast = (id?: string) => {\n if (id) {\n // Try to close from all managers since we don't know which position the toast is in\n for (const [position, manager] of Object.entries(toastManagers)) {\n activeToastIds[position as ToastPosition].delete(id);\n manager.close(id);\n }\n } else {\n // Clear all toasts\n for (const [position, manager] of Object.entries(toastManagers)) {\n const ids = Array.from(activeToastIds[position as ToastPosition]);\n for (const toastId of ids) {\n manager.close(toastId);\n }\n activeToastIds[position as ToastPosition].clear();\n }\n }\n};\n\nconst createSuccessToast = (\n optionsOrMessage: Omit<ToastOptions, 'type'> | string,\n): ToastInstance => {\n return addToast(normalizeOptions(optionsOrMessage, 'success'));\n};\n\nconst createErrorToast = (optionsOrMessage: Omit<ToastOptions, 'type'> | string): ToastInstance => {\n return addToast(normalizeOptions(optionsOrMessage, 'error'));\n};\n\nconst createInfoToast = (optionsOrMessage: Omit<ToastOptions, 'type'> | string): ToastInstance => {\n return addToast(normalizeOptions(optionsOrMessage, 'info'));\n};\n\nconst createWarningToast = (\n optionsOrMessage: Omit<ToastOptions, 'type'> | string,\n): ToastInstance => {\n return addToast(normalizeOptions(optionsOrMessage, 'warning'));\n};\n\nconst createLoadingToast = (\n optionsOrMessage: Omit<ToastOptions, 'type'> | string,\n): ToastInstance => {\n const options = normalizeOptions(optionsOrMessage, 'loading');\n // Loading toasts don't auto-dismiss by default\n return addToast({ duration: 0, ...options });\n};\n\nasync function promiseToast<T>(promise: Promise<T>, options: ToastPromiseOptions<T>): Promise<T> {\n const loadingOptions =\n typeof options.loading === 'string'\n ? { description: options.loading }\n : (options.loading as ToastOptions);\n\n const loadingToast = addToast({\n closable: false,\n duration: 0,\n type: 'loading',\n ...loadingOptions,\n });\n\n try {\n const result = await promise;\n\n loadingToast.close();\n\n const successOptions = (() => {\n if (typeof options.success === 'string') {\n return { description: options.success };\n }\n if (typeof options.success === 'function') {\n return { description: options.success(result) };\n }\n return options.success as ToastOptions;\n })();\n\n addToast({ type: 'success', ...successOptions });\n\n return result;\n } catch (error) {\n loadingToast.close();\n\n const errorOptions = (() => {\n if (typeof options.error === 'string') {\n return { description: options.error };\n }\n if (typeof options.error === 'function') {\n return { description: options.error(error as Error) };\n }\n return options.error as ToastOptions;\n })();\n\n addToast({ type: 'error', ...errorOptions });\n\n throw error;\n }\n}\n\n// Base toast function\nconst baseToast = (options: ToastOptions): ToastInstance => {\n return addToast({ type: 'default', ...options });\n};\n\n// Toast API\nexport const toast: ToastAPI = Object.assign(baseToast, {\n dismiss: dismissToast,\n error: createErrorToast,\n info: createInfoToast,\n loading: createLoadingToast,\n promise: promiseToast,\n success: createSuccessToast,\n warning: createWarningToast,\n});\n\n// Toast List Component\nconst ToastList = memo(() => {\n const { toasts } = BaseToast.useToastManager();\n return toasts.map((t) => <ToastItem key={t.id} toast={t} />);\n});\n\nToastList.displayName = 'ToastList';\n\nexport interface ToastHostProps {\n className?: string;\n /**\n * Default duration for toasts\n * @default 5000\n */\n duration?: number;\n /**\n * Maximum number of toasts\n * @default 5\n */\n limit?: number;\n /**\n * Toast position\n * @default 'bottom-right'\n */\n position?: ToastPosition;\n /**\n * Root element for portal\n */\n root?: HTMLElement | ShadowRoot | null;\n /**\n * Swipe direction to dismiss\n * @default ['down', 'right']\n */\n swipeDirection?: ('left' | 'right' | 'up' | 'down') | ('left' | 'right' | 'up' | 'down')[];\n}\n\nexport const ToastHost = memo(\n ({\n root,\n className,\n duration = 5000,\n limit = 5,\n position = 'bottom-right',\n swipeDirection = ['down', 'right'],\n }: ToastHostProps) => {\n const isClient = useIsClient();\n const appElement = useAppElement();\n const [viewportZIndex, setViewportZIndex] = useState<number | undefined>(undefined);\n\n useEffect(() => {\n globalState = {\n duration,\n limit,\n position,\n swipeDirection,\n };\n }, [duration, limit, position, swipeDirection]);\n\n useEffect(() => {\n setViewportZIndex(acquireLayerZIndex('toast'));\n }, []);\n\n if (!isClient) return null;\n\n const container = root ?? appElement ?? document.body;\n\n return ALL_POSITIONS.map((pos) => (\n <ToastContext key={pos} value={{ position: pos, swipeDirection }}>\n <BaseToast.Provider limit={limit} timeout={duration} toastManager={getManager(pos)}>\n <BaseToast.Portal container={container}>\n <BaseToast.Viewport\n className={cx(viewportVariants({ position: pos }), className)}\n style={{ zIndex: viewportZIndex }}\n >\n <ToastList />\n </BaseToast.Viewport>\n </BaseToast.Portal>\n </BaseToast.Provider>\n </ToastContext>\n ));\n },\n);\n\nToastHost.displayName = 'ToastHost';\n\n// Hook to use toast manager\nexport const useToast = () => toast;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"imperative.mjs","names":["BaseToast"],"sources":["../../../src/base-ui/Toast/imperative.tsx"],"sourcesContent":["'use client';\n\nimport { Toast as BaseToast } from '@base-ui/react/toast';\nimport { cx } from 'antd-style';\nimport { memo, useEffect, useId, useState, useSyncExternalStore } from 'react';\n\nimport { useIsClient } from '@/hooks/useIsClient';\nimport { useAppElement } from '@/ThemeProvider';\n\nimport { acquireLayerZIndex } from '../zIndex';\nimport { ToastContext } from './context';\nimport { isActiveToastHost, registerToastHost, subscribeToastHost } from './hostGuard';\nimport {\n __resetPendingToastQueueForTests,\n markToastHostNotReady,\n markToastHostReady,\n runWhenToastHostReady,\n} from './pendingQueue';\nimport { viewportVariants } from './style';\nimport ToastItem from './Toast';\nimport {\n type ToastAPI,\n type ToastInstance,\n type ToastOptions,\n type ToastPosition,\n type ToastPromiseOptions,\n type ToastType,\n} from './type';\n\n// All possible positions\nconst ALL_POSITIONS: ToastPosition[] = [\n 'top',\n 'top-left',\n 'top-right',\n 'bottom',\n 'bottom-left',\n 'bottom-right',\n];\n\n// Global state management\ninterface ToastState {\n duration: number;\n limit: number;\n position: ToastPosition;\n swipeDirection: ('left' | 'right' | 'up' | 'down') | ('left' | 'right' | 'up' | 'down')[];\n}\n\nlet globalState: ToastState = {\n duration: 5000,\n limit: 5,\n position: 'bottom-right',\n swipeDirection: ['down', 'right'],\n};\n\n// Toast managers for each position\nconst toastManagers: Record<ToastPosition, ReturnType<typeof BaseToast.createToastManager>> = {\n 'bottom': BaseToast.createToastManager(),\n 'bottom-left': BaseToast.createToastManager(),\n 'bottom-right': BaseToast.createToastManager(),\n 'top': BaseToast.createToastManager(),\n 'top-left': BaseToast.createToastManager(),\n 'top-right': BaseToast.createToastManager(),\n};\n\nconst activeToastIds: Record<ToastPosition, Set<string>> = {\n 'bottom': new Set(),\n 'bottom-left': new Set(),\n 'bottom-right': new Set(),\n 'top': new Set(),\n 'top-left': new Set(),\n 'top-right': new Set(),\n};\n\nconst getManager = (position: ToastPosition) => toastManagers[position];\n\nlet toastIdCounter = 0;\nconst generateToastId = (): string =>\n `toast-${Date.now().toString(36)}-${(toastIdCounter++).toString(36)}`;\n\nconst normalizeOptions = (\n optionsOrMessage: Omit<ToastOptions, 'type'> | string,\n type: ToastType,\n): ToastOptions => {\n if (typeof optionsOrMessage === 'string') {\n return {\n description: optionsOrMessage,\n type,\n };\n }\n return {\n ...optionsOrMessage,\n type,\n };\n};\n\nconst createToastInstance = (id: string, position: ToastPosition): ToastInstance => ({\n close: () => runWhenToastHostReady(() => getManager(position).close(id)),\n id,\n update: (options) => {\n runWhenToastHostReady(() =>\n getManager(position).update(id, {\n data: options,\n description: options.description,\n title: options.title,\n }),\n );\n },\n});\n\n// createToastManager() is a stateless emitter (see @base-ui/react/toast/createToastManager):\n// add/close/update just broadcast to whatever is currently subscribed, and the\n// ToastProvider that owns the real toast state only subscribes from a passive\n// useEffect. Calls made while no host is ready (e.g. mid-handoff between the\n// previously-active ToastHost unmounting and its successor's Provider\n// subscribing) would otherwise be silently dropped, so every manager call is\n// routed through runWhenToastHostReady to queue until a host is listening.\nconst addToast = (options: ToastOptions): ToastInstance => {\n const position = options.placement ?? globalState.position;\n const manager = getManager(position);\n const onRemove = options.onRemove;\n const id = generateToastId();\n activeToastIds[position].add(id);\n runWhenToastHostReady(() => {\n manager.add({\n id,\n data: options,\n description: options.description,\n onClose: options.onClose,\n onRemove: () => {\n activeToastIds[position].delete(id);\n onRemove?.();\n },\n timeout: options.duration ?? globalState.duration,\n title: options.title,\n });\n });\n return createToastInstance(id, position);\n};\n\nconst dismissToast = (id?: string) => {\n if (id) {\n // Try to close from all managers since we don't know which position the toast is in\n for (const [position, manager] of Object.entries(toastManagers)) {\n activeToastIds[position as ToastPosition].delete(id);\n runWhenToastHostReady(() => manager.close(id));\n }\n } else {\n // Clear all toasts\n for (const [position, manager] of Object.entries(toastManagers)) {\n const ids = Array.from(activeToastIds[position as ToastPosition]);\n activeToastIds[position as ToastPosition].clear();\n runWhenToastHostReady(() => {\n for (const toastId of ids) {\n manager.close(toastId);\n }\n });\n }\n }\n};\n\nconst createSuccessToast = (\n optionsOrMessage: Omit<ToastOptions, 'type'> | string,\n): ToastInstance => {\n return addToast(normalizeOptions(optionsOrMessage, 'success'));\n};\n\nconst createErrorToast = (optionsOrMessage: Omit<ToastOptions, 'type'> | string): ToastInstance => {\n return addToast(normalizeOptions(optionsOrMessage, 'error'));\n};\n\nconst createInfoToast = (optionsOrMessage: Omit<ToastOptions, 'type'> | string): ToastInstance => {\n return addToast(normalizeOptions(optionsOrMessage, 'info'));\n};\n\nconst createWarningToast = (\n optionsOrMessage: Omit<ToastOptions, 'type'> | string,\n): ToastInstance => {\n return addToast(normalizeOptions(optionsOrMessage, 'warning'));\n};\n\nconst createLoadingToast = (\n optionsOrMessage: Omit<ToastOptions, 'type'> | string,\n): ToastInstance => {\n const options = normalizeOptions(optionsOrMessage, 'loading');\n // Loading toasts don't auto-dismiss by default\n return addToast({ duration: 0, ...options });\n};\n\nasync function promiseToast<T>(promise: Promise<T>, options: ToastPromiseOptions<T>): Promise<T> {\n const loadingOptions =\n typeof options.loading === 'string'\n ? { description: options.loading }\n : (options.loading as ToastOptions);\n\n const loadingToast = addToast({\n closable: false,\n duration: 0,\n type: 'loading',\n ...loadingOptions,\n });\n\n try {\n const result = await promise;\n\n loadingToast.close();\n\n const successOptions = (() => {\n if (typeof options.success === 'string') {\n return { description: options.success };\n }\n if (typeof options.success === 'function') {\n return { description: options.success(result) };\n }\n return options.success as ToastOptions;\n })();\n\n addToast({ type: 'success', ...successOptions });\n\n return result;\n } catch (error) {\n loadingToast.close();\n\n const errorOptions = (() => {\n if (typeof options.error === 'string') {\n return { description: options.error };\n }\n if (typeof options.error === 'function') {\n return { description: options.error(error as Error) };\n }\n return options.error as ToastOptions;\n })();\n\n addToast({ type: 'error', ...errorOptions });\n\n throw error;\n }\n}\n\n// Base toast function\nconst baseToast = (options: ToastOptions): ToastInstance => {\n return addToast({ type: 'default', ...options });\n};\n\n// Toast API\nexport const toast: ToastAPI = Object.assign(baseToast, {\n dismiss: dismissToast,\n error: createErrorToast,\n info: createInfoToast,\n loading: createLoadingToast,\n promise: promiseToast,\n success: createSuccessToast,\n warning: createWarningToast,\n});\n\n// Toast List Component\nconst ToastList = memo(() => {\n const { toasts } = BaseToast.useToastManager();\n return toasts.map((t) => <ToastItem key={t.id} toast={t} />);\n});\n\nToastList.displayName = 'ToastList';\n\nexport interface ToastHostProps {\n className?: string;\n /**\n * Default duration for toasts\n * @default 5000\n */\n duration?: number;\n /**\n * Maximum number of toasts\n * @default 5\n */\n limit?: number;\n /**\n * Toast position\n * @default 'bottom-right'\n */\n position?: ToastPosition;\n /**\n * Root element for portal\n */\n root?: HTMLElement | ShadowRoot | null;\n /**\n * Swipe direction to dismiss\n * @default ['down', 'right']\n */\n swipeDirection?: ('left' | 'right' | 'up' | 'down') | ('left' | 'right' | 'up' | 'down')[];\n}\n\nexport const ToastHost = memo(\n ({\n root,\n className,\n duration = 5000,\n limit = 5,\n position = 'bottom-right',\n swipeDirection = ['down', 'right'],\n }: ToastHostProps) => {\n const isClient = useIsClient();\n const appElement = useAppElement();\n const [viewportZIndex, setViewportZIndex] = useState<number | undefined>(undefined);\n const hostId = useId();\n\n useEffect(() => registerToastHost(hostId), [hostId]);\n\n const isActive = useSyncExternalStore(\n subscribeToastHost,\n () => isActiveToastHost(hostId),\n () => false,\n );\n\n useEffect(() => {\n if (!isActive) return;\n globalState = {\n duration,\n limit,\n position,\n swipeDirection,\n };\n }, [duration, limit, position, swipeDirection, isActive]);\n\n useEffect(() => {\n if (!isActive) return;\n setViewportZIndex(acquireLayerZIndex('toast'));\n }, [isActive]);\n\n useEffect(() => {\n if (!isActive || !isClient) return undefined;\n // Runs after the six BaseToast.Provider children below have committed\n // and subscribed (child effects run before the parent's), so the\n // managers are guaranteed to have a live listener once this fires.\n markToastHostReady();\n return () => {\n markToastHostNotReady();\n };\n }, [isActive, isClient]);\n\n if (!isClient || !isActive) return null;\n\n const container = root ?? appElement ?? document.body;\n\n return ALL_POSITIONS.map((pos) => (\n <ToastContext key={pos} value={{ position: pos, swipeDirection }}>\n <BaseToast.Provider limit={limit} timeout={duration} toastManager={getManager(pos)}>\n <BaseToast.Portal container={container}>\n <BaseToast.Viewport\n className={cx(viewportVariants({ position: pos }), className)}\n style={{ zIndex: viewportZIndex }}\n >\n <ToastList />\n </BaseToast.Viewport>\n </BaseToast.Portal>\n </BaseToast.Provider>\n </ToastContext>\n ));\n },\n);\n\nToastHost.displayName = 'ToastHost';\n\n// Hook to use toast manager\nexport const useToast = () => toast;\n\nexport const __resetToastStateForTests = (): void => {\n globalState = {\n duration: 5000,\n limit: 5,\n position: 'bottom-right',\n swipeDirection: ['down', 'right'],\n };\n for (const position of ALL_POSITIONS) {\n toastManagers[position] = BaseToast.createToastManager();\n activeToastIds[position].clear();\n }\n __resetPendingToastQueueForTests();\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/base-ui/Toast/pendingQueue.ts
|
|
2
|
+
const MAX_QUEUED_ACTIONS = 20;
|
|
3
|
+
const QUEUE_TTL_MS = 5e3;
|
|
4
|
+
let ready = false;
|
|
5
|
+
let queue = [];
|
|
6
|
+
const runWhenToastHostReady = (run) => {
|
|
7
|
+
if (ready) {
|
|
8
|
+
run();
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
if (queue.length >= MAX_QUEUED_ACTIONS) queue.shift();
|
|
12
|
+
queue.push({
|
|
13
|
+
queuedAt: Date.now(),
|
|
14
|
+
run
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
const markToastHostReady = () => {
|
|
18
|
+
ready = true;
|
|
19
|
+
const pending = queue;
|
|
20
|
+
queue = [];
|
|
21
|
+
const now = Date.now();
|
|
22
|
+
for (const action of pending) if (now - action.queuedAt <= QUEUE_TTL_MS) action.run();
|
|
23
|
+
};
|
|
24
|
+
const markToastHostNotReady = () => {
|
|
25
|
+
ready = false;
|
|
26
|
+
};
|
|
27
|
+
//#endregion
|
|
28
|
+
export { markToastHostNotReady, markToastHostReady, runWhenToastHostReady };
|
|
29
|
+
|
|
30
|
+
//# sourceMappingURL=pendingQueue.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pendingQueue.mjs","names":[],"sources":["../../../src/base-ui/Toast/pendingQueue.ts"],"sourcesContent":["const MAX_QUEUED_ACTIONS = 20;\nconst QUEUE_TTL_MS = 5000;\n\ninterface QueuedAction {\n queuedAt: number;\n run: () => void;\n}\n\nlet ready = false;\nlet queue: QueuedAction[] = [];\n\nexport const runWhenToastHostReady = (run: () => void): void => {\n if (ready) {\n run();\n return;\n }\n if (queue.length >= MAX_QUEUED_ACTIONS) {\n queue.shift();\n }\n queue.push({ queuedAt: Date.now(), run });\n};\n\nexport const markToastHostReady = (): void => {\n ready = true;\n const pending = queue;\n queue = [];\n const now = Date.now();\n for (const action of pending) {\n if (now - action.queuedAt <= QUEUE_TTL_MS) action.run();\n }\n};\n\nexport const markToastHostNotReady = (): void => {\n ready = false;\n};\n\nexport const __resetPendingToastQueueForTests = (): void => {\n ready = false;\n queue = [];\n};\n"],"mappings":";AAAA,MAAM,qBAAqB;AAC3B,MAAM,eAAe;AAOrB,IAAI,QAAQ;AACZ,IAAI,QAAwB,CAAC;AAE7B,MAAa,yBAAyB,QAA0B;CAC9D,IAAI,OAAO;EACT,IAAI;EACJ;CACF;CACA,IAAI,MAAM,UAAU,oBAClB,MAAM,MAAM;CAEd,MAAM,KAAK;EAAE,UAAU,KAAK,IAAI;EAAG;CAAI,CAAC;AAC1C;AAEA,MAAa,2BAAiC;CAC5C,QAAQ;CACR,MAAM,UAAU;CAChB,QAAQ,CAAC;CACT,MAAM,MAAM,KAAK,IAAI;CACrB,KAAK,MAAM,UAAU,SACnB,IAAI,MAAM,OAAO,YAAY,cAAc,OAAO,IAAI;AAE1D;AAEA,MAAa,8BAAoC;CAC/C,QAAQ;AACV"}
|
package/es/base-ui/index.mjs
CHANGED
|
@@ -16,9 +16,9 @@ import { PopoverArrow, PopoverBackdrop, PopoverPopup, PopoverPortal, PopoverPosi
|
|
|
16
16
|
import { PopoverProvider, usePopoverContext } from "./Popover/context.mjs";
|
|
17
17
|
import Popover from "./Popover/Popover.mjs";
|
|
18
18
|
import PopoverGroup from "./Popover/PopoverGroup.mjs";
|
|
19
|
+
import { ToastHost, toast, useToast } from "./Toast/imperative.mjs";
|
|
19
20
|
import { ScrollAreaContent, ScrollAreaCorner, ScrollAreaRoot, ScrollAreaScrollbar, ScrollAreaThumb, ScrollAreaViewport } from "./ScrollArea/atoms.mjs";
|
|
20
21
|
import { ScrollArea } from "./ScrollArea/ScrollArea.mjs";
|
|
21
|
-
import { ToastHost, toast, useToast } from "./Toast/imperative.mjs";
|
|
22
22
|
import { controlHeight } from "./controlSize.mjs";
|
|
23
23
|
import { rootVariants, styles as styles$3 } from "./Input/style.mjs";
|
|
24
24
|
import Input from "./Input/Input.mjs";
|