@cratis/components 4.21.0 → 4.22.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/MIGRATION.md +3 -3
- package/compat-manifest.json +16 -16
- package/dist/esm/Canvas/Canvas.d.ts +12 -81
- package/dist/esm/Canvas/Canvas.d.ts.map +1 -1
- package/dist/esm/Canvas/Canvas.js +83 -676
- package/dist/esm/Canvas/Canvas.js.map +1 -1
- package/dist/esm/Canvas/CanvasCaptureAttributes.d.ts +10 -0
- package/dist/esm/Canvas/CanvasCaptureAttributes.d.ts.map +1 -0
- package/dist/esm/Canvas/CanvasCaptureAttributes.js +4 -0
- package/dist/esm/Canvas/CanvasCaptureAttributes.js.map +1 -0
- package/dist/esm/Canvas/CanvasContext.d.ts +9 -0
- package/dist/esm/Canvas/CanvasContext.d.ts.map +1 -0
- package/dist/esm/Canvas/CanvasContext.js +4 -0
- package/dist/esm/Canvas/CanvasContext.js.map +1 -0
- package/dist/esm/Canvas/CanvasHandle.d.ts +13 -0
- package/dist/esm/Canvas/CanvasHandle.d.ts.map +1 -0
- package/dist/esm/Canvas/CanvasHandle.js +4 -0
- package/dist/esm/Canvas/CanvasHandle.js.map +1 -0
- package/dist/esm/Canvas/CanvasItem.js +4 -1
- package/dist/esm/Canvas/CanvasItem.js.map +1 -1
- package/dist/esm/Canvas/CanvasItemData.d.ts +10 -0
- package/dist/esm/Canvas/CanvasItemData.d.ts.map +1 -0
- package/dist/esm/Canvas/CanvasItemData.js +4 -0
- package/dist/esm/Canvas/CanvasItemData.js.map +1 -0
- package/dist/esm/Canvas/CanvasItemRegistryContext.d.ts +5 -0
- package/dist/esm/Canvas/CanvasItemRegistryContext.d.ts.map +1 -0
- package/dist/esm/Canvas/CanvasItemRegistryContext.js +6 -0
- package/dist/esm/Canvas/CanvasItemRegistryContext.js.map +1 -0
- package/dist/esm/Canvas/CanvasItemRegistryContextValue.d.ts +26 -0
- package/dist/esm/Canvas/CanvasItemRegistryContextValue.d.ts.map +1 -0
- package/dist/esm/Canvas/CanvasItemRegistryContextValue.js +4 -0
- package/dist/esm/Canvas/CanvasItemRegistryContextValue.js.map +1 -0
- package/dist/esm/Canvas/CanvasItemRegistryEntry.d.ts +19 -0
- package/dist/esm/Canvas/CanvasItemRegistryEntry.d.ts.map +1 -0
- package/dist/esm/Canvas/CanvasItemRegistryEntry.js +4 -0
- package/dist/esm/Canvas/CanvasItemRegistryEntry.js.map +1 -0
- package/dist/esm/Canvas/WebKitGestureEvent.d.ts +13 -0
- package/dist/esm/Canvas/WebKitGestureEvent.d.ts.map +1 -0
- package/dist/esm/Canvas/WebKitGestureEvent.js +4 -0
- package/dist/esm/Canvas/WebKitGestureEvent.js.map +1 -0
- package/dist/esm/Canvas/canvasEasing.d.ts +8 -0
- package/dist/esm/Canvas/canvasEasing.d.ts.map +1 -0
- package/dist/esm/Canvas/canvasEasing.js +8 -0
- package/dist/esm/Canvas/canvasEasing.js.map +1 -0
- package/dist/esm/Canvas/canvasGestureTuning.d.ts +6 -0
- package/dist/esm/Canvas/canvasGestureTuning.d.ts.map +1 -0
- package/dist/esm/Canvas/canvasGestureTuning.js +7 -0
- package/dist/esm/Canvas/canvasGestureTuning.js.map +1 -0
- package/dist/esm/Canvas/index.js +2 -1
- package/dist/esm/Canvas/index.js.map +1 -1
- package/dist/esm/Canvas/shapes/Region/Region.js +4 -1
- package/dist/esm/Canvas/shapes/Region/Region.js.map +1 -1
- package/dist/esm/Canvas/useCanvasCamera.d.ts +37 -0
- package/dist/esm/Canvas/useCanvasCamera.d.ts.map +1 -0
- package/dist/esm/Canvas/useCanvasCamera.js +79 -0
- package/dist/esm/Canvas/useCanvasCamera.js.map +1 -0
- package/dist/esm/Canvas/useCanvasItemRegistry.d.ts +14 -0
- package/dist/esm/Canvas/useCanvasItemRegistry.d.ts.map +1 -0
- package/dist/esm/Canvas/useCanvasItemRegistry.js +59 -0
- package/dist/esm/Canvas/useCanvasItemRegistry.js.map +1 -0
- package/dist/esm/Canvas/useCanvasNavigation.d.ts +36 -0
- package/dist/esm/Canvas/useCanvasNavigation.d.ts.map +1 -0
- package/dist/esm/Canvas/useCanvasNavigation.js +186 -0
- package/dist/esm/Canvas/useCanvasNavigation.js.map +1 -0
- package/dist/esm/Canvas/useCanvasPointerGestures.d.ts +34 -0
- package/dist/esm/Canvas/useCanvasPointerGestures.d.ts.map +1 -0
- package/dist/esm/Canvas/useCanvasPointerGestures.js +178 -0
- package/dist/esm/Canvas/useCanvasPointerGestures.js.map +1 -0
- package/dist/esm/Canvas/useCanvasWheelZoom.d.ts +23 -0
- package/dist/esm/Canvas/useCanvasWheelZoom.d.ts.map +1 -0
- package/dist/esm/Canvas/useCanvasWheelZoom.js +146 -0
- package/dist/esm/Canvas/useCanvasWheelZoom.js.map +1 -0
- package/dist/esm/Canvas/useLatestRef.d.ts +8 -0
- package/dist/esm/Canvas/useLatestRef.d.ts.map +1 -0
- package/dist/esm/Canvas/useLatestRef.js +12 -0
- package/dist/esm/Canvas/useLatestRef.js.map +1 -0
- package/dist/esm/Canvas/usePixiApplication.d.ts +32 -0
- package/dist/esm/Canvas/usePixiApplication.d.ts.map +1 -0
- package/dist/esm/Canvas/usePixiApplication.js +92 -0
- package/dist/esm/Canvas/usePixiApplication.js.map +1 -0
- package/dist/esm/Canvas/usePixiItems.d.ts +19 -0
- package/dist/esm/Canvas/usePixiItems.d.ts.map +1 -0
- package/dist/esm/Canvas/usePixiItems.js +49 -0
- package/dist/esm/Canvas/usePixiItems.js.map +1 -0
- package/dist/esm/Chat/ChatSidebar.d.ts.map +1 -1
- package/dist/esm/Chat/ChatSidebar.js +1 -1
- package/dist/esm/Chat/ChatSidebar.js.map +1 -1
- package/dist/esm/ClassNames/classNames.d.ts +7 -0
- package/dist/esm/ClassNames/classNames.d.ts.map +1 -0
- package/dist/esm/ClassNames/classNames.js +4 -0
- package/dist/esm/ClassNames/classNames.js.map +1 -0
- package/dist/esm/Common/Breadcrumbs.d.ts.map +1 -1
- package/dist/esm/Common/Breadcrumbs.js +1 -1
- package/dist/esm/Common/Breadcrumbs.js.map +1 -1
- package/dist/esm/Common/ComboBox.d.ts.map +1 -1
- package/dist/esm/Common/ComboBox.js +1 -1
- package/dist/esm/Common/ComboBox.js.map +1 -1
- package/dist/esm/Common/DatePickerInputImplementation.d.ts.map +1 -1
- package/dist/esm/Common/DatePickerInputImplementation.js +1 -1
- package/dist/esm/Common/DatePickerInputImplementation.js.map +1 -1
- package/dist/esm/Common/NumberInput.d.ts.map +1 -1
- package/dist/esm/Common/NumberInput.js +1 -1
- package/dist/esm/Common/NumberInput.js.map +1 -1
- package/dist/esm/Common/Tabs.d.ts.map +1 -1
- package/dist/esm/Common/Tabs.js +1 -1
- package/dist/esm/Common/Tabs.js.map +1 -1
- package/dist/esm/Common/TagGroup.d.ts.map +1 -1
- package/dist/esm/Common/TagGroup.js +1 -1
- package/dist/esm/Common/TagGroup.js.map +1 -1
- package/dist/esm/Common/ToggleGroup.d.ts.map +1 -1
- package/dist/esm/Common/ToggleGroup.js +1 -1
- package/dist/esm/Common/ToggleGroup.js.map +1 -1
- package/dist/esm/DataTables/ColumnFilterMenu.d.ts.map +1 -1
- package/dist/esm/DataTables/ColumnFilterMenu.js +1 -1
- package/dist/esm/DataTables/ColumnFilterMenu.js.map +1 -1
- package/dist/esm/DataTables/DataTableBody.d.ts +22 -0
- package/dist/esm/DataTables/DataTableBody.d.ts.map +1 -0
- package/dist/esm/DataTables/DataTableBody.js +10 -0
- package/dist/esm/DataTables/DataTableBody.js.map +1 -0
- package/dist/esm/DataTables/DataTableCore.d.ts +8 -48
- package/dist/esm/DataTables/DataTableCore.d.ts.map +1 -1
- package/dist/esm/DataTables/DataTableCore.js +50 -278
- package/dist/esm/DataTables/DataTableCore.js.map +1 -1
- package/dist/esm/DataTables/DataTableHeaderCell.d.ts +25 -0
- package/dist/esm/DataTables/DataTableHeaderCell.d.ts.map +1 -0
- package/dist/esm/DataTables/DataTableHeaderCell.js +26 -0
- package/dist/esm/DataTables/DataTableHeaderCell.js.map +1 -0
- package/dist/esm/DataTables/DataTableMessageOverrides.d.ts +12 -0
- package/dist/esm/DataTables/DataTableMessageOverrides.d.ts.map +1 -0
- package/dist/esm/DataTables/DataTableMessageOverrides.js +4 -0
- package/dist/esm/DataTables/DataTableMessageOverrides.js.map +1 -0
- package/dist/esm/DataTables/DataTableMessages.d.ts +12 -0
- package/dist/esm/DataTables/DataTableMessages.d.ts.map +1 -0
- package/dist/esm/DataTables/DataTableMessages.js +4 -0
- package/dist/esm/DataTables/DataTableMessages.js.map +1 -0
- package/dist/esm/DataTables/DataTableParts.d.ts +40 -0
- package/dist/esm/DataTables/DataTableParts.d.ts.map +1 -0
- package/dist/esm/DataTables/DataTableParts.js +4 -0
- package/dist/esm/DataTables/DataTableParts.js.map +1 -0
- package/dist/esm/DataTables/DataTableRow.d.ts +22 -0
- package/dist/esm/DataTables/DataTableRow.d.ts.map +1 -0
- package/dist/esm/DataTables/DataTableRow.js +25 -0
- package/dist/esm/DataTables/DataTableRow.js.map +1 -0
- package/dist/esm/DataTables/DataTableRowClickEvent.d.ts +8 -0
- package/dist/esm/DataTables/DataTableRowClickEvent.d.ts.map +1 -0
- package/dist/esm/DataTables/DataTableRowClickEvent.js +4 -0
- package/dist/esm/DataTables/DataTableRowClickEvent.js.map +1 -0
- package/dist/esm/DataTables/DataTableSearch.d.ts +13 -0
- package/dist/esm/DataTables/DataTableSearch.d.ts.map +1 -0
- package/dist/esm/DataTables/DataTableSearch.js +7 -0
- package/dist/esm/DataTables/DataTableSearch.js.map +1 -0
- package/dist/esm/DataTables/DataTableState.d.ts +18 -0
- package/dist/esm/DataTables/DataTableState.d.ts.map +1 -0
- package/dist/esm/DataTables/DataTableState.js +4 -0
- package/dist/esm/DataTables/DataTableState.js.map +1 -0
- package/dist/esm/DataTables/DataTableStateProps.d.ts +20 -0
- package/dist/esm/DataTables/DataTableStateProps.d.ts.map +1 -0
- package/dist/esm/DataTables/DataTableStateProps.js +4 -0
- package/dist/esm/DataTables/DataTableStateProps.js.map +1 -0
- package/dist/esm/DataTables/ProcessedRow.d.ts +8 -0
- package/dist/esm/DataTables/ProcessedRow.d.ts.map +1 -0
- package/dist/esm/DataTables/ProcessedRow.js +4 -0
- package/dist/esm/DataTables/ProcessedRow.js.map +1 -0
- package/dist/esm/DataTables/RowProcessingState.d.ts +17 -0
- package/dist/esm/DataTables/RowProcessingState.d.ts.map +1 -0
- package/dist/esm/DataTables/RowProcessingState.js +4 -0
- package/dist/esm/DataTables/RowProcessingState.js.map +1 -0
- package/dist/esm/DataTables/RowSelection.d.ts +19 -0
- package/dist/esm/DataTables/RowSelection.d.ts.map +1 -0
- package/dist/esm/DataTables/RowSelection.js +4 -0
- package/dist/esm/DataTables/RowSelection.js.map +1 -0
- package/dist/esm/DataTables/RowSelectionSource.d.ts +16 -0
- package/dist/esm/DataTables/RowSelectionSource.d.ts.map +1 -0
- package/dist/esm/DataTables/RowSelectionSource.js +4 -0
- package/dist/esm/DataTables/RowSelectionSource.js.map +1 -0
- package/dist/esm/DataTables/matchesFilterEntry.d.ts +15 -0
- package/dist/esm/DataTables/matchesFilterEntry.d.ts.map +1 -0
- package/dist/esm/DataTables/matchesFilterEntry.js +70 -0
- package/dist/esm/DataTables/matchesFilterEntry.js.map +1 -0
- package/dist/esm/DataTables/processRows.d.ts +10 -0
- package/dist/esm/DataTables/processRows.d.ts.map +1 -0
- package/dist/esm/DataTables/processRows.js +42 -0
- package/dist/esm/DataTables/processRows.js.map +1 -0
- package/dist/esm/DataTables/useDataTableMessages.d.ts +9 -0
- package/dist/esm/DataTables/useDataTableMessages.d.ts.map +1 -0
- package/dist/esm/DataTables/useDataTableMessages.js +33 -0
- package/dist/esm/DataTables/useDataTableMessages.js.map +1 -0
- package/dist/esm/DataTables/useDataTableState.d.ts +10 -0
- package/dist/esm/DataTables/useDataTableState.d.ts.map +1 -0
- package/dist/esm/DataTables/useDataTableState.js +19 -0
- package/dist/esm/DataTables/useDataTableState.js.map +1 -0
- package/dist/esm/DataTables/useRowSelection.d.ts +9 -0
- package/dist/esm/DataTables/useRowSelection.d.ts.map +1 -0
- package/dist/esm/DataTables/useRowSelection.js +87 -0
- package/dist/esm/DataTables/useRowSelection.js.map +1 -0
- package/dist/esm/DataTables/valueAtPath.d.ts +10 -0
- package/dist/esm/DataTables/valueAtPath.d.ts.map +1 -0
- package/dist/esm/DataTables/valueAtPath.js +18 -0
- package/dist/esm/DataTables/valueAtPath.js.map +1 -0
- package/dist/esm/Dialogs/DialogImplementation.d.ts.map +1 -1
- package/dist/esm/Dialogs/DialogImplementation.js +1 -1
- package/dist/esm/Dialogs/DialogImplementation.js.map +1 -1
- package/dist/esm/Display/Message.d.ts +8 -1
- package/dist/esm/Display/Message.d.ts.map +1 -1
- package/dist/esm/Display/Message.js +1 -1
- package/dist/esm/Display/Message.js.map +1 -1
- package/dist/esm/Dropdown/DropdownImplementation.d.ts.map +1 -1
- package/dist/esm/Dropdown/DropdownImplementation.js +1 -1
- package/dist/esm/Dropdown/DropdownImplementation.js.map +1 -1
- package/dist/esm/Notifications/Toaster.d.ts.map +1 -1
- package/dist/esm/Notifications/Toaster.js +1 -1
- package/dist/esm/Notifications/Toaster.js.map +1 -1
- package/dist/esm/PivotViewer/components/PivotCanvas.d.ts +7 -50
- package/dist/esm/PivotViewer/components/PivotCanvas.d.ts.map +1 -1
- package/dist/esm/PivotViewer/components/PivotCanvas.js +34 -433
- package/dist/esm/PivotViewer/components/PivotCanvas.js.map +1 -1
- package/dist/esm/PivotViewer/components/PivotCanvasProps.d.ts +51 -0
- package/dist/esm/PivotViewer/components/PivotCanvasProps.d.ts.map +1 -0
- package/dist/esm/PivotViewer/components/PivotCanvasProps.js +4 -0
- package/dist/esm/PivotViewer/components/PivotCanvasProps.js.map +1 -0
- package/dist/esm/PivotViewer/components/pivot/PivotRenderContext.d.ts +16 -0
- package/dist/esm/PivotViewer/components/pivot/PivotRenderContext.d.ts.map +1 -0
- package/dist/esm/PivotViewer/components/pivot/PivotRenderContext.js +4 -0
- package/dist/esm/PivotViewer/components/pivot/PivotRenderContext.js.map +1 -0
- package/dist/esm/PivotViewer/components/pivot/PivotStage.d.ts +19 -0
- package/dist/esm/PivotViewer/components/pivot/PivotStage.d.ts.map +1 -0
- package/dist/esm/PivotViewer/components/pivot/PivotStage.js +4 -0
- package/dist/esm/PivotViewer/components/pivot/PivotStage.js.map +1 -0
- package/dist/esm/PivotViewer/components/pivot/PivotTransitionState.d.ts +21 -0
- package/dist/esm/PivotViewer/components/pivot/PivotTransitionState.d.ts.map +1 -0
- package/dist/esm/PivotViewer/components/pivot/PivotTransitionState.js +4 -0
- package/dist/esm/PivotViewer/components/pivot/PivotTransitionState.js.map +1 -0
- package/dist/esm/PivotViewer/components/pivot/asReactMouseEvent.d.ts +8 -0
- package/dist/esm/PivotViewer/components/pivot/asReactMouseEvent.d.ts.map +1 -0
- package/dist/esm/PivotViewer/components/pivot/asReactMouseEvent.js +6 -0
- package/dist/esm/PivotViewer/components/pivot/asReactMouseEvent.js.map +1 -0
- package/dist/esm/PivotViewer/components/pivot/sprites.js +1 -1
- package/dist/esm/PivotViewer/components/pivot/usePivotCardColors.d.ts +22 -0
- package/dist/esm/PivotViewer/components/pivot/usePivotCardColors.d.ts.map +1 -0
- package/dist/esm/PivotViewer/components/pivot/usePivotCardColors.js +25 -0
- package/dist/esm/PivotViewer/components/pivot/usePivotCardColors.js.map +1 -0
- package/dist/esm/PivotViewer/components/pivot/usePivotGroupBackgrounds.d.ts +8 -0
- package/dist/esm/PivotViewer/components/pivot/usePivotGroupBackgrounds.d.ts.map +1 -0
- package/dist/esm/PivotViewer/components/pivot/usePivotGroupBackgrounds.js +53 -0
- package/dist/esm/PivotViewer/components/pivot/usePivotGroupBackgrounds.js.map +1 -0
- package/dist/esm/PivotViewer/components/pivot/usePivotScrollSync.d.ts +8 -0
- package/dist/esm/PivotViewer/components/pivot/usePivotScrollSync.d.ts.map +1 -0
- package/dist/esm/PivotViewer/components/pivot/usePivotScrollSync.js +100 -0
- package/dist/esm/PivotViewer/components/pivot/usePivotScrollSync.js.map +1 -0
- package/dist/esm/PivotViewer/components/pivot/usePivotSelectionHighlight.d.ts +7 -0
- package/dist/esm/PivotViewer/components/pivot/usePivotSelectionHighlight.d.ts.map +1 -0
- package/dist/esm/PivotViewer/components/pivot/usePivotSelectionHighlight.js +35 -0
- package/dist/esm/PivotViewer/components/pivot/usePivotSelectionHighlight.js.map +1 -0
- package/dist/esm/PivotViewer/components/pivot/usePivotSpriteSync.d.ts +8 -0
- package/dist/esm/PivotViewer/components/pivot/usePivotSpriteSync.d.ts.map +1 -0
- package/dist/esm/PivotViewer/components/pivot/usePivotSpriteSync.js +123 -0
- package/dist/esm/PivotViewer/components/pivot/usePivotSpriteSync.js.map +1 -0
- package/dist/esm/PivotViewer/components/pivot/usePivotStage.d.ts +21 -0
- package/dist/esm/PivotViewer/components/pivot/usePivotStage.d.ts.map +1 -0
- package/dist/esm/PivotViewer/components/pivot/usePivotStage.js +171 -0
- package/dist/esm/PivotViewer/components/pivot/usePivotStage.js.map +1 -0
- package/dist/esm/PivotViewer/components/pivot/usePivotTransitionState.d.ts +13 -0
- package/dist/esm/PivotViewer/components/pivot/usePivotTransitionState.d.ts.map +1 -0
- package/dist/esm/PivotViewer/components/pivot/usePivotTransitionState.js +31 -0
- package/dist/esm/PivotViewer/components/pivot/usePivotTransitionState.js.map +1 -0
- package/dist/esm/TimeMachine/EventsView.d.ts.map +1 -1
- package/dist/esm/TimeMachine/EventsView.js +1 -1
- package/dist/esm/TimeMachine/EventsView.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { useCallback, useEffect } from 'react';
|
|
2
|
+
import { easeInOutCubic } from './canvasEasing.js';
|
|
3
|
+
|
|
4
|
+
const useCanvasNavigation = ({ containerRef, animationFrameRef, panRef, zoomRef, itemRegistryRef, minZoom, maxZoom, pixiReady, onHandleReadyRef, applyWorldTransform, refreshMinimap, render, }) => {
|
|
5
|
+
const handleZoomIn = useCallback(() => {
|
|
6
|
+
const container = containerRef.current;
|
|
7
|
+
if (!container)
|
|
8
|
+
return;
|
|
9
|
+
const cx = container.clientWidth / 2;
|
|
10
|
+
const cy = container.clientHeight / 2;
|
|
11
|
+
const factor = 1.2;
|
|
12
|
+
const newZoom = Math.min(maxZoom, zoomRef.current * factor);
|
|
13
|
+
const worldX = (cx - panRef.current.x) / zoomRef.current;
|
|
14
|
+
const worldY = (cy - panRef.current.y) / zoomRef.current;
|
|
15
|
+
panRef.current = { x: cx - worldX * newZoom, y: cy - worldY * newZoom };
|
|
16
|
+
zoomRef.current = newZoom;
|
|
17
|
+
applyWorldTransform();
|
|
18
|
+
refreshMinimap();
|
|
19
|
+
render();
|
|
20
|
+
}, [
|
|
21
|
+
maxZoom,
|
|
22
|
+
applyWorldTransform,
|
|
23
|
+
refreshMinimap,
|
|
24
|
+
render,
|
|
25
|
+
containerRef,
|
|
26
|
+
panRef,
|
|
27
|
+
zoomRef,
|
|
28
|
+
]);
|
|
29
|
+
const handleZoomOut = useCallback(() => {
|
|
30
|
+
const container = containerRef.current;
|
|
31
|
+
if (!container)
|
|
32
|
+
return;
|
|
33
|
+
const cx = container.clientWidth / 2;
|
|
34
|
+
const cy = container.clientHeight / 2;
|
|
35
|
+
const factor = 1.2;
|
|
36
|
+
const newZoom = Math.max(minZoom, zoomRef.current / factor);
|
|
37
|
+
const worldX = (cx - panRef.current.x) / zoomRef.current;
|
|
38
|
+
const worldY = (cy - panRef.current.y) / zoomRef.current;
|
|
39
|
+
panRef.current = { x: cx - worldX * newZoom, y: cy - worldY * newZoom };
|
|
40
|
+
zoomRef.current = newZoom;
|
|
41
|
+
applyWorldTransform();
|
|
42
|
+
refreshMinimap();
|
|
43
|
+
render();
|
|
44
|
+
}, [
|
|
45
|
+
minZoom,
|
|
46
|
+
applyWorldTransform,
|
|
47
|
+
refreshMinimap,
|
|
48
|
+
render,
|
|
49
|
+
containerRef,
|
|
50
|
+
panRef,
|
|
51
|
+
zoomRef,
|
|
52
|
+
]);
|
|
53
|
+
const handleZoomReset = useCallback(() => {
|
|
54
|
+
zoomRef.current = 1;
|
|
55
|
+
applyWorldTransform();
|
|
56
|
+
refreshMinimap();
|
|
57
|
+
render();
|
|
58
|
+
}, [applyWorldTransform, refreshMinimap, render, zoomRef]);
|
|
59
|
+
const handleMinimapPan = useCallback((pan) => {
|
|
60
|
+
panRef.current = pan;
|
|
61
|
+
applyWorldTransform();
|
|
62
|
+
refreshMinimap();
|
|
63
|
+
render();
|
|
64
|
+
}, [applyWorldTransform, refreshMinimap, render, panRef]);
|
|
65
|
+
const smoothPanToWorld = useCallback((worldX, worldY, durationMs = 600) => {
|
|
66
|
+
const container = containerRef.current;
|
|
67
|
+
if (!container)
|
|
68
|
+
return;
|
|
69
|
+
if (animationFrameRef.current !== null) {
|
|
70
|
+
cancelAnimationFrame(animationFrameRef.current);
|
|
71
|
+
animationFrameRef.current = null;
|
|
72
|
+
}
|
|
73
|
+
const viewportWidth = container.clientWidth;
|
|
74
|
+
const viewportHeight = container.clientHeight;
|
|
75
|
+
const targetPanX = viewportWidth / 2 - worldX * zoomRef.current;
|
|
76
|
+
const targetPanY = viewportHeight / 2 - worldY * zoomRef.current;
|
|
77
|
+
const startPanX = panRef.current.x;
|
|
78
|
+
const startPanY = panRef.current.y;
|
|
79
|
+
const startTime = performance.now();
|
|
80
|
+
const animate = (currentTime) => {
|
|
81
|
+
const elapsed = currentTime - startTime;
|
|
82
|
+
const progress = Math.min(elapsed / durationMs, 1);
|
|
83
|
+
const eased = easeInOutCubic(progress);
|
|
84
|
+
panRef.current = {
|
|
85
|
+
x: startPanX + (targetPanX - startPanX) * eased,
|
|
86
|
+
y: startPanY + (targetPanY - startPanY) * eased,
|
|
87
|
+
};
|
|
88
|
+
applyWorldTransform();
|
|
89
|
+
refreshMinimap();
|
|
90
|
+
render();
|
|
91
|
+
if (progress < 1) {
|
|
92
|
+
animationFrameRef.current = requestAnimationFrame(animate);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
animationFrameRef.current = null;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
animationFrameRef.current = requestAnimationFrame(animate);
|
|
99
|
+
}, [
|
|
100
|
+
applyWorldTransform,
|
|
101
|
+
refreshMinimap,
|
|
102
|
+
render,
|
|
103
|
+
animationFrameRef,
|
|
104
|
+
containerRef,
|
|
105
|
+
panRef,
|
|
106
|
+
zoomRef,
|
|
107
|
+
]);
|
|
108
|
+
const smoothPanZoomToWorld = useCallback((worldX, worldY, targetZoom = 1, durationMs = 600) => {
|
|
109
|
+
const container = containerRef.current;
|
|
110
|
+
if (!container)
|
|
111
|
+
return;
|
|
112
|
+
if (animationFrameRef.current !== null) {
|
|
113
|
+
cancelAnimationFrame(animationFrameRef.current);
|
|
114
|
+
animationFrameRef.current = null;
|
|
115
|
+
}
|
|
116
|
+
const viewportWidth = container.clientWidth;
|
|
117
|
+
const viewportHeight = container.clientHeight;
|
|
118
|
+
const startZoom = zoomRef.current;
|
|
119
|
+
const endZoom = Math.min(maxZoom, Math.max(minZoom, targetZoom));
|
|
120
|
+
const startPanX = panRef.current.x;
|
|
121
|
+
const startPanY = panRef.current.y;
|
|
122
|
+
const endPanX = viewportWidth / 2 - worldX * endZoom;
|
|
123
|
+
const endPanY = viewportHeight / 2 - worldY * endZoom;
|
|
124
|
+
const startTime = performance.now();
|
|
125
|
+
const animate = (currentTime) => {
|
|
126
|
+
const elapsed = currentTime - startTime;
|
|
127
|
+
const progress = Math.min(elapsed / durationMs, 1);
|
|
128
|
+
const eased = easeInOutCubic(progress);
|
|
129
|
+
zoomRef.current = startZoom + (endZoom - startZoom) * eased;
|
|
130
|
+
panRef.current = {
|
|
131
|
+
x: startPanX + (endPanX - startPanX) * eased,
|
|
132
|
+
y: startPanY + (endPanY - startPanY) * eased,
|
|
133
|
+
};
|
|
134
|
+
applyWorldTransform();
|
|
135
|
+
refreshMinimap();
|
|
136
|
+
render();
|
|
137
|
+
if (progress < 1) {
|
|
138
|
+
animationFrameRef.current = requestAnimationFrame(animate);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
animationFrameRef.current = null;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
animationFrameRef.current = requestAnimationFrame(animate);
|
|
145
|
+
}, [
|
|
146
|
+
maxZoom,
|
|
147
|
+
minZoom,
|
|
148
|
+
applyWorldTransform,
|
|
149
|
+
refreshMinimap,
|
|
150
|
+
render,
|
|
151
|
+
animationFrameRef,
|
|
152
|
+
containerRef,
|
|
153
|
+
panRef,
|
|
154
|
+
zoomRef,
|
|
155
|
+
]);
|
|
156
|
+
const getContainerRect = useCallback(() => {
|
|
157
|
+
return containerRef.current?.getBoundingClientRect() ?? null;
|
|
158
|
+
}, [containerRef]);
|
|
159
|
+
const getItemBounds = useCallback(() => Array.from(itemRegistryRef.current.values(), (entry) => ({
|
|
160
|
+
x: entry.x,
|
|
161
|
+
y: entry.y,
|
|
162
|
+
width: entry.width,
|
|
163
|
+
height: entry.height,
|
|
164
|
+
})), [itemRegistryRef]);
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
if (!pixiReady)
|
|
167
|
+
return;
|
|
168
|
+
onHandleReadyRef.current?.({
|
|
169
|
+
smoothPanToWorld,
|
|
170
|
+
smoothPanZoomToWorld,
|
|
171
|
+
getContainerRect,
|
|
172
|
+
getItemBounds,
|
|
173
|
+
});
|
|
174
|
+
}, [
|
|
175
|
+
pixiReady,
|
|
176
|
+
smoothPanToWorld,
|
|
177
|
+
smoothPanZoomToWorld,
|
|
178
|
+
getContainerRect,
|
|
179
|
+
getItemBounds,
|
|
180
|
+
onHandleReadyRef,
|
|
181
|
+
]);
|
|
182
|
+
return { handleZoomIn, handleZoomOut, handleZoomReset, handleMinimapPan };
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
export { useCanvasNavigation };
|
|
186
|
+
//# sourceMappingURL=useCanvasNavigation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCanvasNavigation.js","sources":["../../../Canvas/useCanvasNavigation.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { useCallback, useEffect, type RefObject } from 'react';\nimport { easeInOutCubic } from './canvasEasing';\nimport type { CanvasHandle } from './CanvasHandle';\nimport type { CanvasItemRegistryEntry } from './CanvasItemRegistryEntry';\nimport type { MinimapItem } from './CanvasMinimap';\n\n/** The camera state and operations {@link useCanvasNavigation} drives. */\nexport interface CanvasNavigationOptions {\n containerRef: RefObject<HTMLDivElement | null>;\n animationFrameRef: RefObject<number | null>;\n panRef: RefObject<{ x: number; y: number }>;\n zoomRef: RefObject<number>;\n itemRegistryRef: RefObject<Map<string, CanvasItemRegistryEntry>>;\n minZoom: number;\n maxZoom: number;\n pixiReady: boolean;\n onHandleReadyRef: RefObject<((handle: CanvasHandle) => void) | undefined>;\n applyWorldTransform: () => void;\n refreshMinimap: () => void;\n render: () => void;\n}\n\n/**\n * The zoom controls, minimap panning, animated camera moves, and the imperative handle.\n * @param options The container, camera state and operations.\n * @returns The handlers for the integrated controls and minimap.\n */\nexport const useCanvasNavigation = ({\n containerRef,\n animationFrameRef,\n panRef,\n zoomRef,\n itemRegistryRef,\n minZoom,\n maxZoom,\n pixiReady,\n onHandleReadyRef,\n applyWorldTransform,\n refreshMinimap,\n render,\n}: CanvasNavigationOptions) => {\n // Controls handlers\n const handleZoomIn = useCallback(() => {\n const container = containerRef.current;\n if (!container) return;\n const cx = container.clientWidth / 2;\n const cy = container.clientHeight / 2;\n const factor = 1.2;\n const newZoom = Math.min(maxZoom, zoomRef.current * factor);\n const worldX = (cx - panRef.current.x) / zoomRef.current;\n const worldY = (cy - panRef.current.y) / zoomRef.current;\n panRef.current = { x: cx - worldX * newZoom, y: cy - worldY * newZoom };\n zoomRef.current = newZoom;\n applyWorldTransform();\n refreshMinimap();\n render();\n }, [\n maxZoom,\n applyWorldTransform,\n refreshMinimap,\n render,\n containerRef,\n panRef,\n zoomRef,\n ]);\n\n const handleZoomOut = useCallback(() => {\n const container = containerRef.current;\n if (!container) return;\n const cx = container.clientWidth / 2;\n const cy = container.clientHeight / 2;\n const factor = 1.2;\n const newZoom = Math.max(minZoom, zoomRef.current / factor);\n const worldX = (cx - panRef.current.x) / zoomRef.current;\n const worldY = (cy - panRef.current.y) / zoomRef.current;\n panRef.current = { x: cx - worldX * newZoom, y: cy - worldY * newZoom };\n zoomRef.current = newZoom;\n applyWorldTransform();\n refreshMinimap();\n render();\n }, [\n minZoom,\n applyWorldTransform,\n refreshMinimap,\n render,\n containerRef,\n panRef,\n zoomRef,\n ]);\n\n const handleZoomReset = useCallback(() => {\n zoomRef.current = 1;\n applyWorldTransform();\n refreshMinimap();\n render();\n }, [applyWorldTransform, refreshMinimap, render, zoomRef]);\n\n const handleMinimapPan = useCallback(\n (pan: { x: number; y: number }) => {\n panRef.current = pan;\n applyWorldTransform();\n refreshMinimap();\n render();\n },\n [applyWorldTransform, refreshMinimap, render, panRef],\n );\n\n const smoothPanToWorld = useCallback(\n (worldX: number, worldY: number, durationMs = 600) => {\n const container = containerRef.current;\n if (!container) return;\n\n if (animationFrameRef.current !== null) {\n cancelAnimationFrame(animationFrameRef.current);\n animationFrameRef.current = null;\n }\n\n const viewportWidth = container.clientWidth;\n const viewportHeight = container.clientHeight;\n const targetPanX = viewportWidth / 2 - worldX * zoomRef.current;\n const targetPanY = viewportHeight / 2 - worldY * zoomRef.current;\n const startPanX = panRef.current.x;\n const startPanY = panRef.current.y;\n const startTime = performance.now();\n\n const animate = (currentTime: number) => {\n const elapsed = currentTime - startTime;\n const progress = Math.min(elapsed / durationMs, 1);\n const eased = easeInOutCubic(progress);\n panRef.current = {\n x: startPanX + (targetPanX - startPanX) * eased,\n y: startPanY + (targetPanY - startPanY) * eased,\n };\n applyWorldTransform();\n refreshMinimap();\n render();\n if (progress < 1) {\n animationFrameRef.current = requestAnimationFrame(animate);\n } else {\n animationFrameRef.current = null;\n }\n };\n\n animationFrameRef.current = requestAnimationFrame(animate);\n },\n [\n applyWorldTransform,\n refreshMinimap,\n render,\n animationFrameRef,\n containerRef,\n panRef,\n zoomRef,\n ],\n );\n\n const smoothPanZoomToWorld = useCallback(\n (worldX: number, worldY: number, targetZoom = 1, durationMs = 600) => {\n const container = containerRef.current;\n if (!container) return;\n\n if (animationFrameRef.current !== null) {\n cancelAnimationFrame(animationFrameRef.current);\n animationFrameRef.current = null;\n }\n\n const viewportWidth = container.clientWidth;\n const viewportHeight = container.clientHeight;\n const startZoom = zoomRef.current;\n const endZoom = Math.min(maxZoom, Math.max(minZoom, targetZoom));\n const startPanX = panRef.current.x;\n const startPanY = panRef.current.y;\n // Final pan that centers the world point at the target zoom.\n const endPanX = viewportWidth / 2 - worldX * endZoom;\n const endPanY = viewportHeight / 2 - worldY * endZoom;\n const startTime = performance.now();\n\n const animate = (currentTime: number) => {\n const elapsed = currentTime - startTime;\n const progress = Math.min(elapsed / durationMs, 1);\n const eased = easeInOutCubic(progress);\n zoomRef.current = startZoom + (endZoom - startZoom) * eased;\n panRef.current = {\n x: startPanX + (endPanX - startPanX) * eased,\n y: startPanY + (endPanY - startPanY) * eased,\n };\n applyWorldTransform();\n refreshMinimap();\n render();\n if (progress < 1) {\n animationFrameRef.current = requestAnimationFrame(animate);\n } else {\n animationFrameRef.current = null;\n }\n };\n\n animationFrameRef.current = requestAnimationFrame(animate);\n },\n [\n maxZoom,\n minZoom,\n applyWorldTransform,\n refreshMinimap,\n render,\n animationFrameRef,\n containerRef,\n panRef,\n zoomRef,\n ],\n );\n\n const getContainerRect = useCallback((): DOMRect | null => {\n return containerRef.current?.getBoundingClientRect() ?? null;\n }, [containerRef]);\n\n // Measured, not estimated: the registry holds what each CanvasItem's ResizeObserver last reported, so a\n // fit-to-content computed from it includes everything an item really renders (specifications under the\n // rows included), unlike the size estimates the minimap falls back to before anything has mounted.\n const getItemBounds = useCallback(\n (): MinimapItem[] =>\n Array.from(itemRegistryRef.current.values(), (entry) => ({\n x: entry.x,\n y: entry.y,\n width: entry.width,\n height: entry.height,\n })),\n [itemRegistryRef],\n );\n\n // Expose imperative handle once the canvas is set up\n useEffect(() => {\n if (!pixiReady) return;\n onHandleReadyRef.current?.({\n smoothPanToWorld,\n smoothPanZoomToWorld,\n getContainerRect,\n getItemBounds,\n });\n }, [\n pixiReady,\n smoothPanToWorld,\n smoothPanZoomToWorld,\n getContainerRect,\n getItemBounds,\n onHandleReadyRef,\n ]);\n\n return { handleZoomIn, handleZoomOut, handleZoomReset, handleMinimapPan };\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type PointerEvent, type RefObject } from 'react';
|
|
2
|
+
import { type PointerPosition } from './pinchGesture.js';
|
|
3
|
+
/** The camera state and operations {@link useCanvasPointerGestures} drives. */
|
|
4
|
+
export interface CanvasPointerGesturesOptions {
|
|
5
|
+
containerRef: RefObject<HTMLDivElement | null>;
|
|
6
|
+
animationFrameRef: RefObject<number | null>;
|
|
7
|
+
panRef: RefObject<{
|
|
8
|
+
x: number;
|
|
9
|
+
y: number;
|
|
10
|
+
}>;
|
|
11
|
+
zoomRef: RefObject<number>;
|
|
12
|
+
/** Touch pointers currently down on the canvas, in the order they landed. */
|
|
13
|
+
touchPointersRef: RefObject<Map<number, PointerPosition>>;
|
|
14
|
+
minZoom: number;
|
|
15
|
+
maxZoom: number;
|
|
16
|
+
readOnly: boolean;
|
|
17
|
+
backgroundDragPans: boolean;
|
|
18
|
+
applyWorldTransform: () => void;
|
|
19
|
+
refreshMinimap: () => void;
|
|
20
|
+
render: () => void;
|
|
21
|
+
noteGestureActivity: () => void;
|
|
22
|
+
scheduleTransformApply: () => void;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Background drag-pan, middle-button pan, one-finger pan with momentum, and two-finger pinch.
|
|
26
|
+
* @param options The container, camera state and operations.
|
|
27
|
+
* @returns The pointer handlers for the canvas surface.
|
|
28
|
+
*/
|
|
29
|
+
export declare const useCanvasPointerGestures: ({ containerRef, animationFrameRef, panRef, zoomRef, touchPointersRef, minZoom, maxZoom, readOnly, backgroundDragPans, applyWorldTransform, refreshMinimap, render, noteGestureActivity, scheduleTransformApply, }: CanvasPointerGesturesOptions) => {
|
|
30
|
+
handlePointerDown: (e: PointerEvent<HTMLDivElement>) => void;
|
|
31
|
+
handlePointerMove: (e: PointerEvent<HTMLDivElement>) => void;
|
|
32
|
+
handlePointerUp: (e: PointerEvent<HTMLDivElement>) => void;
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=useCanvasPointerGestures.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCanvasPointerGestures.d.ts","sourceRoot":"","sources":["../../../Canvas/useCanvasPointerGestures.ts"],"names":[],"mappings":"AAGA,OAAO,EAAuB,KAAK,YAAY,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAa/E,OAAO,EAEH,KAAK,eAAe,EAGvB,MAAM,mBAAgB,CAAC;AAExB,+EAA+E;AAC/E,MAAM,WAAW,4BAA4B;IACzC,YAAY,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAC/C,iBAAiB,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC5C,MAAM,EAAE,SAAS,CAAC;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5C,OAAO,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;IAC3B,6EAA6E;IAC7E,gBAAgB,EAAE,SAAS,CAAC,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC;IAC1D,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,mBAAmB,EAAE,MAAM,IAAI,CAAC;IAChC,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,mBAAmB,EAAE,MAAM,IAAI,CAAC;IAChC,sBAAsB,EAAE,MAAM,IAAI,CAAC;CACtC;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,sNAelC,4BAA4B;2BAoHnB,YAAY,CAAC,cAAc,CAAC;2BAoD5B,YAAY,CAAC,cAAc,CAAC;yBA2C5B,YAAY,CAAC,cAAc,CAAC;CA4BvC,CAAC"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { useRef, useCallback } from 'react';
|
|
2
|
+
import { MOMENTUM_MIN_VELOCITY, MOMENTUM_SAMPLE_WINDOW_MS } from './canvasGestureTuning.js';
|
|
3
|
+
import { isBackgroundPointerTarget } from './isBackgroundPointerTarget.js';
|
|
4
|
+
import { velocityFromSamples, trimSamples, decayVelocity } from './panMomentum.js';
|
|
5
|
+
import { pinchSnapshotOf, pinchChangeBetween } from './pinchGesture.js';
|
|
6
|
+
|
|
7
|
+
const useCanvasPointerGestures = ({ containerRef, animationFrameRef, panRef, zoomRef, touchPointersRef, minZoom, maxZoom, readOnly, backgroundDragPans, applyWorldTransform, refreshMinimap, render, noteGestureActivity, scheduleTransformApply, }) => {
|
|
8
|
+
const isPanningRef = useRef(false);
|
|
9
|
+
const lastPointerRef = useRef({ x: 0, y: 0 });
|
|
10
|
+
const setPanCursor = useCallback((panning) => {
|
|
11
|
+
if (containerRef.current)
|
|
12
|
+
containerRef.current.style.cursor = panning ? 'grabbing' : 'default';
|
|
13
|
+
}, [containerRef]);
|
|
14
|
+
const pinchRef = useRef(null);
|
|
15
|
+
const touchPanSamplesRef = useRef([]);
|
|
16
|
+
const applyPinch = useCallback(() => {
|
|
17
|
+
const container = containerRef.current;
|
|
18
|
+
const snapshot = pinchSnapshotOf(touchPointersRef.current.values());
|
|
19
|
+
if (!container || !snapshot)
|
|
20
|
+
return;
|
|
21
|
+
const previous = pinchRef.current;
|
|
22
|
+
pinchRef.current = snapshot;
|
|
23
|
+
if (!previous)
|
|
24
|
+
return;
|
|
25
|
+
const change = pinchChangeBetween(previous, snapshot);
|
|
26
|
+
const rect = container.getBoundingClientRect();
|
|
27
|
+
const focusX = snapshot.center.x - rect.left;
|
|
28
|
+
const focusY = snapshot.center.y - rect.top;
|
|
29
|
+
const newZoom = Math.max(minZoom, Math.min(maxZoom, zoomRef.current * change.scale));
|
|
30
|
+
const worldX = (focusX - change.panX - panRef.current.x) / zoomRef.current;
|
|
31
|
+
const worldY = (focusY - change.panY - panRef.current.y) / zoomRef.current;
|
|
32
|
+
panRef.current = { x: focusX - worldX * newZoom, y: focusY - worldY * newZoom };
|
|
33
|
+
zoomRef.current = newZoom;
|
|
34
|
+
noteGestureActivity();
|
|
35
|
+
scheduleTransformApply();
|
|
36
|
+
}, [
|
|
37
|
+
minZoom,
|
|
38
|
+
maxZoom,
|
|
39
|
+
noteGestureActivity,
|
|
40
|
+
scheduleTransformApply,
|
|
41
|
+
containerRef,
|
|
42
|
+
panRef,
|
|
43
|
+
touchPointersRef,
|
|
44
|
+
zoomRef,
|
|
45
|
+
]);
|
|
46
|
+
const startTouchMomentum = useCallback(() => {
|
|
47
|
+
const initialVelocity = velocityFromSamples(touchPanSamplesRef.current, MOMENTUM_MIN_VELOCITY);
|
|
48
|
+
if (!initialVelocity)
|
|
49
|
+
return;
|
|
50
|
+
if (animationFrameRef.current !== null) {
|
|
51
|
+
cancelAnimationFrame(animationFrameRef.current);
|
|
52
|
+
animationFrameRef.current = null;
|
|
53
|
+
}
|
|
54
|
+
let velocity = initialVelocity;
|
|
55
|
+
let lastFrameTime = performance.now();
|
|
56
|
+
const animate = (currentTime) => {
|
|
57
|
+
const frameElapsedMs = currentTime - lastFrameTime;
|
|
58
|
+
lastFrameTime = currentTime;
|
|
59
|
+
panRef.current = {
|
|
60
|
+
x: panRef.current.x + velocity.x * frameElapsedMs,
|
|
61
|
+
y: panRef.current.y + velocity.y * frameElapsedMs,
|
|
62
|
+
};
|
|
63
|
+
velocity = decayVelocity(velocity, frameElapsedMs);
|
|
64
|
+
applyWorldTransform();
|
|
65
|
+
refreshMinimap();
|
|
66
|
+
render();
|
|
67
|
+
noteGestureActivity();
|
|
68
|
+
if (Math.hypot(velocity.x, velocity.y) > MOMENTUM_MIN_VELOCITY) {
|
|
69
|
+
animationFrameRef.current = requestAnimationFrame(animate);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
animationFrameRef.current = null;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
animationFrameRef.current = requestAnimationFrame(animate);
|
|
76
|
+
}, [
|
|
77
|
+
applyWorldTransform,
|
|
78
|
+
refreshMinimap,
|
|
79
|
+
render,
|
|
80
|
+
noteGestureActivity,
|
|
81
|
+
animationFrameRef,
|
|
82
|
+
panRef,
|
|
83
|
+
]);
|
|
84
|
+
const handlePointerDown = useCallback((e) => {
|
|
85
|
+
if (animationFrameRef.current !== null) {
|
|
86
|
+
cancelAnimationFrame(animationFrameRef.current);
|
|
87
|
+
animationFrameRef.current = null;
|
|
88
|
+
}
|
|
89
|
+
if (e.pointerType === 'touch') {
|
|
90
|
+
touchPointersRef.current.set(e.pointerId, { x: e.clientX, y: e.clientY });
|
|
91
|
+
if (touchPointersRef.current.size > 1) {
|
|
92
|
+
isPanningRef.current = false;
|
|
93
|
+
setPanCursor(false);
|
|
94
|
+
pinchRef.current = pinchSnapshotOf(touchPointersRef.current.values());
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const isBackground = isBackgroundPointerTarget(e.pointerType, readOnly, e.target, containerRef.current);
|
|
99
|
+
const mayDragPan = backgroundDragPans || e.pointerType === 'touch';
|
|
100
|
+
if (e.button === 1 || (e.button === 0 && isBackground && mayDragPan)) {
|
|
101
|
+
if (e.button === 1)
|
|
102
|
+
e.preventDefault();
|
|
103
|
+
isPanningRef.current = true;
|
|
104
|
+
setPanCursor(true);
|
|
105
|
+
lastPointerRef.current = { x: e.clientX, y: e.clientY };
|
|
106
|
+
if (e.pointerType === 'touch') {
|
|
107
|
+
touchPanSamplesRef.current = [
|
|
108
|
+
{ x: e.clientX, y: e.clientY, time: performance.now() },
|
|
109
|
+
];
|
|
110
|
+
}
|
|
111
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
112
|
+
}
|
|
113
|
+
}, [
|
|
114
|
+
readOnly,
|
|
115
|
+
backgroundDragPans,
|
|
116
|
+
setPanCursor,
|
|
117
|
+
animationFrameRef,
|
|
118
|
+
containerRef,
|
|
119
|
+
touchPointersRef,
|
|
120
|
+
]);
|
|
121
|
+
const handlePointerMove = useCallback((e) => {
|
|
122
|
+
if (e.pointerType === 'touch' && touchPointersRef.current.has(e.pointerId)) {
|
|
123
|
+
touchPointersRef.current.set(e.pointerId, { x: e.clientX, y: e.clientY });
|
|
124
|
+
if (touchPointersRef.current.size > 1) {
|
|
125
|
+
applyPinch();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (!isPanningRef.current)
|
|
130
|
+
return;
|
|
131
|
+
const deltaX = e.clientX - lastPointerRef.current.x;
|
|
132
|
+
const deltaY = e.clientY - lastPointerRef.current.y;
|
|
133
|
+
lastPointerRef.current = { x: e.clientX, y: e.clientY };
|
|
134
|
+
panRef.current = {
|
|
135
|
+
x: panRef.current.x + deltaX,
|
|
136
|
+
y: panRef.current.y + deltaY,
|
|
137
|
+
};
|
|
138
|
+
if (e.pointerType === 'touch') {
|
|
139
|
+
const samples = [
|
|
140
|
+
...touchPanSamplesRef.current,
|
|
141
|
+
{ x: e.clientX, y: e.clientY, time: performance.now() },
|
|
142
|
+
];
|
|
143
|
+
touchPanSamplesRef.current = trimSamples(samples, MOMENTUM_SAMPLE_WINDOW_MS);
|
|
144
|
+
}
|
|
145
|
+
noteGestureActivity();
|
|
146
|
+
scheduleTransformApply();
|
|
147
|
+
}, [
|
|
148
|
+
applyPinch,
|
|
149
|
+
noteGestureActivity,
|
|
150
|
+
scheduleTransformApply,
|
|
151
|
+
panRef,
|
|
152
|
+
touchPointersRef,
|
|
153
|
+
]);
|
|
154
|
+
const handlePointerUp = useCallback((e) => {
|
|
155
|
+
if (touchPointersRef.current.delete(e.pointerId)) {
|
|
156
|
+
pinchRef.current = pinchSnapshotOf(touchPointersRef.current.values());
|
|
157
|
+
const [remaining] = [...touchPointersRef.current.values()];
|
|
158
|
+
if (remaining) {
|
|
159
|
+
isPanningRef.current = true;
|
|
160
|
+
setPanCursor(true);
|
|
161
|
+
lastPointerRef.current = { x: remaining.x, y: remaining.y };
|
|
162
|
+
touchPanSamplesRef.current = [
|
|
163
|
+
{ x: remaining.x, y: remaining.y, time: performance.now() },
|
|
164
|
+
];
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const wasPanningTouch = e.pointerType === 'touch' && isPanningRef.current;
|
|
169
|
+
isPanningRef.current = false;
|
|
170
|
+
setPanCursor(false);
|
|
171
|
+
if (wasPanningTouch)
|
|
172
|
+
startTouchMomentum();
|
|
173
|
+
}, [startTouchMomentum, setPanCursor, touchPointersRef]);
|
|
174
|
+
return { handlePointerDown, handlePointerMove, handlePointerUp };
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export { useCanvasPointerGestures };
|
|
178
|
+
//# sourceMappingURL=useCanvasPointerGestures.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCanvasPointerGestures.js","sources":["../../../Canvas/useCanvasPointerGestures.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { useCallback, useRef, type PointerEvent, type RefObject } from 'react';\nimport {\n MOMENTUM_FRICTION,\n MOMENTUM_MIN_VELOCITY,\n MOMENTUM_SAMPLE_WINDOW_MS,\n} from './canvasGestureTuning';\nimport { isBackgroundPointerTarget } from './isBackgroundPointerTarget';\nimport {\n type PanSample,\n decayVelocity,\n trimSamples,\n velocityFromSamples,\n} from './panMomentum';\nimport {\n type PinchSnapshot,\n type PointerPosition,\n pinchChangeBetween,\n pinchSnapshotOf,\n} from './pinchGesture';\n\n/** The camera state and operations {@link useCanvasPointerGestures} drives. */\nexport interface CanvasPointerGesturesOptions {\n containerRef: RefObject<HTMLDivElement | null>;\n animationFrameRef: RefObject<number | null>;\n panRef: RefObject<{ x: number; y: number }>;\n zoomRef: RefObject<number>;\n /** Touch pointers currently down on the canvas, in the order they landed. */\n touchPointersRef: RefObject<Map<number, PointerPosition>>;\n minZoom: number;\n maxZoom: number;\n readOnly: boolean;\n backgroundDragPans: boolean;\n applyWorldTransform: () => void;\n refreshMinimap: () => void;\n render: () => void;\n noteGestureActivity: () => void;\n scheduleTransformApply: () => void;\n}\n\n/**\n * Background drag-pan, middle-button pan, one-finger pan with momentum, and two-finger pinch.\n * @param options The container, camera state and operations.\n * @returns The pointer handlers for the canvas surface.\n */\nexport const useCanvasPointerGestures = ({\n containerRef,\n animationFrameRef,\n panRef,\n zoomRef,\n touchPointersRef,\n minZoom,\n maxZoom,\n readOnly,\n backgroundDragPans,\n applyWorldTransform,\n refreshMinimap,\n render,\n noteGestureActivity,\n scheduleTransformApply,\n}: CanvasPointerGesturesOptions) => {\n const isPanningRef = useRef(false);\n const lastPointerRef = useRef({ x: 0, y: 0 });\n\n // Direct DOM write, not React state — an idle background should not look grabbable until a drag\n // starts. Swapping to 'grabbing' for the life of a drag-pan is exactly the kind of per-gesture\n // churn this file\n // already avoids re-rendering for elsewhere (see the transform writes below).\n const setPanCursor = useCallback(\n (panning: boolean) => {\n if (containerRef.current)\n containerRef.current.style.cursor = panning ? 'grabbing' : 'default';\n },\n [containerRef],\n );\n\n const pinchRef = useRef<PinchSnapshot | null>(null);\n\n // Recent positions of the single finger currently panning, newest last, trimmed to\n // MOMENTUM_SAMPLE_WINDOW_MS — the trailing window startTouchMomentum reads the release velocity from.\n const touchPanSamplesRef = useRef<PanSample[]>([]);\n\n // Touch: two fingers pan and zoom together, anywhere on the surface — the trackpad's\n // wheel/ctrl+wheel equivalent for a tablet. The world point under the pinch midpoint stays put\n // while the midpoint itself drags the board, so zooming and panning feel like one gesture.\n const applyPinch = useCallback(() => {\n const container = containerRef.current;\n const snapshot = pinchSnapshotOf(touchPointersRef.current.values());\n if (!container || !snapshot) return;\n\n const previous = pinchRef.current;\n pinchRef.current = snapshot;\n // The first move of a gesture only establishes the baseline — there is nothing to move yet.\n if (!previous) return;\n\n const change = pinchChangeBetween(previous, snapshot);\n const rect = container.getBoundingClientRect();\n const focusX = snapshot.center.x - rect.left;\n const focusY = snapshot.center.y - rect.top;\n const newZoom = Math.max(\n minZoom,\n Math.min(maxZoom, zoomRef.current * change.scale),\n );\n // The world point that was under the midpoint BEFORE this step is the one being held, so the\n // midpoint's own movement is what pans — exactly as zoom-towards-cursor does for the wheel.\n const worldX = (focusX - change.panX - panRef.current.x) / zoomRef.current;\n const worldY = (focusY - change.panY - panRef.current.y) / zoomRef.current;\n panRef.current = { x: focusX - worldX * newZoom, y: focusY - worldY * newZoom };\n zoomRef.current = newZoom;\n\n noteGestureActivity();\n scheduleTransformApply();\n }, [\n minZoom,\n maxZoom,\n noteGestureActivity,\n scheduleTransformApply,\n containerRef,\n panRef,\n touchPointersRef,\n zoomRef,\n ]);\n\n // Coasts the board after a touch pan release, decaying the release velocity to a stop — the\n // manual equivalent of the momentum a trackpad's own wheel-event stream gives panning for free.\n // Shares animationFrameRef with smoothPanToWorld/smoothPanZoomToWorld so only one of the three\n // ever drives the transform at once; each cancels whichever of the others was still running.\n const startTouchMomentum = useCallback(() => {\n const initialVelocity = velocityFromSamples(\n touchPanSamplesRef.current,\n MOMENTUM_MIN_VELOCITY,\n );\n if (!initialVelocity) return;\n\n if (animationFrameRef.current !== null) {\n cancelAnimationFrame(animationFrameRef.current);\n animationFrameRef.current = null;\n }\n\n let velocity = initialVelocity;\n let lastFrameTime = performance.now();\n\n const animate = (currentTime: number) => {\n const frameElapsedMs = currentTime - lastFrameTime;\n lastFrameTime = currentTime;\n\n panRef.current = {\n x: panRef.current.x + velocity.x * frameElapsedMs,\n y: panRef.current.y + velocity.y * frameElapsedMs,\n };\n velocity = decayVelocity(velocity, frameElapsedMs, MOMENTUM_FRICTION);\n\n applyWorldTransform();\n refreshMinimap();\n render();\n noteGestureActivity();\n\n if (Math.hypot(velocity.x, velocity.y) > MOMENTUM_MIN_VELOCITY) {\n animationFrameRef.current = requestAnimationFrame(animate);\n } else {\n animationFrameRef.current = null;\n }\n };\n\n animationFrameRef.current = requestAnimationFrame(animate);\n }, [\n applyWorldTransform,\n refreshMinimap,\n render,\n noteGestureActivity,\n animationFrameRef,\n panRef,\n ]);\n\n // Pointer: pan via middle mouse, left-click on empty background, or a single finger on it\n const handlePointerDown = useCallback(\n (e: PointerEvent<HTMLDivElement>) => {\n // A fresh gesture always wins over a still-coasting one from the last touch pan.\n if (animationFrameRef.current !== null) {\n cancelAnimationFrame(animationFrameRef.current);\n animationFrameRef.current = null;\n }\n\n if (e.pointerType === 'touch') {\n touchPointersRef.current.set(e.pointerId, { x: e.clientX, y: e.clientY });\n if (touchPointersRef.current.size > 1) {\n // A second finger turns whatever was happening into a pinch: end the one-finger drag\n // so the two paths never both move the board in the same frame.\n isPanningRef.current = false;\n setPanCursor(false);\n pinchRef.current = pinchSnapshotOf(touchPointersRef.current.values());\n return;\n }\n }\n\n const isBackground = isBackgroundPointerTarget(\n e.pointerType,\n readOnly,\n e.target,\n containerRef.current,\n );\n // A host that claims the background drag for itself only gives up the mouse/pen gesture: touch\n // has no middle button and no wheel, so one finger must keep panning or the board is stuck.\n const mayDragPan = backgroundDragPans || e.pointerType === 'touch';\n if (e.button === 1 || (e.button === 0 && isBackground && mayDragPan)) {\n if (e.button === 1) e.preventDefault();\n isPanningRef.current = true;\n setPanCursor(true);\n lastPointerRef.current = { x: e.clientX, y: e.clientY };\n if (e.pointerType === 'touch') {\n touchPanSamplesRef.current = [\n { x: e.clientX, y: e.clientY, time: performance.now() },\n ];\n }\n e.currentTarget.setPointerCapture(e.pointerId);\n }\n },\n [\n readOnly,\n backgroundDragPans,\n setPanCursor,\n animationFrameRef,\n containerRef,\n touchPointersRef,\n ],\n );\n\n const handlePointerMove = useCallback(\n (e: PointerEvent<HTMLDivElement>) => {\n if (e.pointerType === 'touch' && touchPointersRef.current.has(e.pointerId)) {\n touchPointersRef.current.set(e.pointerId, { x: e.clientX, y: e.clientY });\n if (touchPointersRef.current.size > 1) {\n applyPinch();\n return;\n }\n }\n if (!isPanningRef.current) return;\n const deltaX = e.clientX - lastPointerRef.current.x;\n const deltaY = e.clientY - lastPointerRef.current.y;\n lastPointerRef.current = { x: e.clientX, y: e.clientY };\n panRef.current = {\n x: panRef.current.x + deltaX,\n y: panRef.current.y + deltaY,\n };\n\n if (e.pointerType === 'touch') {\n const samples = [\n ...touchPanSamplesRef.current,\n { x: e.clientX, y: e.clientY, time: performance.now() },\n ];\n touchPanSamplesRef.current = trimSamples(\n samples,\n MOMENTUM_SAMPLE_WINDOW_MS,\n );\n }\n\n // Same gesture path as wheel pan: coalesce onto one rAF and signal the gesture so\n // virtualization holds still during a drag-pan too.\n noteGestureActivity();\n scheduleTransformApply();\n },\n [\n applyPinch,\n noteGestureActivity,\n scheduleTransformApply,\n panRef,\n touchPointersRef,\n ],\n );\n\n const handlePointerUp = useCallback(\n (e: PointerEvent<HTMLDivElement>) => {\n if (touchPointersRef.current.delete(e.pointerId)) {\n // Re-baseline on the fingers that are left: keeping the old geometry would register the\n // lifted finger's absence as a huge pinch/pan step on the next move.\n pinchRef.current = pinchSnapshotOf(touchPointersRef.current.values());\n const [remaining] = [...touchPointersRef.current.values()];\n if (remaining) {\n // Lifting back down to one finger continues the same gesture as a plain drag-pan\n // rather than stopping dead until the user lifts and starts over.\n isPanningRef.current = true;\n setPanCursor(true);\n lastPointerRef.current = { x: remaining.x, y: remaining.y };\n touchPanSamplesRef.current = [\n { x: remaining.x, y: remaining.y, time: performance.now() },\n ];\n return;\n }\n }\n\n const wasPanningTouch = e.pointerType === 'touch' && isPanningRef.current;\n isPanningRef.current = false;\n setPanCursor(false);\n if (wasPanningTouch) startTouchMomentum();\n },\n [startTouchMomentum, setPanCursor, touchPointersRef],\n );\n\n return { handlePointerDown, handlePointerMove, handlePointerUp };\n};\n"],"names":[],"mappings":";;;;;;AA+CO,MAAM,wBAAwB,GAAG,CAAC,EACrC,YAAY,EACZ,iBAAiB,EACjB,MAAM,EACN,OAAO,EACP,gBAAgB,EAChB,OAAO,EACP,OAAO,EACP,QAAQ,EACR,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,EACd,MAAM,EACN,mBAAmB,EACnB,sBAAsB,GACK,KAAI;AAC/B,IAAA,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC;AAClC,IAAA,MAAM,cAAc,GAAG,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;AAM7C,IAAA,MAAM,YAAY,GAAG,WAAW,CAC5B,CAAC,OAAgB,KAAI;QACjB,IAAI,YAAY,CAAC,OAAO;AACpB,YAAA,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS;AAC5E,IAAA,CAAC,EACD,CAAC,YAAY,CAAC,CACjB;AAED,IAAA,MAAM,QAAQ,GAAG,MAAM,CAAuB,IAAI,CAAC;AAInD,IAAA,MAAM,kBAAkB,GAAG,MAAM,CAAc,EAAE,CAAC;AAKlD,IAAA,MAAM,UAAU,GAAG,WAAW,CAAC,MAAK;AAChC,QAAA,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO;QACtC,MAAM,QAAQ,GAAG,eAAe,CAAC,gBAAgB,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;AACnE,QAAA,IAAI,CAAC,SAAS,IAAI,CAAC,QAAQ;YAAE;AAE7B,QAAA,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO;AACjC,QAAA,QAAQ,CAAC,OAAO,GAAG,QAAQ;AAE3B,QAAA,IAAI,CAAC,QAAQ;YAAE;QAEf,MAAM,MAAM,GAAG,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,CAAC;AACrD,QAAA,MAAM,IAAI,GAAG,SAAS,CAAC,qBAAqB,EAAE;QAC9C,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI;QAC5C,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG;QAC3C,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CACpB,OAAO,EACP,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CACpD;AAGD,QAAA,MAAM,MAAM,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,OAAO,CAAC,OAAO;AAC1E,QAAA,MAAM,MAAM,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,OAAO,CAAC,OAAO;QAC1E,MAAM,CAAC,OAAO,GAAG,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE;AAC/E,QAAA,OAAO,CAAC,OAAO,GAAG,OAAO;AAEzB,QAAA,mBAAmB,EAAE;AACrB,QAAA,sBAAsB,EAAE;AAC5B,IAAA,CAAC,EAAE;QACC,OAAO;QACP,OAAO;QACP,mBAAmB;QACnB,sBAAsB;QACtB,YAAY;QACZ,MAAM;QACN,gBAAgB;QAChB,OAAO;AACV,KAAA,CAAC;AAMF,IAAA,MAAM,kBAAkB,GAAG,WAAW,CAAC,MAAK;QACxC,MAAM,eAAe,GAAG,mBAAmB,CACvC,kBAAkB,CAAC,OAAO,EAC1B,qBAAqB,CACxB;AACD,QAAA,IAAI,CAAC,eAAe;YAAE;AAEtB,QAAA,IAAI,iBAAiB,CAAC,OAAO,KAAK,IAAI,EAAE;AACpC,YAAA,oBAAoB,CAAC,iBAAiB,CAAC,OAAO,CAAC;AAC/C,YAAA,iBAAiB,CAAC,OAAO,GAAG,IAAI;QACpC;QAEA,IAAI,QAAQ,GAAG,eAAe;AAC9B,QAAA,IAAI,aAAa,GAAG,WAAW,CAAC,GAAG,EAAE;AAErC,QAAA,MAAM,OAAO,GAAG,CAAC,WAAmB,KAAI;AACpC,YAAA,MAAM,cAAc,GAAG,WAAW,GAAG,aAAa;YAClD,aAAa,GAAG,WAAW;YAE3B,MAAM,CAAC,OAAO,GAAG;gBACb,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,GAAG,cAAc;gBACjD,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,GAAG,cAAc;aACpD;YACD,QAAQ,GAAG,aAAa,CAAC,QAAQ,EAAE,cAAiC,CAAC;AAErE,YAAA,mBAAmB,EAAE;AACrB,YAAA,cAAc,EAAE;AAChB,YAAA,MAAM,EAAE;AACR,YAAA,mBAAmB,EAAE;AAErB,YAAA,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,qBAAqB,EAAE;AAC5D,gBAAA,iBAAiB,CAAC,OAAO,GAAG,qBAAqB,CAAC,OAAO,CAAC;YAC9D;iBAAO;AACH,gBAAA,iBAAiB,CAAC,OAAO,GAAG,IAAI;YACpC;AACJ,QAAA,CAAC;AAED,QAAA,iBAAiB,CAAC,OAAO,GAAG,qBAAqB,CAAC,OAAO,CAAC;AAC9D,IAAA,CAAC,EAAE;QACC,mBAAmB;QACnB,cAAc;QACd,MAAM;QACN,mBAAmB;QACnB,iBAAiB;QACjB,MAAM;AACT,KAAA,CAAC;AAGF,IAAA,MAAM,iBAAiB,GAAG,WAAW,CACjC,CAAC,CAA+B,KAAI;AAEhC,QAAA,IAAI,iBAAiB,CAAC,OAAO,KAAK,IAAI,EAAE;AACpC,YAAA,oBAAoB,CAAC,iBAAiB,CAAC,OAAO,CAAC;AAC/C,YAAA,iBAAiB,CAAC,OAAO,GAAG,IAAI;QACpC;AAEA,QAAA,IAAI,CAAC,CAAC,WAAW,KAAK,OAAO,EAAE;YAC3B,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;YACzE,IAAI,gBAAgB,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,EAAE;AAGnC,gBAAA,YAAY,CAAC,OAAO,GAAG,KAAK;gBAC5B,YAAY,CAAC,KAAK,CAAC;AACnB,gBAAA,QAAQ,CAAC,OAAO,GAAG,eAAe,CAAC,gBAAgB,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;gBACrE;YACJ;QACJ;AAEA,QAAA,MAAM,YAAY,GAAG,yBAAyB,CAC1C,CAAC,CAAC,WAAW,EACb,QAAQ,EACR,CAAC,CAAC,MAAM,EACR,YAAY,CAAC,OAAO,CACvB;QAGD,MAAM,UAAU,GAAG,kBAAkB,IAAI,CAAC,CAAC,WAAW,KAAK,OAAO;AAClE,QAAA,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,YAAY,IAAI,UAAU,CAAC,EAAE;AAClE,YAAA,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;gBAAE,CAAC,CAAC,cAAc,EAAE;AACtC,YAAA,YAAY,CAAC,OAAO,GAAG,IAAI;YAC3B,YAAY,CAAC,IAAI,CAAC;AAClB,YAAA,cAAc,CAAC,OAAO,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE;AACvD,YAAA,IAAI,CAAC,CAAC,WAAW,KAAK,OAAO,EAAE;gBAC3B,kBAAkB,CAAC,OAAO,GAAG;AACzB,oBAAA,EAAE,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,IAAI,EAAE,WAAW,CAAC,GAAG,EAAE,EAAE;iBAC1D;YACL;YACA,CAAC,CAAC,aAAa,CAAC,iBAAiB,CAAC,CAAC,CAAC,SAAS,CAAC;QAClD;AACJ,IAAA,CAAC,EACD;QACI,QAAQ;QACR,kBAAkB;QAClB,YAAY;QACZ,iBAAiB;QACjB,YAAY;QACZ,gBAAgB;AACnB,KAAA,CACJ;AAED,IAAA,MAAM,iBAAiB,GAAG,WAAW,CACjC,CAAC,CAA+B,KAAI;AAChC,QAAA,IAAI,CAAC,CAAC,WAAW,KAAK,OAAO,IAAI,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE;YACxE,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;YACzE,IAAI,gBAAgB,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,EAAE;AACnC,gBAAA,UAAU,EAAE;gBACZ;YACJ;QACJ;QACA,IAAI,CAAC,YAAY,CAAC,OAAO;YAAE;QAC3B,MAAM,MAAM,GAAG,CAAC,CAAC,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;QACnD,MAAM,MAAM,GAAG,CAAC,CAAC,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;AACnD,QAAA,cAAc,CAAC,OAAO,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE;QACvD,MAAM,CAAC,OAAO,GAAG;AACb,YAAA,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,MAAM;AAC5B,YAAA,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,MAAM;SAC/B;AAED,QAAA,IAAI,CAAC,CAAC,WAAW,KAAK,OAAO,EAAE;AAC3B,YAAA,MAAM,OAAO,GAAG;gBACZ,GAAG,kBAAkB,CAAC,OAAO;AAC7B,gBAAA,EAAE,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,IAAI,EAAE,WAAW,CAAC,GAAG,EAAE,EAAE;aAC1D;YACD,kBAAkB,CAAC,OAAO,GAAG,WAAW,CACpC,OAAO,EACP,yBAAyB,CAC5B;QACL;AAIA,QAAA,mBAAmB,EAAE;AACrB,QAAA,sBAAsB,EAAE;AAC5B,IAAA,CAAC,EACD;QACI,UAAU;QACV,mBAAmB;QACnB,sBAAsB;QACtB,MAAM;QACN,gBAAgB;AACnB,KAAA,CACJ;AAED,IAAA,MAAM,eAAe,GAAG,WAAW,CAC/B,CAAC,CAA+B,KAAI;QAChC,IAAI,gBAAgB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE;AAG9C,YAAA,QAAQ,CAAC,OAAO,GAAG,eAAe,CAAC,gBAAgB,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;AACrE,YAAA,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,gBAAgB,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YAC1D,IAAI,SAAS,EAAE;AAGX,gBAAA,YAAY,CAAC,OAAO,GAAG,IAAI;gBAC3B,YAAY,CAAC,IAAI,CAAC;AAClB,gBAAA,cAAc,CAAC,OAAO,GAAG,EAAE,CAAC,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,SAAS,CAAC,CAAC,EAAE;gBAC3D,kBAAkB,CAAC,OAAO,GAAG;AACzB,oBAAA,EAAE,CAAC,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,CAAC,GAAG,EAAE,EAAE;iBAC9D;gBACD;YACJ;QACJ;QAEA,MAAM,eAAe,GAAG,CAAC,CAAC,WAAW,KAAK,OAAO,IAAI,YAAY,CAAC,OAAO;AACzE,QAAA,YAAY,CAAC,OAAO,GAAG,KAAK;QAC5B,YAAY,CAAC,KAAK,CAAC;AACnB,QAAA,IAAI,eAAe;AAAE,YAAA,kBAAkB,EAAE;IAC7C,CAAC,EACD,CAAC,kBAAkB,EAAE,YAAY,EAAE,gBAAgB,CAAC,CACvD;AAED,IAAA,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,eAAe,EAAE;AACpE;;"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
import type { PointerPosition } from './pinchGesture.js';
|
|
3
|
+
/** The camera state and operations {@link useCanvasWheelZoom} drives. */
|
|
4
|
+
export interface CanvasWheelZoomOptions {
|
|
5
|
+
containerRef: RefObject<HTMLDivElement | null>;
|
|
6
|
+
animationFrameRef: RefObject<number | null>;
|
|
7
|
+
panRef: RefObject<{
|
|
8
|
+
x: number;
|
|
9
|
+
y: number;
|
|
10
|
+
}>;
|
|
11
|
+
/** Touch pointers currently down; a touch pinch owns zooming while any are. */
|
|
12
|
+
touchPointersRef: RefObject<Map<number, PointerPosition>>;
|
|
13
|
+
zoomTowards: (focusX: number, focusY: number, factor: number) => void;
|
|
14
|
+
noteGestureActivity: () => void;
|
|
15
|
+
scheduleTransformApply: () => void;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Wheel pan, Ctrl/Cmd+wheel zoom, and Safari's trackpad gesture events, on the canvas and for
|
|
19
|
+
* floating content over it, without letting the browser zoom the page.
|
|
20
|
+
* @param options The container, camera state and operations.
|
|
21
|
+
*/
|
|
22
|
+
export declare const useCanvasWheelZoom: ({ containerRef, animationFrameRef, panRef, touchPointersRef, zoomTowards, noteGestureActivity, scheduleTransformApply, }: CanvasWheelZoomOptions) => void;
|
|
23
|
+
//# sourceMappingURL=useCanvasWheelZoom.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCanvasWheelZoom.d.ts","sourceRoot":"","sources":["../../../Canvas/useCanvasWheelZoom.ts"],"names":[],"mappings":"AAGA,OAAO,EAA0B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAG/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAgB,CAAC;AAGtD,yEAAyE;AACzE,MAAM,WAAW,sBAAsB;IACnC,YAAY,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAC/C,iBAAiB,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC5C,MAAM,EAAE,SAAS,CAAC;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5C,+EAA+E;IAC/E,gBAAgB,EAAE,SAAS,CAAC,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC;IAC1D,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACtE,mBAAmB,EAAE,MAAM,IAAI,CAAC;IAChC,sBAAsB,EAAE,MAAM,IAAI,CAAC;CACtC;AAED;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,6HAQ5B,sBAAsB,SA8LxB,CAAC"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { useCallback, useEffect } from 'react';
|
|
2
|
+
import { ZOOM_INTENSITY } from './canvasGestureTuning.js';
|
|
3
|
+
import { isWithinScrollableContent } from './isWithinScrollableContent.js';
|
|
4
|
+
|
|
5
|
+
const useCanvasWheelZoom = ({ containerRef, animationFrameRef, panRef, touchPointersRef, zoomTowards, noteGestureActivity, scheduleTransformApply, }) => {
|
|
6
|
+
const handleWheel = useCallback((e) => {
|
|
7
|
+
const container = containerRef.current;
|
|
8
|
+
if (!container)
|
|
9
|
+
return;
|
|
10
|
+
if (!(e.ctrlKey || e.metaKey) &&
|
|
11
|
+
isWithinScrollableContent(e.target, container))
|
|
12
|
+
return;
|
|
13
|
+
e.preventDefault();
|
|
14
|
+
if (animationFrameRef.current !== null) {
|
|
15
|
+
cancelAnimationFrame(animationFrameRef.current);
|
|
16
|
+
animationFrameRef.current = null;
|
|
17
|
+
}
|
|
18
|
+
const rect = container.getBoundingClientRect();
|
|
19
|
+
const mouseX = e.clientX - rect.left;
|
|
20
|
+
const mouseY = e.clientY - rect.top;
|
|
21
|
+
if (e.ctrlKey || e.metaKey) {
|
|
22
|
+
zoomTowards(mouseX, mouseY, Math.exp(-e.deltaY * ZOOM_INTENSITY));
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
panRef.current = {
|
|
26
|
+
x: panRef.current.x - e.deltaX,
|
|
27
|
+
y: panRef.current.y - e.deltaY,
|
|
28
|
+
};
|
|
29
|
+
noteGestureActivity();
|
|
30
|
+
scheduleTransformApply();
|
|
31
|
+
}
|
|
32
|
+
}, [
|
|
33
|
+
zoomTowards,
|
|
34
|
+
noteGestureActivity,
|
|
35
|
+
scheduleTransformApply,
|
|
36
|
+
animationFrameRef,
|
|
37
|
+
containerRef,
|
|
38
|
+
panRef,
|
|
39
|
+
]);
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
const container = containerRef.current;
|
|
42
|
+
if (!container)
|
|
43
|
+
return;
|
|
44
|
+
container.addEventListener('wheel', handleWheel, { passive: false });
|
|
45
|
+
return () => container.removeEventListener('wheel', handleWheel);
|
|
46
|
+
}, [handleWheel, containerRef]);
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
const container = containerRef.current;
|
|
49
|
+
if (!container)
|
|
50
|
+
return;
|
|
51
|
+
let lastScale = 1;
|
|
52
|
+
const handleGestureStart = (event) => {
|
|
53
|
+
event.preventDefault();
|
|
54
|
+
lastScale = event.scale || 1;
|
|
55
|
+
};
|
|
56
|
+
const handleGestureChange = (event) => {
|
|
57
|
+
event.preventDefault();
|
|
58
|
+
if (touchPointersRef.current.size > 0)
|
|
59
|
+
return;
|
|
60
|
+
const gesture = event;
|
|
61
|
+
if (!gesture.scale || !lastScale)
|
|
62
|
+
return;
|
|
63
|
+
const rect = container.getBoundingClientRect();
|
|
64
|
+
zoomTowards(gesture.clientX - rect.left, gesture.clientY - rect.top, gesture.scale / lastScale);
|
|
65
|
+
lastScale = gesture.scale;
|
|
66
|
+
};
|
|
67
|
+
const handleGestureEnd = (event) => event.preventDefault();
|
|
68
|
+
container.addEventListener('gesturestart', handleGestureStart, {
|
|
69
|
+
passive: false,
|
|
70
|
+
});
|
|
71
|
+
container.addEventListener('gesturechange', handleGestureChange, {
|
|
72
|
+
passive: false,
|
|
73
|
+
});
|
|
74
|
+
container.addEventListener('gestureend', handleGestureEnd, { passive: false });
|
|
75
|
+
return () => {
|
|
76
|
+
container.removeEventListener('gesturestart', handleGestureStart);
|
|
77
|
+
container.removeEventListener('gesturechange', handleGestureChange);
|
|
78
|
+
container.removeEventListener('gestureend', handleGestureEnd);
|
|
79
|
+
};
|
|
80
|
+
}, [zoomTowards, containerRef, touchPointersRef]);
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
const isWithinCanvas = (x, y) => {
|
|
83
|
+
const rect = containerRef.current?.getBoundingClientRect();
|
|
84
|
+
return (!!rect &&
|
|
85
|
+
x >= rect.left &&
|
|
86
|
+
x <= rect.right &&
|
|
87
|
+
y >= rect.top &&
|
|
88
|
+
y <= rect.bottom);
|
|
89
|
+
};
|
|
90
|
+
const reachesContainerListeners = (event) => event.target instanceof Node &&
|
|
91
|
+
!!containerRef.current?.contains(event.target);
|
|
92
|
+
const handleWindowWheel = (event) => {
|
|
93
|
+
if ((event.ctrlKey || event.metaKey) &&
|
|
94
|
+
isWithinCanvas(event.clientX, event.clientY)) {
|
|
95
|
+
event.preventDefault();
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
let lastScale = 1;
|
|
99
|
+
const handleWindowGestureStart = (event) => {
|
|
100
|
+
const gesture = event;
|
|
101
|
+
if (!isWithinCanvas(gesture.clientX, gesture.clientY))
|
|
102
|
+
return;
|
|
103
|
+
event.preventDefault();
|
|
104
|
+
lastScale = gesture.scale || 1;
|
|
105
|
+
};
|
|
106
|
+
const handleWindowGestureChange = (event) => {
|
|
107
|
+
const gesture = event;
|
|
108
|
+
if (!isWithinCanvas(gesture.clientX, gesture.clientY))
|
|
109
|
+
return;
|
|
110
|
+
event.preventDefault();
|
|
111
|
+
if (reachesContainerListeners(event))
|
|
112
|
+
return;
|
|
113
|
+
if (touchPointersRef.current.size > 0)
|
|
114
|
+
return;
|
|
115
|
+
if (!gesture.scale || !lastScale)
|
|
116
|
+
return;
|
|
117
|
+
const rect = containerRef.current?.getBoundingClientRect();
|
|
118
|
+
if (!rect)
|
|
119
|
+
return;
|
|
120
|
+
zoomTowards(gesture.clientX - rect.left, gesture.clientY - rect.top, gesture.scale / lastScale);
|
|
121
|
+
lastScale = gesture.scale;
|
|
122
|
+
};
|
|
123
|
+
const handleWindowGestureEnd = (event) => {
|
|
124
|
+
const gesture = event;
|
|
125
|
+
if (isWithinCanvas(gesture.clientX, gesture.clientY))
|
|
126
|
+
event.preventDefault();
|
|
127
|
+
};
|
|
128
|
+
window.addEventListener('wheel', handleWindowWheel, { passive: false });
|
|
129
|
+
window.addEventListener('gesturestart', handleWindowGestureStart, {
|
|
130
|
+
passive: false,
|
|
131
|
+
});
|
|
132
|
+
window.addEventListener('gesturechange', handleWindowGestureChange, {
|
|
133
|
+
passive: false,
|
|
134
|
+
});
|
|
135
|
+
window.addEventListener('gestureend', handleWindowGestureEnd, { passive: false });
|
|
136
|
+
return () => {
|
|
137
|
+
window.removeEventListener('wheel', handleWindowWheel);
|
|
138
|
+
window.removeEventListener('gesturestart', handleWindowGestureStart);
|
|
139
|
+
window.removeEventListener('gesturechange', handleWindowGestureChange);
|
|
140
|
+
window.removeEventListener('gestureend', handleWindowGestureEnd);
|
|
141
|
+
};
|
|
142
|
+
}, [zoomTowards, containerRef, touchPointersRef]);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export { useCanvasWheelZoom };
|
|
146
|
+
//# sourceMappingURL=useCanvasWheelZoom.js.map
|