@file-viewer/renderer-cad 3.0.0 → 3.0.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.
Files changed (2) hide show
  1. package/dist/cad.js +226 -2
  2. package/package.json +2 -2
package/dist/cad.js CHANGED
@@ -5,6 +5,8 @@ const CAD_WORKER_TIMEOUT = 120000;
5
5
  const CAD_DEFAULT_FIT_PADDING = 0.92;
6
6
  const CAD_BOUNDS_EPSILON = 1e-9;
7
7
  const CAD_BEST_FIT_OUTLIER_RATIO = 8;
8
+ const CAD_NATIVE_MIN_ZOOM_RATIO = 0.05;
9
+ const CAD_NATIVE_MAX_ZOOM_RATIO = 64;
8
10
  const cadStyle = `
9
11
  .cad-shell{display:flex;height:100%;min-height:100%;flex-direction:column;background:#f5f7fb;color:#142335}
10
12
  .cad-shell *{box-sizing:border-box}
@@ -198,6 +200,105 @@ const getCadDocumentBaseUrl = (target) => {
198
200
  : 'file:///';
199
201
  };
200
202
  const CAD_DWG_WORKER_FILENAME = 'file-viewer-input.dwg';
203
+ const getCadPinchSnapshot = (points) => {
204
+ const [first, second] = Array.from(points.values());
205
+ if (!first || !second) {
206
+ return null;
207
+ }
208
+ return {
209
+ center: {
210
+ x: (first.x + second.x) / 2,
211
+ y: (first.y + second.y) / 2,
212
+ },
213
+ distance: Math.hypot(second.x - first.x, second.y - first.y),
214
+ };
215
+ };
216
+ /**
217
+ * The bundled CAD engines currently treat every touch pointer as the same
218
+ * drag. Coordinate two touch pointers before their target listeners run so a
219
+ * pinch becomes one anchored zoom operation instead of two competing pans.
220
+ */
221
+ const registerCadPinchZoom = (surface, onStart, onZoom) => {
222
+ const points = new Map();
223
+ let previous = null;
224
+ let suppressDragUntilRelease = false;
225
+ const suppressPointerEvent = (event) => {
226
+ event.preventDefault();
227
+ event.stopImmediatePropagation();
228
+ };
229
+ const handlePointerDown = (event) => {
230
+ if (event.pointerType !== 'touch') {
231
+ return;
232
+ }
233
+ points.set(event.pointerId, { x: event.clientX, y: event.clientY });
234
+ try {
235
+ surface.setPointerCapture(event.pointerId);
236
+ }
237
+ catch {
238
+ // Synthetic browser harness events may not create an active pointer.
239
+ }
240
+ if (points.size < 2) {
241
+ return;
242
+ }
243
+ suppressDragUntilRelease = true;
244
+ previous = getCadPinchSnapshot(points);
245
+ onStart();
246
+ suppressPointerEvent(event);
247
+ };
248
+ const handlePointerMove = (event) => {
249
+ if (event.pointerType !== 'touch' || !points.has(event.pointerId)) {
250
+ return;
251
+ }
252
+ points.set(event.pointerId, { x: event.clientX, y: event.clientY });
253
+ if (!suppressDragUntilRelease) {
254
+ return;
255
+ }
256
+ if (points.size >= 2) {
257
+ const current = getCadPinchSnapshot(points);
258
+ if (previous && current && previous.distance > 0 && current.distance > 0) {
259
+ onZoom({
260
+ center: current.center,
261
+ deltaX: current.center.x - previous.center.x,
262
+ deltaY: current.center.y - previous.center.y,
263
+ factor: Math.min(2, Math.max(0.5, current.distance / previous.distance)),
264
+ });
265
+ }
266
+ previous = current;
267
+ }
268
+ suppressPointerEvent(event);
269
+ };
270
+ const handlePointerEnd = (event) => {
271
+ if (event.pointerType !== 'touch' || !points.has(event.pointerId)) {
272
+ return;
273
+ }
274
+ points.delete(event.pointerId);
275
+ previous = points.size >= 2 ? getCadPinchSnapshot(points) : null;
276
+ if (points.size === 0) {
277
+ suppressDragUntilRelease = false;
278
+ }
279
+ try {
280
+ if (surface.hasPointerCapture(event.pointerId)) {
281
+ surface.releasePointerCapture(event.pointerId);
282
+ }
283
+ }
284
+ catch {
285
+ // The underlying engine may already have released this pointer.
286
+ }
287
+ // Do not stop pointerup/cancel: the engine must clear the single-pointer
288
+ // drag that began before the second finger started the pinch.
289
+ };
290
+ surface.addEventListener('pointerdown', handlePointerDown, { capture: true });
291
+ surface.addEventListener('pointermove', handlePointerMove, { capture: true });
292
+ surface.addEventListener('pointerup', handlePointerEnd, { capture: true });
293
+ surface.addEventListener('pointercancel', handlePointerEnd, { capture: true });
294
+ return () => {
295
+ surface.removeEventListener('pointerdown', handlePointerDown, { capture: true });
296
+ surface.removeEventListener('pointermove', handlePointerMove, { capture: true });
297
+ surface.removeEventListener('pointerup', handlePointerEnd, { capture: true });
298
+ surface.removeEventListener('pointercancel', handlePointerEnd, { capture: true });
299
+ points.clear();
300
+ };
301
+ };
201
302
  const createEmptyCadBounds = () => ({
202
303
  minX: Number.POSITIVE_INFINITY,
203
304
  minY: Number.POSITIVE_INFINITY,
@@ -557,7 +658,10 @@ export default async function renderCad(buffer, target, type = 'dxf', context) {
557
658
  let viewer = null;
558
659
  let resizeObserver = null;
559
660
  let abortController = null;
661
+ let disposeCadPinch = null;
662
+ let disposeNativeInteractions = null;
560
663
  let fitViewActive = true;
664
+ let nativeZoomRatio = 1;
561
665
  let disposed = false;
562
666
  const style = createStyle();
563
667
  const shell = createElement('div', 'cad-shell');
@@ -615,8 +719,11 @@ export default async function renderCad(buffer, target, type = 'dxf', context) {
615
719
  };
616
720
  const getWarnings = () => (loadResult === null || loadResult === void 0 ? void 0 : loadResult.warnings) || (loadResult === null || loadResult === void 0 ? void 0 : loadResult.document.warnings) || [];
617
721
  const getZoomPercent = () => {
618
- var _a, _b, _c;
619
- const zoom = (_c = (_a = viewState === null || viewState === void 0 ? void 0 : viewState.zoomPercent) !== null && _a !== void 0 ? _a : (_b = viewer === null || viewer === void 0 ? void 0 : viewer.getZoomPercent) === null || _b === void 0 ? void 0 : _b.call(viewer)) !== null && _c !== void 0 ? _c : 100;
722
+ var _a, _b, _c, _d;
723
+ if ((_a = viewer === null || viewer === void 0 ? void 0 : viewer.isNativeRendererActive) === null || _a === void 0 ? void 0 : _a.call(viewer)) {
724
+ return Math.round(nativeZoomRatio * 100);
725
+ }
726
+ const zoom = (_d = (_b = viewState === null || viewState === void 0 ? void 0 : viewState.zoomPercent) !== null && _b !== void 0 ? _b : (_c = viewer === null || viewer === void 0 ? void 0 : viewer.getZoomPercent) === null || _c === void 0 ? void 0 : _c.call(viewer)) !== null && _d !== void 0 ? _d : 100;
620
727
  return Number.isFinite(zoom) ? Math.round(zoom) : 100;
621
728
  };
622
729
  const getCadZoomState = () => {
@@ -655,6 +762,98 @@ export default async function renderCad(buffer, target, type = 'dxf', context) {
655
762
  }
656
763
  return state;
657
764
  };
765
+ const clampNativeZoomRatio = (ratio) => {
766
+ return Math.min(CAD_NATIVE_MAX_ZOOM_RATIO, Math.max(CAD_NATIVE_MIN_ZOOM_RATIO, ratio));
767
+ };
768
+ const notifyNativeZoomChange = (action) => {
769
+ cadZoomEmitter.emit();
770
+ syncState();
771
+ emitCadViewStateChange(action, 'user');
772
+ };
773
+ const bindNativeCadInteractions = () => {
774
+ disposeNativeInteractions === null || disposeNativeInteractions === void 0 ? void 0 : disposeNativeInteractions();
775
+ disposeNativeInteractions = null;
776
+ const surface = nativeHost.querySelector('.dwfv-overlay-canvas');
777
+ if (!surface) {
778
+ return;
779
+ }
780
+ let notificationQueued = false;
781
+ const queueNativeZoomChange = (action) => {
782
+ if (notificationQueued) {
783
+ return;
784
+ }
785
+ notificationQueued = true;
786
+ queueMicrotask(() => {
787
+ var _a;
788
+ notificationQueued = false;
789
+ if (!disposed && ((_a = viewer === null || viewer === void 0 ? void 0 : viewer.isNativeRendererActive) === null || _a === void 0 ? void 0 : _a.call(viewer))) {
790
+ notifyNativeZoomChange(action);
791
+ }
792
+ });
793
+ };
794
+ const applyNativeZoomFactor = (factor, action) => {
795
+ if (!Number.isFinite(factor) || factor <= 0) {
796
+ return;
797
+ }
798
+ nativeZoomRatio = clampNativeZoomRatio(nativeZoomRatio * factor);
799
+ queueNativeZoomChange(action);
800
+ };
801
+ const handleNativeClick = (event) => {
802
+ const eventTarget = event.target;
803
+ const button = eventTarget instanceof Element ? eventTarget.closest('button') : null;
804
+ if (!button || !nativeHost.contains(button)) {
805
+ return;
806
+ }
807
+ const label = (button.textContent || '').trim();
808
+ if (label === '+') {
809
+ applyNativeZoomFactor(1.25, 'zoom-in');
810
+ }
811
+ else if (label === '-' || label === '−') {
812
+ applyNativeZoomFactor(0.8, 'zoom-out');
813
+ }
814
+ else if (/适应|fit|reset/i.test(label)) {
815
+ nativeZoomRatio = 1;
816
+ queueNativeZoomChange('zoom-reset');
817
+ }
818
+ };
819
+ const handleNativeWheel = (event) => {
820
+ const rect = surface.getBoundingClientRect();
821
+ const delta = event.deltaMode === WheelEvent.DOM_DELTA_LINE
822
+ ? event.deltaY * 16
823
+ : event.deltaMode === WheelEvent.DOM_DELTA_PAGE
824
+ ? event.deltaY * Math.max(1, rect.height)
825
+ : event.deltaY;
826
+ applyNativeZoomFactor(Math.exp(-delta * 0.0015), 'cad-view-change');
827
+ };
828
+ const handleNativePageChange = (event) => {
829
+ if (event.target instanceof HTMLSelectElement && nativeHost.contains(event.target)) {
830
+ nativeZoomRatio = 1;
831
+ queueNativeZoomChange('zoom-reset');
832
+ }
833
+ };
834
+ const disposePinch = registerCadPinchZoom(surface, () => {
835
+ fitViewActive = false;
836
+ }, ({ center, factor }) => {
837
+ const deltaY = -Math.log(factor) / 0.0015;
838
+ surface.dispatchEvent(new WheelEvent('wheel', {
839
+ bubbles: true,
840
+ cancelable: true,
841
+ clientX: center.x,
842
+ clientY: center.y,
843
+ deltaMode: WheelEvent.DOM_DELTA_PIXEL,
844
+ deltaY,
845
+ }));
846
+ });
847
+ nativeHost.addEventListener('click', handleNativeClick);
848
+ nativeHost.addEventListener('change', handleNativePageChange);
849
+ surface.addEventListener('wheel', handleNativeWheel);
850
+ disposeNativeInteractions = () => {
851
+ disposePinch();
852
+ nativeHost.removeEventListener('click', handleNativeClick);
853
+ nativeHost.removeEventListener('change', handleNativePageChange);
854
+ surface.removeEventListener('wheel', handleNativeWheel);
855
+ };
856
+ };
658
857
  const syncInspector = () => {
659
858
  const summary = loadResult === null || loadResult === void 0 ? void 0 : loadResult.summary;
660
859
  const rows = [
@@ -744,6 +943,7 @@ export default async function renderCad(buffer, target, type = 'dxf', context) {
744
943
  return false;
745
944
  }
746
945
  if ((_a = viewer.isNativeRendererActive) === null || _a === void 0 ? void 0 : _a.call(viewer)) {
946
+ nativeZoomRatio = 1;
747
947
  viewer.fit();
748
948
  return true;
749
949
  }
@@ -918,6 +1118,19 @@ export default async function renderCad(buffer, target, type = 'dxf', context) {
918
1118
  syncState();
919
1119
  },
920
1120
  });
1121
+ disposeCadPinch === null || disposeCadPinch === void 0 ? void 0 : disposeCadPinch();
1122
+ const renderer = nextViewer.renderer;
1123
+ disposeCadPinch = registerCadPinchZoom(nextViewer.canvas, () => {
1124
+ fitViewActive = false;
1125
+ }, ({ center, deltaX, deltaY, factor }) => {
1126
+ var _a, _b;
1127
+ const rect = nextViewer.canvas.getBoundingClientRect();
1128
+ (_a = renderer.panByScreenDelta) === null || _a === void 0 ? void 0 : _a.call(renderer, deltaX, deltaY);
1129
+ (_b = renderer.zoom) === null || _b === void 0 ? void 0 : _b.call(renderer, factor, {
1130
+ x: center.x - rect.left,
1131
+ y: center.y - rect.top,
1132
+ });
1133
+ });
921
1134
  if (options.preloadDwg !== false && normalizedType === 'dwg') {
922
1135
  void nextViewer.preloadDwg({ wasmPath, workerUrl }).catch(() => {
923
1136
  // 预热失败不阻断真实加载,loadBuffer 会返回完整错误上下文。
@@ -926,6 +1139,7 @@ export default async function renderCad(buffer, target, type = 'dxf', context) {
926
1139
  return nextViewer;
927
1140
  };
928
1141
  const loadCad = async () => {
1142
+ var _a;
929
1143
  status = 'loading';
930
1144
  progressMessage = t('cad.state.parsing');
931
1145
  errorMessage = '';
@@ -934,6 +1148,9 @@ export default async function renderCad(buffer, target, type = 'dxf', context) {
934
1148
  viewState = null;
935
1149
  layers = [];
936
1150
  fitViewActive = true;
1151
+ nativeZoomRatio = 1;
1152
+ disposeNativeInteractions === null || disposeNativeInteractions === void 0 ? void 0 : disposeNativeInteractions();
1153
+ disposeNativeInteractions = null;
937
1154
  syncUi();
938
1155
  abortController === null || abortController === void 0 ? void 0 : abortController.abort();
939
1156
  const controller = new AbortController();
@@ -956,6 +1173,9 @@ export default async function renderCad(buffer, target, type = 'dxf', context) {
956
1173
  loadResult = restoreLoadResultSourceName(result);
957
1174
  layers = collectLayers(result);
958
1175
  status = 'ready';
1176
+ if ((_a = viewer.isNativeRendererActive) === null || _a === void 0 ? void 0 : _a.call(viewer)) {
1177
+ bindNativeCadInteractions();
1178
+ }
959
1179
  syncUi();
960
1180
  queueMicrotask(() => {
961
1181
  applyCadFit();
@@ -999,6 +1219,10 @@ export default async function renderCad(buffer, target, type = 'dxf', context) {
999
1219
  unregisterFileViewerZoomProvider(shell);
1000
1220
  abortController === null || abortController === void 0 ? void 0 : abortController.abort();
1001
1221
  abortController = null;
1222
+ disposeCadPinch === null || disposeCadPinch === void 0 ? void 0 : disposeCadPinch();
1223
+ disposeCadPinch = null;
1224
+ disposeNativeInteractions === null || disposeNativeInteractions === void 0 ? void 0 : disposeNativeInteractions();
1225
+ disposeNativeInteractions = null;
1002
1226
  resizeObserver === null || resizeObserver === void 0 ? void 0 : resizeObserver.disconnect();
1003
1227
  resizeObserver = null;
1004
1228
  viewer === null || viewer === void 0 ? void 0 : viewer.destroy();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@file-viewer/renderer-cad",
3
- "version": "3.0.0",
3
+ "version": "3.0.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Standalone CAD renderer plugin for File Viewer powered by @flyfish-dev/cad-viewer.",
@@ -56,7 +56,7 @@
56
56
  "LICENSE"
57
57
  ],
58
58
  "dependencies": {
59
- "@file-viewer/core": "3.0.0",
59
+ "@file-viewer/core": "3.0.1",
60
60
  "@flyfish-dev/cad-viewer": "0.8.0"
61
61
  },
62
62
  "devDependencies": {