@windrun-huaiin/third-ui 5.13.6 → 5.14.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windrun-huaiin/third-ui",
3
- "version": "5.13.6",
3
+ "version": "5.14.1",
4
4
  "description": "Third-party integrated UI components for windrun-huaiin projects",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -1,11 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { globalLucideIcons as icons } from '@base-ui/components/global-icon';
4
- import {
5
- AlertDialog,
6
- AlertDialogContent,
7
- AlertDialogTitle,
8
- } from '@base-ui/ui/alert-dialog';
4
+ // Attention: do not use external dialog library, avoid react context conflict when building third-party applications
9
5
  import type { MermaidConfig } from 'mermaid';
10
6
  import { useTheme } from 'next-themes';
11
7
  import { useCallback, useEffect, useId, useRef, useState } from 'react';
@@ -72,22 +68,24 @@ export function Mermaid({ chart, title, watermarkEnabled, watermarkText, enableP
72
68
  // helpers for preview zoom
73
69
  const clamp = (v: number, min: number, max: number) => Math.min(Math.max(v, min), max);
74
70
  const resetTransform = useCallback(() => {
75
- setScale(1);
71
+ setScale(4); // 400%
76
72
  setTranslate({ x: 0, y: 0 });
77
73
  }, []);
78
74
 
79
75
  const zoomBy = useCallback((delta: number) => {
80
- // 基于中心缩放:保持缩放中心在画布中点,不引入位移
81
- setScale((prev) => clamp(prev + delta, 0.25, 6));
76
+ // zoom by center: keep the zoom center at the center of the canvas, without introducing displacement
77
+ setScale((prev) => clamp(prev + delta, 0.25, 10));
82
78
  }, []);
83
79
 
84
80
  const onWheel = useCallback((e: React.WheelEvent<HTMLDivElement>) => {
85
- // Cmd/Ctrl + 滚轮缩放(围绕中心点),否则上下平移
81
+ // Cmd/Ctrl + wheel zoom (around the center point), otherwise up and down panning
86
82
  if (e.metaKey || e.ctrlKey) {
87
83
  e.preventDefault();
84
+ e.stopPropagation();
88
85
  const delta = e.deltaY > 0 ? -0.1 : 0.1;
89
- setScale((prev) => clamp(prev + delta, 0.25, 6));
86
+ setScale((prev) => clamp(prev + delta, 0.25, 10));
90
87
  } else {
88
+ e.stopPropagation();
91
89
  setTranslate((prev) => ({ x: prev.x, y: prev.y - e.deltaY }));
92
90
  }
93
91
  }, []);
@@ -110,6 +108,62 @@ export function Mermaid({ chart, title, watermarkEnabled, watermarkText, enableP
110
108
  isPanningRef.current = false;
111
109
  (e.currentTarget as HTMLDivElement).releasePointerCapture(e.pointerId);
112
110
  }, []);
111
+
112
+ // prevent browser-level zoom (touchpad pinch/shortcut) from taking effect when the dialog is open
113
+ useEffect(() => {
114
+ if (!open) return;
115
+ // 初次打开时,默认放大到 400%
116
+ resetTransform();
117
+ const onGlobalWheel = (ev: WheelEvent) => {
118
+ if (ev.ctrlKey || ev.metaKey) {
119
+ ev.preventDefault();
120
+ }
121
+ };
122
+ const onKeyDown = (ev: KeyboardEvent) => {
123
+ if (!(ev.ctrlKey || ev.metaKey)) return;
124
+ const k = ev.key;
125
+ if (k === '=' || k === '+') {
126
+ ev.preventDefault();
127
+ setScale((prev) => clamp(prev + 0.2, 0.25, 10));
128
+ } else if (k === '-') {
129
+ ev.preventDefault();
130
+ setScale((prev) => clamp(prev - 0.2, 0.25, 10));
131
+ } else if (k === '0') {
132
+ ev.preventDefault();
133
+ resetTransform();
134
+ }
135
+ };
136
+ window.addEventListener('wheel', onGlobalWheel, { passive: false, capture: true });
137
+ window.addEventListener('keydown', onKeyDown, { capture: true });
138
+ return () => {
139
+ window.removeEventListener('wheel', onGlobalWheel, true);
140
+ window.removeEventListener('keydown', onKeyDown, true);
141
+ };
142
+ }, [open, resetTransform]);
143
+
144
+ // Lock background scroll when dialog is open
145
+ useEffect(() => {
146
+ if (!open) return;
147
+ const previousPosition = document.body.style.position;
148
+ const previousTop = document.body.style.top;
149
+ const previousLeft = document.body.style.left;
150
+ const previousRight = document.body.style.right;
151
+ const previousWidth = document.body.style.width;
152
+ const scrollY = window.scrollY;
153
+ document.body.style.position = 'fixed';
154
+ document.body.style.top = `-${scrollY}px`;
155
+ document.body.style.left = '0';
156
+ document.body.style.right = '0';
157
+ document.body.style.width = '100%';
158
+ return () => {
159
+ document.body.style.position = previousPosition;
160
+ document.body.style.top = previousTop;
161
+ document.body.style.left = previousLeft;
162
+ document.body.style.right = previousRight;
163
+ document.body.style.width = previousWidth;
164
+ window.scrollTo(0, scrollY);
165
+ };
166
+ }, [open]);
113
167
 
114
168
  return (
115
169
  <div>
@@ -133,11 +187,21 @@ export function Mermaid({ chart, title, watermarkEnabled, watermarkText, enableP
133
187
  </div>
134
188
  )}
135
189
 
136
- {/* Preview Dialog */}
137
- {enablePreview && (
138
- <AlertDialog open={open} onOpenChange={(o) => { setOpen(o); if (!o) resetTransform(); }}>
139
- <AlertDialogContent className="z-50 max-w-[95vw] w-[95vw] h-[88vh] p-0 bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-700">
140
- <AlertDialogTitle className="sr-only">{title ?? 'Mermaid Preview'}</AlertDialogTitle>
190
+ {/* Preview Dialog (custom minimal dialog) */}
191
+ {enablePreview && open && (
192
+ <div
193
+ role="dialog"
194
+ aria-modal="true"
195
+ aria-label={typeof title === 'string' ? title : 'Mermaid Preview'}
196
+ className="fixed inset-0 z-[9999] flex items-center justify-center"
197
+ >
198
+ <div
199
+ className="absolute inset-0 bg-black/60"
200
+ onClick={() => { setOpen(false); resetTransform(); }}
201
+ onWheel={(e) => { e.preventDefault(); e.stopPropagation(); }}
202
+ onTouchMove={(e) => { e.preventDefault(); e.stopPropagation(); }}
203
+ />
204
+ <div className="relative z-[1] max-w-[95vw] w-[95vw] h-[88vh] p-0 bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-700 rounded-md shadow-2xl overflow-hidden">
141
205
  {/* Top bar */}
142
206
  <div className="flex items-center justify-between px-3 py-2 border-b border-neutral-200 dark:border-neutral-700">
143
207
  <div className="flex items-center gap-2 text-sm text-neutral-600 dark:text-neutral-300">
@@ -148,7 +212,7 @@ export function Mermaid({ chart, title, watermarkEnabled, watermarkText, enableP
148
212
  <button
149
213
  aria-label="Zoom out"
150
214
  className="flex h-6 w-6 items-center justify-center rounded border border-neutral-300 dark:border-neutral-600 text-[13px]"
151
- onClick={() => zoomBy(-0.2)}
215
+ onClick={() => zoomBy(-0.5)}
152
216
  >
153
217
 
154
218
  </button>
@@ -156,10 +220,40 @@ export function Mermaid({ chart, title, watermarkEnabled, watermarkText, enableP
156
220
  <button
157
221
  aria-label="Zoom in"
158
222
  className="flex h-6 w-6 items-center justify-center rounded border border-neutral-300 dark:border-neutral-600 text-[13px]"
159
- onClick={() => zoomBy(0.2)}
223
+ onClick={() => zoomBy(0.5)}
160
224
  >
161
225
 
162
226
  </button>
227
+ {/* quick zoom shortcuts */}
228
+ <div className="mx-1 h-4 w-px bg-neutral-300 dark:bg-neutral-700" />
229
+ <button
230
+ aria-label="Zoom 100%"
231
+ className="inline-flex h-6 min-w-8 items-center justify-center rounded border border-neutral-300 dark:border-neutral-600 px-1.5 text-[12px]"
232
+ onClick={() => setScale(1)}
233
+ >
234
+ X1
235
+ </button>
236
+ <button
237
+ aria-label="Zoom 200%"
238
+ className="ml-1 inline-flex h-6 min-w-8 items-center justify-center rounded border border-neutral-300 dark:border-neutral-600 px-1.5 text-[12px]"
239
+ onClick={() => setScale(2)}
240
+ >
241
+ X2
242
+ </button>
243
+ <button
244
+ aria-label="Zoom 300%"
245
+ className="ml-1 inline-flex h-6 min-w-8 items-center justify-center rounded border border-neutral-300 dark:border-neutral-600 px-1.5 text-[12px]"
246
+ onClick={() => setScale(3)}
247
+ >
248
+ X3
249
+ </button>
250
+ <button
251
+ aria-label="Zoom 1000%"
252
+ className="ml-1 inline-flex h-6 min-w-10 items-center justify-center rounded border border-neutral-300 dark:border-neutral-600 px-1.5 text-[12px]"
253
+ onClick={() => setScale(10)}
254
+ >
255
+ X10
256
+ </button>
163
257
  <button
164
258
  aria-label="Reset"
165
259
  className="ml-1 flex h-6 w-6 items-center justify-center rounded text-purple-500 hover:text-purple-600"
@@ -170,7 +264,7 @@ export function Mermaid({ chart, title, watermarkEnabled, watermarkText, enableP
170
264
  <button
171
265
  aria-label="Close"
172
266
  className="ml-1 flex h-6 w-6 items-center justify-center rounded text-purple-500 hover:text-purple-600"
173
- onClick={() => setOpen(false)}
267
+ onClick={() => { setOpen(false); resetTransform(); }}
174
268
  >
175
269
  <icons.X className="h-3.5 w-3.5" />
176
270
  </button>
@@ -179,7 +273,7 @@ export function Mermaid({ chart, title, watermarkEnabled, watermarkText, enableP
179
273
 
180
274
  {/* Canvas */}
181
275
  <div
182
- className="relative h-[calc(88vh-40px)] w-full overflow-hidden bg-white dark:bg-neutral-900"
276
+ className="relative h-[calc(88vh-40px)] w-full overflow-hidden bg-white dark:bg-neutral-900 touch-none overscroll-contain"
183
277
  onWheel={onWheel}
184
278
  onPointerDown={onPointerDown}
185
279
  onPointerMove={onPointerMove}
@@ -199,8 +293,8 @@ export function Mermaid({ chart, title, watermarkEnabled, watermarkText, enableP
199
293
  Drag to pan, hold Cmd/Ctrl + scroll to zoom
200
294
  </div>
201
295
  </div>
202
- </AlertDialogContent>
203
- </AlertDialog>
296
+ </div>
297
+ </div>
204
298
  )}
205
299
  </div>
206
300
  );