@choice-ui/react 1.6.8 → 1.6.9

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.
Files changed (81) hide show
  1. package/dist/components/description/dist/index.d.ts +8 -0
  2. package/dist/components/description/dist/index.js +29 -0
  3. package/dist/components/description/src/description.d.ts +6 -0
  4. package/dist/components/description/src/description.js +18 -0
  5. package/dist/components/description/src/index.d.ts +2 -0
  6. package/dist/components/description/src/tv.d.ts +13 -0
  7. package/dist/components/description/src/tv.js +15 -0
  8. package/dist/components/description/tsup.config.d.ts +2 -0
  9. package/dist/components/emoji-picker/dist/index.d.ts +1 -0
  10. package/dist/components/emoji-picker/dist/index.js +4 -2
  11. package/dist/components/emoji-picker/src/emoji-picker.d.ts +1 -0
  12. package/dist/components/emoji-picker/src/emoji-picker.js +4 -2
  13. package/dist/components/error-message/dist/index.d.ts +8 -0
  14. package/dist/components/error-message/dist/index.js +30 -0
  15. package/dist/components/error-message/src/error-message.d.ts +6 -0
  16. package/dist/components/error-message/src/error-message.js +19 -0
  17. package/dist/components/error-message/src/index.d.ts +2 -0
  18. package/dist/components/error-message/src/tv.d.ts +13 -0
  19. package/dist/components/error-message/src/tv.js +15 -0
  20. package/dist/components/error-message/tsup.config.d.ts +2 -0
  21. package/dist/components/form/src/adapters/base-adapter.js +4 -2
  22. package/dist/components/form/src/tv.d.ts +0 -12
  23. package/dist/components/form/src/tv.js +1 -13
  24. package/dist/components/index.d.ts +3 -0
  25. package/dist/components/md-render/dist/index.d.ts +2 -1
  26. package/dist/components/md-render/dist/index.js +3 -1
  27. package/dist/components/md-render/src/md-render.js +3 -1
  28. package/dist/components/md-render/src/types.d.ts +2 -1
  29. package/dist/components/notifications/dist/index.d.ts +1 -5
  30. package/dist/components/notifications/src/notifications.d.ts +0 -1
  31. package/dist/components/notifications/src/notifications.js +0 -1
  32. package/dist/components/numeric-input/dist/index.d.ts +23 -9
  33. package/dist/components/numeric-input/dist/index.js +26 -3
  34. package/dist/components/numeric-input/src/components/numeric-input-menu-trigger.js +4 -1
  35. package/dist/components/numeric-input/src/hooks/index.d.ts +1 -0
  36. package/dist/components/numeric-input/src/hooks/use-numeric-long-press.d.ts +13 -0
  37. package/dist/components/numeric-input/src/hooks/use-numeric-long-press.js +27 -0
  38. package/dist/components/numeric-input/src/index.d.ts +1 -0
  39. package/dist/components/numeric-input/src/tv.js +22 -2
  40. package/dist/components/picture-preview/dist/index.d.ts +5 -0
  41. package/dist/components/picture-preview/dist/index.js +287 -140
  42. package/dist/components/picture-preview/src/hooks/useWheelHandler.d.ts +6 -1
  43. package/dist/components/picture-preview/src/hooks/useWheelHandler.js +25 -7
  44. package/dist/components/picture-preview/src/picture-preview.d.ts +5 -0
  45. package/dist/components/picture-preview/src/picture-preview.js +214 -123
  46. package/dist/components/picture-preview/src/tv.d.ts +93 -3
  47. package/dist/components/picture-preview/src/tv.js +48 -10
  48. package/dist/components/separator/dist/index.d.ts +1 -8
  49. package/dist/components/separator/src/separator.d.ts +1 -8
  50. package/dist/components/separator/src/separator.js +33 -5
  51. package/dist/components/separator/src/tv.d.ts +39 -18
  52. package/dist/components/separator/src/tv.js +37 -7
  53. package/dist/components/text-field/dist/index.d.ts +2 -3
  54. package/dist/components/text-field/dist/index.js +4 -19
  55. package/dist/components/text-field/src/components/index.d.ts +0 -1
  56. package/dist/components/text-field/src/text-field.d.ts +3 -2
  57. package/dist/components/text-field/src/text-field.js +2 -2
  58. package/dist/components/text-field/src/tv.d.ts +3 -3
  59. package/dist/components/text-field/src/tv.js +1 -6
  60. package/dist/components/toast/dist/index.d.ts +248 -0
  61. package/dist/components/toast/src/components/index.d.ts +3 -0
  62. package/dist/components/toast/src/components/toast-progress-bar.d.ts +7 -0
  63. package/dist/components/toast/src/components/toast-progress-bar.js +53 -0
  64. package/dist/components/toast/src/components/toaster-item.d.ts +26 -0
  65. package/dist/components/toast/src/components/toaster-item.js +416 -0
  66. package/dist/components/toast/src/components/toaster-slots.d.ts +87 -0
  67. package/dist/components/toast/src/components/toaster-slots.js +38 -0
  68. package/dist/components/toast/src/index.d.ts +5 -0
  69. package/dist/components/toast/src/store.d.ts +101 -0
  70. package/dist/components/toast/src/store.js +205 -0
  71. package/dist/components/toast/src/toaster.d.ts +87 -0
  72. package/dist/components/toast/src/toaster.js +271 -0
  73. package/dist/components/toast/src/tv.d.ts +365 -0
  74. package/dist/components/toast/src/tv.js +412 -0
  75. package/dist/components/toast/src/types.d.ts +79 -0
  76. package/dist/components/toast/tsup.config.d.ts +2 -0
  77. package/dist/index.js +11 -2
  78. package/dist/styles/components.css +2 -0
  79. package/package.json +1 -1
  80. package/dist/components/text-field/src/components/field-description.d.ts +0 -2
  81. package/dist/components/text-field/src/components/field-description.js +0 -16
@@ -1,7 +1,8 @@
1
1
  import { Dropdown as Dropdown2 } from "../../dropdown/dist/index.js";
2
2
  import { IconButton } from "../../icon-button/dist/index.js";
3
3
  import { LoaderCircle, ImageRemove, Delete, Add } from "@choiceform/icons-react";
4
- import { forwardRef, useState, useRef, useCallback, useEffect, useMemo } from "react";
4
+ import { forwardRef, useState, useMemo, useRef, useEffect, useCallback } from "react";
5
+ import { useEventCallback } from "usehooks-ts";
5
6
  import isHotkey from "is-hotkey";
6
7
  import { jsxs, jsx } from "react/jsx-runtime";
7
8
  import { tcv, tcx } from "../../../shared/utils/tcx/tcx.js";
@@ -91,7 +92,7 @@ function useDraggable(initialPosition = { x: 0, y: 0 }, options = {}) {
91
92
  };
92
93
  }
93
94
  function useWheelHandler(targetRef, zoomRef, positionRef, options = {}) {
94
- const { minZoom = 0.01, maxZoom = 10, zoomStep = 0.1, onZoom, onPan } = options;
95
+ const { minZoom = 0.01, maxZoom = 10, zoomStep = 0.1, onZoom, onPan, onZoomAtPoint } = options;
95
96
  const isMac = useRef(
96
97
  typeof navigator !== "undefined" && navigator.platform.toUpperCase().indexOf("MAC") >= 0
97
98
  );
@@ -100,14 +101,32 @@ function useWheelHandler(targetRef, zoomRef, positionRef, options = {}) {
100
101
  (event) => {
101
102
  event.preventDefault();
102
103
  event.stopPropagation();
103
- if (!zoomRef.current || !positionRef.current) return;
104
+ if (zoomRef.current === void 0 || zoomRef.current === null || !positionRef.current) return;
104
105
  const isPreciseEvent = event.deltaMode === 0;
105
106
  const hasDeltaX = Math.abs(event.deltaX) > 0;
106
107
  const isZoomModifier = isMac.current && event.metaKey || !isMac.current && event.ctrlKey;
107
- if (isZoomModifier) {
108
- const delta = -Math.sign(event.deltaY) * zoomStep;
109
- const newZoom = Math.max(minZoom, Math.min(maxZoom, zoomRef.current + delta));
110
- if (onZoom) {
108
+ const isPinchZoom = event.ctrlKey && isPreciseEvent && !event.metaKey;
109
+ if (isZoomModifier || isPinchZoom) {
110
+ const oldZoom = zoomRef.current;
111
+ let newZoom;
112
+ const sensitivity = isPinchZoom ? 8e-3 : 3e-3;
113
+ const delta = event.deltaY;
114
+ newZoom = oldZoom * Math.exp(-delta * sensitivity);
115
+ newZoom = Math.max(minZoom, Math.min(maxZoom, newZoom));
116
+ const target = targetRef.current;
117
+ if (onZoomAtPoint && target) {
118
+ const rect = target.getBoundingClientRect();
119
+ const mouseX = event.clientX - rect.left - rect.width / 2;
120
+ const mouseY = event.clientY - rect.top - rect.height / 2;
121
+ const zoomRatio = newZoom / oldZoom;
122
+ const currentX = positionRef.current.x;
123
+ const currentY = positionRef.current.y;
124
+ const newPosition = {
125
+ x: mouseX - (mouseX - currentX) * zoomRatio,
126
+ y: mouseY - (mouseY - currentY) * zoomRatio
127
+ };
128
+ onZoomAtPoint({ newZoom, newPosition });
129
+ } else if (onZoom) {
111
130
  onZoom(newZoom);
112
131
  }
113
132
  } else if (isPreciseEvent && hasDeltaX) {
@@ -139,7 +158,7 @@ function useWheelHandler(targetRef, zoomRef, positionRef, options = {}) {
139
158
  }
140
159
  }
141
160
  },
142
- [minZoom, maxZoom, zoomStep, onZoom, onPan]
161
+ [minZoom, maxZoom, onZoom, onPan, onZoomAtPoint, targetRef]
143
162
  );
144
163
  const handleKeyDown = useCallback((e) => {
145
164
  if (isMac.current && e.metaKey || !isMac.current && e.ctrlKey) {
@@ -200,24 +219,30 @@ var HOTKEYS = {
200
219
  };
201
220
  var PicturePreviewTv = tcv({
202
221
  slots: {
203
- root: ["relative flex flex-col overflow-hidden", "h-full w-full", "touch-none select-none"],
222
+ root: [
223
+ "group/picture-preview relative flex flex-col overflow-hidden",
224
+ "h-full w-full",
225
+ "touch-none select-none"
226
+ ],
204
227
  loading: [
205
228
  "text-secondary-foreground absolute inset-0 z-10 flex flex-col items-center justify-center gap-4"
206
229
  ],
207
230
  content: [
208
231
  "relative flex-1 overflow-hidden",
209
232
  "flex items-center justify-center",
210
- "bg-gray-100 dark:bg-gray-900"
233
+ "bg-gray-100 dark:bg-gray-900",
234
+ "[contain:layout_paint]"
211
235
  ],
212
236
  canvas: [
213
- "relative h-full w-full",
214
237
  "transform-gpu will-change-transform",
215
- "cursor-grab active:cursor-grabbing"
238
+ "cursor-grab active:cursor-grabbing",
239
+ "origin-center",
240
+ "flex items-center justify-center"
216
241
  ],
217
- image: ["pointer-events-none", "h-full w-full object-contain"],
242
+ image: ["pointer-events-none", "block w-auto h-auto"],
218
243
  controlGroup: [
219
244
  "overflow-hidden",
220
- "absolute right-2 bottom-2 flex items-center",
245
+ "absolute flex items-center",
221
246
  "bg-default-background",
222
247
  "rounded-md",
223
248
  "shadow-md"
@@ -226,26 +251,58 @@ var PicturePreviewTv = tcv({
226
251
  variants: {
227
252
  isLoading: {
228
253
  true: {
229
- image: "opacity-0 transition-opacity duration-300"
254
+ image: "opacity-0 scale-105 blur-sm transition-all duration-500 ease-out"
230
255
  },
231
- false: {}
256
+ false: {
257
+ image: "opacity-100 scale-100 blur-0 transition-all duration-500 ease-out"
258
+ }
232
259
  },
233
260
  isError: {
234
261
  true: {
235
- image: "opacity-0 transition-opacity duration-300"
262
+ image: "opacity-0"
263
+ },
264
+ false: {}
265
+ },
266
+ isMenuOpen: {
267
+ true: {
268
+ controlGroup: "opacity-100"
236
269
  },
237
270
  false: {}
271
+ },
272
+ controlPosition: {
273
+ "top-left": {
274
+ controlGroup: "top-2 left-2"
275
+ },
276
+ "top-right": {
277
+ controlGroup: "top-2 right-2"
278
+ },
279
+ "bottom-left": {
280
+ controlGroup: "bottom-2 left-2"
281
+ },
282
+ "bottom-right": {
283
+ controlGroup: "bottom-2 right-2"
284
+ }
285
+ },
286
+ controlShow: {
287
+ always: {
288
+ controlGroup: ""
289
+ },
290
+ hover: {
291
+ controlGroup: "group-hover/picture-preview:opacity-100 opacity-0 transition-opacity duration-200"
292
+ }
238
293
  }
239
294
  },
240
295
  defaultVariants: {
241
296
  isLoading: false,
242
- isError: false
297
+ isError: false,
298
+ isMenuOpen: false,
299
+ controlPosition: "bottom-right"
243
300
  }
244
301
  });
245
- var MIN_ZOOM = 0.01;
246
- var MAX_ZOOM = 10;
247
302
  var ZOOM_STEP = 0.1;
248
303
  var INITIAL_ZOOM = 1;
304
+ var MIN_ACTUAL_PERCENT = 2;
305
+ var MAX_ACTUAL_PERCENT = 1e3;
249
306
  var PicturePreview = forwardRef(
250
307
  function PicturePreview2(props, ref) {
251
308
  const {
@@ -263,16 +320,32 @@ var PicturePreview = forwardRef(
263
320
  zoomTo200: "Zoom to 200%",
264
321
  error: "Image loading failed, please try again."
265
322
  },
323
+ control = {
324
+ enable: true,
325
+ position: "bottom-right",
326
+ show: "hover"
327
+ },
266
328
  ...rest
267
329
  } = props;
268
330
  const [zoom, setZoom] = useState(INITIAL_ZOOM);
269
331
  const [isLoading, setIsLoading] = useState(true);
270
332
  const [isError, setIsError] = useState(false);
333
+ const [naturalSize, setNaturalSize] = useState(null);
334
+ const [baseScale, setBaseScale] = useState(1);
335
+ const [menuIsOpen, setMenuIsOpen] = useState(false);
336
+ const minZoom = useMemo(
337
+ () => baseScale > 0 ? MIN_ACTUAL_PERCENT / 100 / baseScale : 0.01,
338
+ [baseScale]
339
+ );
340
+ const maxZoom = useMemo(
341
+ () => baseScale > 0 ? MAX_ACTUAL_PERCENT / 100 / baseScale : 100,
342
+ [baseScale]
343
+ );
271
344
  const internalRef = useRef(null);
272
345
  const canvasRef = useRef(null);
273
346
  const zoomRef = useRef(zoom);
274
347
  const rafId = useRef(null);
275
- const scheduleUpdate = useCallback(() => {
348
+ const scheduleUpdate = useEventCallback(() => {
276
349
  if (rafId.current !== null) {
277
350
  return;
278
351
  }
@@ -280,7 +353,7 @@ var PicturePreview = forwardRef(
280
353
  setZoom(zoomRef.current);
281
354
  rafId.current = null;
282
355
  });
283
- }, []);
356
+ });
284
357
  useEffect(() => {
285
358
  return () => {
286
359
  if (rafId.current !== null) {
@@ -292,43 +365,52 @@ var PicturePreview = forwardRef(
292
365
  zoomRef.current = zoom;
293
366
  }, [zoom]);
294
367
  const { position, isDragging, handleMouseDown, updatePosition, positionRef } = useDraggable();
295
- const handleZoomChange = useCallback(
296
- (newZoom) => {
297
- zoomRef.current = newZoom;
368
+ const handlePositionChange = useEventCallback((newPosition) => {
369
+ updatePosition(newPosition);
370
+ });
371
+ const handleZoomAtPoint = useEventCallback(
372
+ (params) => {
373
+ zoomRef.current = params.newZoom;
374
+ updatePosition(params.newPosition);
298
375
  scheduleUpdate();
299
- },
300
- [scheduleUpdate]
301
- );
302
- const handlePositionChange = useCallback(
303
- (newPosition) => {
304
- updatePosition(newPosition);
305
- },
306
- [updatePosition]
376
+ }
307
377
  );
308
378
  useWheelHandler(internalRef, zoomRef, positionRef, {
309
- minZoom: MIN_ZOOM,
310
- maxZoom: MAX_ZOOM,
379
+ minZoom,
380
+ maxZoom,
311
381
  zoomStep: ZOOM_STEP,
312
- onZoom: handleZoomChange,
313
- onPan: handlePositionChange
382
+ onPan: handlePositionChange,
383
+ onZoomAtPoint: handleZoomAtPoint
384
+ });
385
+ const zoomIn = useEventCallback(() => {
386
+ zoomRef.current = Math.min(maxZoom, zoomRef.current + ZOOM_STEP);
387
+ scheduleUpdate();
314
388
  });
315
- const zoomIn = useCallback(() => {
316
- handleZoomChange(Math.min(MAX_ZOOM, zoomRef.current + ZOOM_STEP));
317
- }, [handleZoomChange]);
318
- const zoomOut = useCallback(() => {
319
- handleZoomChange(Math.max(MIN_ZOOM, zoomRef.current - ZOOM_STEP));
320
- }, [handleZoomChange]);
321
- const resetView = useCallback(() => {
389
+ const zoomOut = useEventCallback(() => {
390
+ zoomRef.current = Math.max(minZoom, zoomRef.current - ZOOM_STEP);
391
+ scheduleUpdate();
392
+ });
393
+ const resetView = useEventCallback(() => {
322
394
  zoomRef.current = INITIAL_ZOOM;
323
395
  updatePosition({ x: 0, y: 0 });
324
396
  scheduleUpdate();
325
- }, [updatePosition, scheduleUpdate]);
326
- const fitToView = useCallback(() => {
327
- if (!canvasRef.current) return;
328
- zoomRef.current = 1;
397
+ });
398
+ const fitToView = useEventCallback(() => {
399
+ if (!canvasRef.current || !naturalSize || !internalRef.current || baseScale <= 0) return;
400
+ const containerRect = internalRef.current.getBoundingClientRect();
401
+ const containerWidth = containerRect.width;
402
+ const containerHeight = containerRect.height;
403
+ if (naturalSize.width <= 0 || naturalSize.height <= 0) return;
404
+ const scaleX = containerWidth / naturalSize.width;
405
+ const scaleY = containerHeight / naturalSize.height;
406
+ const fitScale = Math.min(scaleX, scaleY);
407
+ zoomRef.current = fitScale / baseScale;
329
408
  updatePosition({ x: 0, y: 0 });
330
409
  scheduleUpdate();
331
- }, [updatePosition, scheduleUpdate]);
410
+ });
411
+ const handleDoubleClick = useEventCallback(() => {
412
+ fitToView();
413
+ });
332
414
  useHotkeys([
333
415
  {
334
416
  hotkey: HOTKEYS.ZOOM_IN,
@@ -347,13 +429,16 @@ var PicturePreview = forwardRef(
347
429
  handler: () => fitToView()
348
430
  }
349
431
  ]);
350
- const handleZoomMenuItemClick = useCallback(
351
- (zoomLevel) => {
352
- zoomRef.current = zoomLevel;
353
- scheduleUpdate();
354
- },
355
- [scheduleUpdate]
356
- );
432
+ const handleZoomMenuItemClick = useEventCallback((zoomLevel) => {
433
+ zoomRef.current = zoomLevel;
434
+ scheduleUpdate();
435
+ });
436
+ const setActualZoomPercent = useEventCallback((percent) => {
437
+ if (baseScale === 0) return;
438
+ const newZoom = percent / 100 / baseScale;
439
+ zoomRef.current = Math.max(minZoom, Math.min(maxZoom, newZoom));
440
+ scheduleUpdate();
441
+ });
357
442
  useEffect(() => {
358
443
  if (!internalRef.current) return;
359
444
  if (typeof ref === "function") {
@@ -373,26 +458,75 @@ var PicturePreview = forwardRef(
373
458
  backfaceVisibility: "hidden"
374
459
  };
375
460
  }, [position.x, position.y, zoom, isDragging]);
461
+ const calculateBaseScale = useCallback(() => {
462
+ if (!naturalSize || !internalRef.current) return 1;
463
+ const containerRect = internalRef.current.getBoundingClientRect();
464
+ const containerWidth = containerRect.width;
465
+ const containerHeight = containerRect.height;
466
+ if (naturalSize.width <= 0 || naturalSize.height <= 0 || containerWidth <= 0 || containerHeight <= 0) {
467
+ return 1;
468
+ }
469
+ const scaleX = containerWidth / naturalSize.width;
470
+ const scaleY = containerHeight / naturalSize.height;
471
+ return Math.min(scaleX, scaleY);
472
+ }, [naturalSize]);
473
+ useEffect(() => {
474
+ if (!naturalSize || !internalRef.current) return;
475
+ const updateBaseScale = () => {
476
+ const scale = calculateBaseScale();
477
+ setBaseScale(scale);
478
+ };
479
+ updateBaseScale();
480
+ const resizeObserver = new ResizeObserver(() => {
481
+ updateBaseScale();
482
+ });
483
+ resizeObserver.observe(internalRef.current);
484
+ return () => {
485
+ resizeObserver.disconnect();
486
+ };
487
+ }, [naturalSize, calculateBaseScale]);
376
488
  useEffect(() => {
377
489
  setIsLoading(true);
490
+ setIsError(false);
491
+ setNaturalSize(null);
378
492
  const img = new Image();
379
- img.src = src;
493
+ let isCancelled = false;
380
494
  img.onload = () => {
495
+ if (isCancelled) return;
496
+ setNaturalSize({ width: img.naturalWidth, height: img.naturalHeight });
381
497
  setIsLoading(false);
382
498
  };
383
499
  img.onerror = () => {
500
+ if (isCancelled) return;
501
+ setIsError(true);
384
502
  setIsLoading(false);
385
503
  };
504
+ img.src = src;
505
+ return () => {
506
+ isCancelled = true;
507
+ img.onload = null;
508
+ img.onerror = null;
509
+ img.src = "";
510
+ };
386
511
  }, [src]);
387
- const styles = PicturePreviewTv({ isLoading, isError });
512
+ const actualZoomPercent = useMemo(() => {
513
+ return Math.round(zoom * baseScale * 100);
514
+ }, [zoom, baseScale]);
515
+ const tv = PicturePreviewTv({
516
+ isLoading,
517
+ isError,
518
+ isMenuOpen: menuIsOpen,
519
+ controlPosition: control.position,
520
+ controlShow: control.show
521
+ });
388
522
  return /* @__PURE__ */ jsxs(
389
523
  "div",
390
524
  {
391
525
  ref: internalRef,
392
- className: tcx(styles.root(), className),
526
+ className: tcx(tv.root(), className),
393
527
  ...rest,
394
528
  children: [
395
- isLoading && /* @__PURE__ */ jsx("div", { className: styles.loading(), children: /* @__PURE__ */ jsx(
529
+ isLoading && /* @__PURE__ */ jsx("div", { className: tv.loading(), children: /* @__PURE__ */ jsx(
396
530
  LoaderCircle,
397
531
  {
398
532
  className: "animate-spin",
@@ -400,7 +534,7 @@ var PicturePreview = forwardRef(
400
534
  height: 32
401
535
  }
402
536
  ) }),
403
- isError && /* @__PURE__ */ jsxs("div", { className: styles.loading(), children: [
537
+ isError && /* @__PURE__ */ jsxs("div", { className: tv.loading(), children: [
404
538
  /* @__PURE__ */ jsx(
405
539
  ImageRemove,
406
540
  {
@@ -410,19 +544,24 @@ var PicturePreview = forwardRef(
410
544
  ),
411
545
  /* @__PURE__ */ jsx("span", { children: defaultText.error })
412
546
  ] }),
413
- /* @__PURE__ */ jsx("div", { className: styles.content(), children: /* @__PURE__ */ jsx(
547
+ !isError && /* @__PURE__ */ jsx("div", { className: tv.content(), children: /* @__PURE__ */ jsx(
414
548
  "div",
415
549
  {
416
550
  ref: canvasRef,
417
- className: styles.canvas(),
551
+ className: tv.canvas(),
418
552
  style: transformStyle,
419
553
  onMouseDown: handleMouseDown,
554
+ onDoubleClick: handleDoubleClick,
420
555
  children: /* @__PURE__ */ jsx(
421
556
  "img",
422
557
  {
423
558
  src,
424
559
  alt: fileName || "Preview",
425
- className: styles.image(),
560
+ className: tv.image(),
561
+ style: naturalSize ? {
562
+ width: naturalSize.width * baseScale,
563
+ height: naturalSize.height * baseScale
564
+ } : void 0,
426
565
  draggable: false,
427
566
  loading: "eager",
428
567
  decoding: "async",
@@ -432,7 +571,7 @@ var PicturePreview = forwardRef(
432
571
  )
433
572
  }
434
573
  ) }),
435
- isError || isLoading ? null : /* @__PURE__ */ jsxs("div", { className: styles.controlGroup(), children: [
574
+ isError || isLoading || control.enable === false ? null : /* @__PURE__ */ jsxs("div", { className: tv.controlGroup(), children: [
436
575
  /* @__PURE__ */ jsx(
437
576
  IconButton,
438
577
  {
@@ -449,82 +588,90 @@ var PicturePreview = forwardRef(
449
588
  children: /* @__PURE__ */ jsx(Delete, {})
450
589
  }
451
590
  ),
452
- /* @__PURE__ */ jsxs(Dropdown2, { selection: true, children: [
453
- /* @__PURE__ */ jsx(
454
- Dropdown2.Trigger,
455
- {
456
- variant: "ghost",
457
- className: "border-x-default rounded-none",
458
- size: "large",
459
- children: /* @__PURE__ */ jsxs("span", { className: "flex-1", children: [
460
- Math.round(zoom * 100),
461
- "%"
591
+ /* @__PURE__ */ jsxs(
592
+ Dropdown2,
593
+ {
594
+ selection: true,
595
+ open: menuIsOpen,
596
+ onOpenChange: setMenuIsOpen,
597
+ children: [
598
+ /* @__PURE__ */ jsx(
599
+ Dropdown2.Trigger,
600
+ {
601
+ variant: "ghost",
602
+ className: "border-x-default rounded-none",
603
+ size: "large",
604
+ children: /* @__PURE__ */ jsxs("span", { className: "flex-1", children: [
605
+ actualZoomPercent,
606
+ "%"
607
+ ] })
608
+ }
609
+ ),
610
+ /* @__PURE__ */ jsxs(Dropdown2.Content, { children: [
611
+ /* @__PURE__ */ jsx(
612
+ Dropdown2.Item,
613
+ {
614
+ onMouseUp: () => handleZoomMenuItemClick(zoomRef.current + ZOOM_STEP),
615
+ shortcut: {
616
+ keys: "+",
617
+ modifier: "command"
618
+ },
619
+ children: /* @__PURE__ */ jsx("span", { className: "flex-1", children: defaultText.zoomIn })
620
+ }
621
+ ),
622
+ /* @__PURE__ */ jsx(
623
+ Dropdown2.Item,
624
+ {
625
+ onMouseUp: () => handleZoomMenuItemClick(zoomRef.current - ZOOM_STEP),
626
+ shortcut: {
627
+ keys: "-",
628
+ modifier: "command"
629
+ },
630
+ children: /* @__PURE__ */ jsx("span", { className: "flex-1", children: defaultText.zoomOut })
631
+ }
632
+ ),
633
+ /* @__PURE__ */ jsx(
634
+ Dropdown2.Item,
635
+ {
636
+ selected: actualZoomPercent === 50,
637
+ onMouseUp: () => setActualZoomPercent(50),
638
+ children: /* @__PURE__ */ jsx("span", { className: "flex-1", children: defaultText.zoomTo50 })
639
+ }
640
+ ),
641
+ /* @__PURE__ */ jsx(
642
+ Dropdown2.Item,
643
+ {
644
+ selected: actualZoomPercent === 100,
645
+ onMouseUp: () => setActualZoomPercent(100),
646
+ children: /* @__PURE__ */ jsx("span", { className: "flex-1", children: defaultText.zoomTo100 })
647
+ }
648
+ ),
649
+ /* @__PURE__ */ jsx(
650
+ Dropdown2.Item,
651
+ {
652
+ selected: actualZoomPercent === 200,
653
+ onMouseUp: () => setActualZoomPercent(200),
654
+ children: /* @__PURE__ */ jsx("span", { className: "flex-1", children: defaultText.zoomTo200 })
655
+ }
656
+ ),
657
+ /* @__PURE__ */ jsx(Dropdown2.Divider, {}),
658
+ /* @__PURE__ */ jsx(
659
+ Dropdown2.Item,
660
+ {
661
+ onMouseUp: () => {
662
+ fitToView();
663
+ },
664
+ shortcut: {
665
+ keys: "1",
666
+ modifier: "command"
667
+ },
668
+ children: /* @__PURE__ */ jsx("span", { className: "flex-1", children: defaultText.fitToScreen })
669
+ }
670
+ )
462
671
  ] })
463
- }
464
- ),
465
- /* @__PURE__ */ jsxs(Dropdown2.Content, { children: [
466
- /* @__PURE__ */ jsx(
467
- Dropdown2.Item,
468
- {
469
- onMouseUp: () => handleZoomMenuItemClick(zoomRef.current + ZOOM_STEP),
470
- shortcut: {
471
- keys: "+",
472
- modifier: "command"
473
- },
474
- children: /* @__PURE__ */ jsx("span", { className: "flex-1", children: defaultText.zoomIn })
475
- }
476
- ),
477
- /* @__PURE__ */ jsx(
478
- Dropdown2.Item,
479
- {
480
- onMouseUp: () => handleZoomMenuItemClick(zoomRef.current - ZOOM_STEP),
481
- shortcut: {
482
- keys: "-",
483
- modifier: "command"
484
- },
485
- children: /* @__PURE__ */ jsx("span", { className: "flex-1", children: defaultText.zoomOut })
486
- }
487
- ),
488
- /* @__PURE__ */ jsx(
489
- Dropdown2.Item,
490
- {
491
- selected: zoomRef.current === 0.5,
492
- onMouseUp: () => handleZoomMenuItemClick(0.5),
493
- children: /* @__PURE__ */ jsx("span", { className: "flex-1", children: defaultText.zoomTo50 })
494
- }
495
- ),
496
- /* @__PURE__ */ jsx(
497
- Dropdown2.Item,
498
- {
499
- selected: zoomRef.current === 1,
500
- onMouseUp: () => handleZoomMenuItemClick(1),
501
- children: /* @__PURE__ */ jsx("span", { className: "flex-1", children: defaultText.zoomTo100 })
502
- }
503
- ),
504
- /* @__PURE__ */ jsx(
505
- Dropdown2.Item,
506
- {
507
- selected: zoomRef.current === 2,
508
- onMouseUp: () => handleZoomMenuItemClick(2),
509
- children: /* @__PURE__ */ jsx("span", { className: "flex-1", children: defaultText.zoomTo200 })
510
- }
511
- ),
512
- /* @__PURE__ */ jsx(Dropdown2.Divider, {}),
513
- /* @__PURE__ */ jsx(
514
- Dropdown2.Item,
515
- {
516
- onMouseUp: () => {
517
- fitToView();
518
- },
519
- shortcut: {
520
- keys: "1",
521
- modifier: "command"
522
- },
523
- children: /* @__PURE__ */ jsx("span", { className: "flex-1", children: defaultText.fitToScreen })
524
- }
525
- )
526
- ] })
527
- ] }),
672
+ ]
673
+ }
674
+ ),
528
675
  /* @__PURE__ */ jsx(
529
676
  IconButton,
530
677
  {
@@ -2,12 +2,17 @@ interface Position {
2
2
  x: number;
3
3
  y: number;
4
4
  }
5
+ interface ZoomAtPointParams {
6
+ newZoom: number;
7
+ newPosition: Position;
8
+ }
5
9
  interface WheelHandlerOptions {
6
10
  minZoom?: number;
7
11
  maxZoom?: number;
8
12
  zoomStep?: number;
9
13
  onZoom?: (newZoom: number) => void;
10
14
  onPan?: (newPosition: Position) => void;
15
+ onZoomAtPoint?: (params: ZoomAtPointParams) => void;
11
16
  }
12
17
  /**
13
18
  * 用于处理鼠标滚轮和触摸板手势的自定义Hook
@@ -16,7 +21,7 @@ interface WheelHandlerOptions {
16
21
  * @param positionRef 当前位置的ref
17
22
  * @param options 配置选项
18
23
  */
19
- export declare function useWheelHandler(targetRef: React.RefObject<HTMLElement>, zoomRef: React.RefObject<number>, positionRef: React.RefObject<Position>, options?: WheelHandlerOptions): {
24
+ export declare function useWheelHandler(targetRef: React.RefObject<HTMLElement | null>, zoomRef: React.RefObject<number>, positionRef: React.RefObject<Position>, options?: WheelHandlerOptions): {
20
25
  isMac: boolean;
21
26
  isCmdPressed: import('react').MutableRefObject<boolean>;
22
27
  };
@@ -1,6 +1,6 @@
1
1
  import { useRef, useCallback, useEffect } from "react";
2
2
  function useWheelHandler(targetRef, zoomRef, positionRef, options = {}) {
3
- const { minZoom = 0.01, maxZoom = 10, zoomStep = 0.1, onZoom, onPan } = options;
3
+ const { minZoom = 0.01, maxZoom = 10, zoomStep = 0.1, onZoom, onPan, onZoomAtPoint } = options;
4
4
  const isMac = useRef(
5
5
  typeof navigator !== "undefined" && navigator.platform.toUpperCase().indexOf("MAC") >= 0
6
6
  );
@@ -9,14 +9,32 @@ function useWheelHandler(targetRef, zoomRef, positionRef, options = {}) {
9
9
  (event) => {
10
10
  event.preventDefault();
11
11
  event.stopPropagation();
12
- if (!zoomRef.current || !positionRef.current) return;
12
+ if (zoomRef.current === void 0 || zoomRef.current === null || !positionRef.current) return;
13
13
  const isPreciseEvent = event.deltaMode === 0;
14
14
  const hasDeltaX = Math.abs(event.deltaX) > 0;
15
15
  const isZoomModifier = isMac.current && event.metaKey || !isMac.current && event.ctrlKey;
16
- if (isZoomModifier) {
17
- const delta = -Math.sign(event.deltaY) * zoomStep;
18
- const newZoom = Math.max(minZoom, Math.min(maxZoom, zoomRef.current + delta));
19
- if (onZoom) {
16
+ const isPinchZoom = event.ctrlKey && isPreciseEvent && !event.metaKey;
17
+ if (isZoomModifier || isPinchZoom) {
18
+ const oldZoom = zoomRef.current;
19
+ let newZoom;
20
+ const sensitivity = isPinchZoom ? 8e-3 : 3e-3;
21
+ const delta = event.deltaY;
22
+ newZoom = oldZoom * Math.exp(-delta * sensitivity);
23
+ newZoom = Math.max(minZoom, Math.min(maxZoom, newZoom));
24
+ const target = targetRef.current;
25
+ if (onZoomAtPoint && target) {
26
+ const rect = target.getBoundingClientRect();
27
+ const mouseX = event.clientX - rect.left - rect.width / 2;
28
+ const mouseY = event.clientY - rect.top - rect.height / 2;
29
+ const zoomRatio = newZoom / oldZoom;
30
+ const currentX = positionRef.current.x;
31
+ const currentY = positionRef.current.y;
32
+ const newPosition = {
33
+ x: mouseX - (mouseX - currentX) * zoomRatio,
34
+ y: mouseY - (mouseY - currentY) * zoomRatio
35
+ };
36
+ onZoomAtPoint({ newZoom, newPosition });
37
+ } else if (onZoom) {
20
38
  onZoom(newZoom);
21
39
  }
22
40
  } else if (isPreciseEvent && hasDeltaX) {
@@ -48,7 +66,7 @@ function useWheelHandler(targetRef, zoomRef, positionRef, options = {}) {
48
66
  }
49
67
  }
50
68
  },
51
- [minZoom, maxZoom, zoomStep, onZoom, onPan]
69
+ [minZoom, maxZoom, onZoom, onPan, onZoomAtPoint, targetRef]
52
70
  );
53
71
  const handleKeyDown = useCallback((e) => {
54
72
  if (isMac.current && e.metaKey || !isMac.current && e.ctrlKey) {