canvu-react 0.4.18 → 0.4.20

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/dist/native.js CHANGED
@@ -4002,6 +4002,7 @@ function nativeFallbackToolCursorPoint(size) {
4002
4002
  }
4003
4003
 
4004
4004
  // src/native/native-vector-interactions.ts
4005
+ var NATIVE_SELECTION_HANDLE_HIT_RADIUS_PX = 24;
4005
4006
  function supportsNativeResizeHandles(item) {
4006
4007
  const k = item?.toolKind;
4007
4008
  if (k === "rect" || k === "ellipse" || k === "architectural-cloud" || k === "line" || k === "arrow" || k === "image" || k === "text") {
@@ -4023,7 +4024,7 @@ function hitTestNativeSelectionHandle({
4023
4024
  if (!supportsNativeResizeHandles(selectedItem)) return null;
4024
4025
  const bounds = normalizeRect(selectedItem.bounds);
4025
4026
  const rotation = selectedItem.rotation ?? 0;
4026
- const handleRadiusWorld = 6 / Math.max(zoom, 1e-9);
4027
+ const handleRadiusWorld = NATIVE_SELECTION_HANDLE_HIT_RADIUS_PX / Math.max(zoom, 1e-9);
4027
4028
  const rotateOffsetWorld = 24 / Math.max(zoom, 1e-9);
4028
4029
  if (hitTestRotateHandle(
4029
4030
  bounds,
@@ -4330,12 +4331,6 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4330
4331
  useEffect(() => {
4331
4332
  showFallbackToolCursor(toolId);
4332
4333
  }, [showFallbackToolCursor, toolId]);
4333
- const handlePointerMove = useCallback(
4334
- (event) => {
4335
- updateToolCursorPoint(screenPointFromPointerEvent(event));
4336
- },
4337
- [updateToolCursorPoint]
4338
- );
4339
4334
  const selectedItems = useMemo(
4340
4335
  () => items.filter((it) => selectedIds.includes(it.id)),
4341
4336
  [items, selectedIds]
@@ -4348,590 +4343,615 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4348
4343
  const showResizeHandles = interactive && selectedItems.length === 1 && !selectedItems[0]?.locked && supportsNativeResizeHandles(selectedItems[0]);
4349
4344
  const lastPinchDist = useRef(null);
4350
4345
  const lastPanPoint = useRef(null);
4351
- const panResponder = useMemo(
4352
- () => PanResponder.create({
4353
- onStartShouldSetPanResponder: () => true,
4354
- onMoveShouldSetPanResponder: () => true,
4355
- onPanResponderGrant: (evt) => {
4356
- lastPinchDist.current = null;
4357
- lastPanPoint.current = null;
4358
- const touches = evt.nativeEvent.touches;
4359
- const sx = evt.nativeEvent.locationX;
4360
- const sy = evt.nativeEvent.locationY;
4361
- if (touches && touches.length >= 2) {
4362
- hideToolCursor();
4363
- dragStateRef.current = { kind: "pan" };
4364
- return;
4365
- }
4366
- updateToolCursorPoint({ x: sx, y: sy });
4367
- if (!interactive) {
4368
- dragStateRef.current = { kind: "pan" };
4369
- return;
4370
- }
4371
- const tool = toolIdRef.current;
4372
- const cam = cameraRef.current;
4373
- if (!cam) return;
4374
- const { worldX, worldY } = screenToWorld(sx, sy);
4375
- if (tool === "hand") {
4376
- dragStateRef.current = { kind: "pan" };
4377
- return;
4378
- }
4379
- if (tool === "select") {
4380
- const currentSelectedIds = selectedIdsRef.current;
4381
- const selectedItem = currentSelectedIds.length === 1 ? itemsRef.current.find((item) => item.id === currentSelectedIds[0]) : void 0;
4382
- const selectionHandle = hitTestNativeSelectionHandle({
4383
- selectedItem,
4384
- selectedCount: currentSelectedIds.length,
4385
- worldPoint: { x: worldX, y: worldY },
4386
- zoom: cam.zoom
4387
- });
4388
- if (selectionHandle && selectedItem) {
4389
- if (selectionHandle.kind === "rotate") {
4390
- const rotationStart = nativeRotationDragStart({
4391
- item: selectedItem,
4392
- worldPoint: { x: worldX, y: worldY }
4393
- });
4394
- dragStateRef.current = {
4395
- kind: "rotate",
4396
- id: selectedItem.id,
4397
- snapshot: selectedItem,
4398
- ...rotationStart
4399
- };
4400
- return;
4401
- }
4346
+ const beginDragAtScreenPoint = useCallback(
4347
+ (point) => {
4348
+ lastPinchDist.current = null;
4349
+ lastPanPoint.current = null;
4350
+ const sx = point.x;
4351
+ const sy = point.y;
4352
+ updateToolCursorPoint(point);
4353
+ if (!interactive) {
4354
+ dragStateRef.current = { kind: "pan" };
4355
+ return;
4356
+ }
4357
+ const tool = toolIdRef.current;
4358
+ const cam = cameraRef.current;
4359
+ if (!cam) return;
4360
+ const { worldX, worldY } = screenToWorld(sx, sy);
4361
+ if (tool === "hand") {
4362
+ dragStateRef.current = { kind: "pan" };
4363
+ return;
4364
+ }
4365
+ if (tool === "select") {
4366
+ const currentSelectedIds = selectedIdsRef.current;
4367
+ const selectedItem = currentSelectedIds.length === 1 ? itemsRef.current.find((item) => item.id === currentSelectedIds[0]) : void 0;
4368
+ const selectionHandle = hitTestNativeSelectionHandle({
4369
+ selectedItem,
4370
+ selectedCount: currentSelectedIds.length,
4371
+ worldPoint: { x: worldX, y: worldY },
4372
+ zoom: cam.zoom
4373
+ });
4374
+ if (selectionHandle && selectedItem) {
4375
+ if (selectionHandle.kind === "rotate") {
4376
+ const rotationStart = nativeRotationDragStart({
4377
+ item: selectedItem,
4378
+ worldPoint: { x: worldX, y: worldY }
4379
+ });
4402
4380
  dragStateRef.current = {
4403
- kind: "resize",
4381
+ kind: "rotate",
4404
4382
  id: selectedItem.id,
4405
- handle: selectionHandle.handle,
4406
4383
  snapshot: selectedItem,
4407
- start: {
4408
- bounds: selectedItem.bounds,
4409
- line: selectedItem.line
4410
- }
4384
+ ...rotationStart
4411
4385
  };
4412
4386
  return;
4413
4387
  }
4414
- const hit = hitTestWorldPoint(itemsRef.current, worldX, worldY, {
4415
- lineHitWorld: 10 / cam.zoom,
4416
- ignoreLocked: true
4417
- });
4418
- if (hit) {
4419
- const cur = selectedIdsRef.current;
4420
- const ids = cur.includes(hit.id) ? [...cur] : [hit.id];
4421
- const snapshots = {};
4422
- for (const id of ids) {
4423
- const it = itemsRef.current.find((i) => i.id === id);
4424
- if (it) snapshots[id] = it;
4425
- }
4426
- dragStateRef.current = {
4427
- kind: "move",
4428
- ids,
4429
- snapshots,
4430
- startWorld: { x: worldX, y: worldY }
4431
- };
4432
- if (!cur.includes(hit.id)) {
4433
- onSelectionChangeRef.current?.([hit.id]);
4434
- }
4435
- } else {
4436
- onSelectionChangeRef.current?.([]);
4437
- dragStateRef.current = {
4438
- kind: "marquee",
4439
- startWorld: { x: worldX, y: worldY }
4440
- };
4441
- setPlacementPreview({
4442
- kind: "marquee",
4443
- rect: { x: worldX, y: worldY, width: 0, height: 0 }
4444
- });
4445
- }
4446
- return;
4447
- }
4448
- if (tool === "draw" || tool === "marker" || tool === "laser") {
4449
4388
  dragStateRef.current = {
4450
- kind: "draw",
4451
- tool,
4452
- points: [{ x: worldX, y: worldY }]
4453
- };
4454
- if (tool === "laser") {
4455
- if (laserClearTimerRef.current) {
4456
- clearTimeout(laserClearTimerRef.current);
4457
- laserClearTimerRef.current = null;
4389
+ kind: "resize",
4390
+ id: selectedItem.id,
4391
+ handle: selectionHandle.handle,
4392
+ snapshot: selectedItem,
4393
+ start: {
4394
+ bounds: selectedItem.bounds,
4395
+ line: selectedItem.line
4458
4396
  }
4459
- setLaserTrail([{ x: worldX, y: worldY, t: Date.now() }]);
4460
- } else {
4461
- setPlacementPreview({
4462
- kind: "stroke",
4463
- tool,
4464
- points: [{ x: worldX, y: worldY }],
4465
- style: { ...strokeStyleRef.current }
4466
- });
4467
- }
4397
+ };
4468
4398
  return;
4469
4399
  }
4470
- if (tool === "eraser") {
4471
- dragStateRef.current = { kind: "erase" };
4472
- eraserPreviewIdSetRef.current = /* @__PURE__ */ new Set();
4473
- setEraserPreviewIds([]);
4474
- setEraserTrail([{ x: worldX, y: worldY, t: Date.now() }]);
4475
- const toErase = collectEraserTargetsAtWorldPoint(
4476
- itemsRef.current,
4477
- worldX,
4478
- worldY,
4479
- { lineHitWorld: 10 / cam.zoom, ignoreLocked: true }
4480
- );
4481
- for (const id of toErase) {
4482
- eraserPreviewIdSetRef.current.add(id);
4400
+ const hit = hitTestWorldPoint(itemsRef.current, worldX, worldY, {
4401
+ lineHitWorld: 10 / cam.zoom,
4402
+ ignoreLocked: true
4403
+ });
4404
+ if (hit) {
4405
+ const cur = selectedIdsRef.current;
4406
+ const ids = cur.includes(hit.id) ? [...cur] : [hit.id];
4407
+ const snapshots = {};
4408
+ for (const id of ids) {
4409
+ const it = itemsRef.current.find((i) => i.id === id);
4410
+ if (it) snapshots[id] = it;
4483
4411
  }
4484
- setEraserPreviewIds(Array.from(eraserPreviewIdSetRef.current));
4485
- return;
4486
- }
4487
- if (isPlacementTool(tool)) {
4488
4412
  dragStateRef.current = {
4489
- kind: "place",
4490
- tool,
4491
- startWorld: { x: worldX, y: worldY },
4492
- startScreen: { x: sx, y: sy }
4413
+ kind: "move",
4414
+ ids,
4415
+ snapshots,
4416
+ startWorld: { x: worldX, y: worldY }
4493
4417
  };
4494
- setPlacementPreview(
4495
- placementPreviewForTool(
4496
- tool,
4497
- { x: worldX, y: worldY },
4498
- {
4499
- x: worldX,
4500
- y: worldY
4501
- }
4502
- )
4503
- );
4504
- return;
4505
- }
4506
- const customPlacement2 = resolveNativeCustomPlacement(
4507
- tool,
4508
- customPlacementRef.current,
4509
- customPlacementsRef.current
4510
- );
4511
- if (customPlacement2) {
4418
+ if (!cur.includes(hit.id)) {
4419
+ onSelectionChangeRef.current?.([hit.id]);
4420
+ }
4421
+ } else {
4422
+ onSelectionChangeRef.current?.([]);
4512
4423
  dragStateRef.current = {
4513
- kind: "custom-place",
4514
- tool,
4515
- placement: customPlacement2,
4516
- startWorld: { x: worldX, y: worldY },
4517
- startScreen: { x: sx, y: sy }
4424
+ kind: "marquee",
4425
+ startWorld: { x: worldX, y: worldY }
4518
4426
  };
4519
4427
  setPlacementPreview({
4520
- kind: "rect",
4428
+ kind: "marquee",
4521
4429
  rect: { x: worldX, y: worldY, width: 0, height: 0 }
4522
4430
  });
4523
- return;
4524
- }
4525
- if (tool === "note" || tool === "text") {
4526
- dragStateRef.current = {
4527
- kind: "tap",
4528
- tool,
4529
- startWorld: { x: worldX, y: worldY },
4530
- startScreen: { x: sx, y: sy }
4531
- };
4532
- return;
4533
- }
4534
- const handleWorldPointerDown = onWorldPointerDownRef.current;
4535
- if (handleWorldPointerDown) {
4536
- handleWorldPointerDown({
4537
- toolId: tool,
4538
- worldX,
4539
- worldY,
4540
- screenX: sx,
4541
- screenY: sy
4542
- });
4543
- requestSelectToolAfterUse();
4544
- return;
4545
- }
4546
- dragStateRef.current = { kind: "pan" };
4547
- },
4548
- onPanResponderMove: (evt) => {
4549
- const cam = cameraRef.current;
4550
- if (!cam) return;
4551
- const touches = evt.nativeEvent.touches;
4552
- const sx = evt.nativeEvent.locationX;
4553
- const sy = evt.nativeEvent.locationY;
4554
- const pageX = evt.nativeEvent.pageX;
4555
- const pageY = evt.nativeEvent.pageY;
4556
- if (touches && touches.length >= 2) {
4557
- hideToolCursor();
4558
- const t0 = touches[0];
4559
- const t1 = touches[1];
4560
- if (t0 && t1) {
4561
- const dx = t1.pageX - t0.pageX;
4562
- const dy = t1.pageY - t0.pageY;
4563
- const dist = Math.hypot(dx, dy);
4564
- if (lastPinchDist.current != null) {
4565
- const scale = dist / lastPinchDist.current;
4566
- const cx = (t0.pageX + t1.pageX) / 2;
4567
- const cy = (t0.pageY + t1.pageY) / 2;
4568
- cam.setZoom(cam.zoom * scale, { x: cx, y: cy });
4569
- requestRender();
4570
- }
4571
- lastPinchDist.current = dist;
4572
- lastPanPoint.current = null;
4573
- }
4574
- return;
4575
4431
  }
4576
- lastPinchDist.current = null;
4577
- updateToolCursorPoint({ x: sx, y: sy });
4578
- const { worldX, worldY } = screenToWorld(sx, sy);
4579
- const st = dragStateRef.current;
4580
- if (st.kind === "pan") {
4581
- const current = { x: pageX, y: pageY };
4582
- if (lastPanPoint.current) {
4583
- const dx = current.x - lastPanPoint.current.x;
4584
- const dy = current.y - lastPanPoint.current.y;
4585
- cam.x += dx;
4586
- cam.y += dy;
4587
- requestRender();
4588
- }
4589
- lastPanPoint.current = current;
4590
- return;
4591
- }
4592
- lastPanPoint.current = null;
4593
- if (st.kind === "draw") {
4594
- const pts = st.points;
4595
- const last = pts[pts.length - 1];
4596
- const dx = worldX - (last?.x ?? worldX);
4597
- const dy = worldY - (last?.y ?? worldY);
4598
- const shouldAppendPoint = Math.hypot(dx, dy) > 0.5 / cam.zoom;
4599
- if (shouldAppendPoint) {
4600
- pts.push({ x: worldX, y: worldY });
4601
- }
4602
- if (st.tool === "laser") {
4603
- if (shouldAppendPoint) {
4604
- setLaserTrail((prev) => [
4605
- ...prev,
4606
- { x: worldX, y: worldY, t: Date.now() }
4607
- ]);
4608
- }
4609
- return;
4432
+ return;
4433
+ }
4434
+ if (tool === "draw" || tool === "marker" || tool === "laser") {
4435
+ dragStateRef.current = {
4436
+ kind: "draw",
4437
+ tool,
4438
+ points: [{ x: worldX, y: worldY }]
4439
+ };
4440
+ if (tool === "laser") {
4441
+ if (laserClearTimerRef.current) {
4442
+ clearTimeout(laserClearTimerRef.current);
4443
+ laserClearTimerRef.current = null;
4610
4444
  }
4445
+ setLaserTrail([{ x: worldX, y: worldY, t: Date.now() }]);
4446
+ } else {
4611
4447
  setPlacementPreview({
4612
4448
  kind: "stroke",
4613
- tool: st.tool,
4614
- points: [...pts],
4449
+ tool,
4450
+ points: [{ x: worldX, y: worldY }],
4615
4451
  style: { ...strokeStyleRef.current }
4616
4452
  });
4617
- return;
4618
- }
4619
- if (st.kind === "move") {
4620
- const dx = worldX - st.startWorld.x;
4621
- const dy = worldY - st.startWorld.y;
4622
- const change = onItemsChangeRef.current;
4623
- if (!change) return;
4624
- const nextList = itemsRef.current.map((it) => {
4625
- const snap = st.snapshots[it.id];
4626
- if (!snap) return it;
4627
- return {
4628
- ...snap,
4629
- x: snap.x + dx,
4630
- y: snap.y + dy,
4631
- bounds: {
4632
- ...snap.bounds,
4633
- x: snap.bounds.x + dx,
4634
- y: snap.bounds.y + dy
4635
- }
4636
- };
4637
- });
4638
- change(nextList);
4639
- return;
4640
- }
4641
- if (st.kind === "rotate") {
4642
- const change = onItemsChangeRef.current;
4643
- if (!change) return;
4644
- const angle = Math.atan2(
4645
- worldY - st.pivotWorld.y,
4646
- worldX - st.pivotWorld.x
4647
- );
4648
- const next = applyRotationFromPointer(
4649
- st.snapshot,
4650
- st.startRotation,
4651
- st.startPointerAngleRad,
4652
- angle
4653
- );
4654
- change(
4655
- itemsRef.current.map((item) => item.id === st.id ? next : item)
4656
- );
4657
- return;
4658
- }
4659
- if (st.kind === "resize") {
4660
- const change = onItemsChangeRef.current;
4661
- if (!change) return;
4662
- const next = resizeItemByHandle(st.snapshot, st.start, st.handle, {
4663
- x: worldX,
4664
- y: worldY
4665
- });
4666
- change(
4667
- itemsRef.current.map((item) => item.id === st.id ? next : item)
4668
- );
4669
- return;
4670
- }
4671
- if (st.kind === "marquee") {
4672
- const a = st.startWorld;
4673
- const b = { x: worldX, y: worldY };
4674
- const rect = {
4675
- x: Math.min(a.x, b.x),
4676
- y: Math.min(a.y, b.y),
4677
- width: Math.abs(b.x - a.x),
4678
- height: Math.abs(b.y - a.y)
4679
- };
4680
- setPlacementPreview({ kind: "marquee", rect });
4681
- return;
4682
4453
  }
4683
- if (st.kind === "erase") {
4684
- setEraserTrail((prev) => [
4685
- ...prev,
4686
- { x: worldX, y: worldY, t: Date.now() }
4687
- ]);
4688
- const toErase = collectEraserTargetsAtWorldPoint(
4689
- itemsRef.current,
4690
- worldX,
4691
- worldY,
4692
- { lineHitWorld: 10 / cam.zoom, ignoreLocked: true }
4693
- );
4694
- for (const id of toErase) {
4695
- eraserPreviewIdSetRef.current.add(id);
4696
- }
4697
- setEraserPreviewIds(Array.from(eraserPreviewIdSetRef.current));
4698
- return;
4454
+ return;
4455
+ }
4456
+ if (tool === "eraser") {
4457
+ dragStateRef.current = { kind: "erase" };
4458
+ eraserPreviewIdSetRef.current = /* @__PURE__ */ new Set();
4459
+ setEraserPreviewIds([]);
4460
+ setEraserTrail([{ x: worldX, y: worldY, t: Date.now() }]);
4461
+ const toErase = collectEraserTargetsAtWorldPoint(
4462
+ itemsRef.current,
4463
+ worldX,
4464
+ worldY,
4465
+ { lineHitWorld: 10 / cam.zoom, ignoreLocked: true }
4466
+ );
4467
+ for (const id of toErase) {
4468
+ eraserPreviewIdSetRef.current.add(id);
4699
4469
  }
4700
- if (st.kind === "place") {
4701
- setPlacementPreview(
4702
- placementPreviewForTool(st.tool, st.startWorld, {
4470
+ setEraserPreviewIds(Array.from(eraserPreviewIdSetRef.current));
4471
+ return;
4472
+ }
4473
+ if (isPlacementTool(tool)) {
4474
+ dragStateRef.current = {
4475
+ kind: "place",
4476
+ tool,
4477
+ startWorld: { x: worldX, y: worldY },
4478
+ startScreen: { x: sx, y: sy }
4479
+ };
4480
+ setPlacementPreview(
4481
+ placementPreviewForTool(
4482
+ tool,
4483
+ { x: worldX, y: worldY },
4484
+ {
4703
4485
  x: worldX,
4704
4486
  y: worldY
4705
- })
4706
- );
4707
- return;
4487
+ }
4488
+ )
4489
+ );
4490
+ return;
4491
+ }
4492
+ const customPlacement2 = resolveNativeCustomPlacement(
4493
+ tool,
4494
+ customPlacementRef.current,
4495
+ customPlacementsRef.current
4496
+ );
4497
+ if (customPlacement2) {
4498
+ dragStateRef.current = {
4499
+ kind: "custom-place",
4500
+ tool,
4501
+ placement: customPlacement2,
4502
+ startWorld: { x: worldX, y: worldY },
4503
+ startScreen: { x: sx, y: sy }
4504
+ };
4505
+ setPlacementPreview({
4506
+ kind: "rect",
4507
+ rect: { x: worldX, y: worldY, width: 0, height: 0 }
4508
+ });
4509
+ return;
4510
+ }
4511
+ if (tool === "note" || tool === "text") {
4512
+ dragStateRef.current = {
4513
+ kind: "tap",
4514
+ tool,
4515
+ startWorld: { x: worldX, y: worldY },
4516
+ startScreen: { x: sx, y: sy }
4517
+ };
4518
+ return;
4519
+ }
4520
+ const handleWorldPointerDown = onWorldPointerDownRef.current;
4521
+ if (handleWorldPointerDown) {
4522
+ handleWorldPointerDown({
4523
+ toolId: tool,
4524
+ worldX,
4525
+ worldY,
4526
+ screenX: sx,
4527
+ screenY: sy
4528
+ });
4529
+ requestSelectToolAfterUse();
4530
+ return;
4531
+ }
4532
+ dragStateRef.current = { kind: "pan" };
4533
+ },
4534
+ [interactive, requestSelectToolAfterUse, screenToWorld, updateToolCursorPoint]
4535
+ );
4536
+ const applyDragMoveAtScreenPoint = useCallback(
4537
+ (point, pagePoint) => {
4538
+ const cam = cameraRef.current;
4539
+ if (!cam) return;
4540
+ updateToolCursorPoint(point);
4541
+ const { worldX, worldY } = screenToWorld(point.x, point.y);
4542
+ const st = dragStateRef.current;
4543
+ if (st.kind === "pan") {
4544
+ const current = pagePoint ?? point;
4545
+ if (lastPanPoint.current) {
4546
+ const dx = current.x - lastPanPoint.current.x;
4547
+ const dy = current.y - lastPanPoint.current.y;
4548
+ cam.x += dx;
4549
+ cam.y += dy;
4550
+ requestRender();
4708
4551
  }
4709
- if (st.kind === "custom-place") {
4710
- setPlacementPreview({
4711
- kind: "rect",
4712
- rect: rectFromCorners(st.startWorld, { x: worldX, y: worldY })
4713
- });
4552
+ lastPanPoint.current = current;
4553
+ return;
4554
+ }
4555
+ lastPanPoint.current = null;
4556
+ if (st.kind === "draw") {
4557
+ const pts = st.points;
4558
+ const last = pts[pts.length - 1];
4559
+ const dx = worldX - (last?.x ?? worldX);
4560
+ const dy = worldY - (last?.y ?? worldY);
4561
+ const shouldAppendPoint = Math.hypot(dx, dy) > 0.5 / cam.zoom;
4562
+ if (shouldAppendPoint) {
4563
+ pts.push({ x: worldX, y: worldY });
4564
+ }
4565
+ if (st.tool === "laser") {
4566
+ if (shouldAppendPoint) {
4567
+ setLaserTrail((prev) => [
4568
+ ...prev,
4569
+ { x: worldX, y: worldY, t: Date.now() }
4570
+ ]);
4571
+ }
4714
4572
  return;
4715
4573
  }
4716
- },
4717
- onPanResponderRelease: (evt) => {
4718
- lastPinchDist.current = null;
4719
- lastPanPoint.current = null;
4720
- updateToolCursorPoint({
4721
- x: evt.nativeEvent.locationX,
4722
- y: evt.nativeEvent.locationY
4574
+ setPlacementPreview({
4575
+ kind: "stroke",
4576
+ tool: st.tool,
4577
+ points: [...pts],
4578
+ style: { ...strokeStyleRef.current }
4723
4579
  });
4724
- const st = dragStateRef.current;
4725
- if (st.kind === "draw") {
4726
- dragStateRef.current = { kind: "idle" };
4727
- setPlacementPreview(null);
4728
- if (st.tool === "laser") {
4729
- if (laserClearTimerRef.current) {
4730
- clearTimeout(laserClearTimerRef.current);
4580
+ return;
4581
+ }
4582
+ if (st.kind === "move") {
4583
+ const dx = worldX - st.startWorld.x;
4584
+ const dy = worldY - st.startWorld.y;
4585
+ const change = onItemsChangeRef.current;
4586
+ if (!change) return;
4587
+ const nextList = itemsRef.current.map((it) => {
4588
+ const snap = st.snapshots[it.id];
4589
+ if (!snap) return it;
4590
+ return {
4591
+ ...snap,
4592
+ x: snap.x + dx,
4593
+ y: snap.y + dy,
4594
+ bounds: {
4595
+ ...snap.bounds,
4596
+ x: snap.bounds.x + dx,
4597
+ y: snap.bounds.y + dy
4731
4598
  }
4732
- laserClearTimerRef.current = setTimeout(() => {
4733
- setLaserTrail([]);
4734
- laserClearTimerRef.current = null;
4735
- }, 650);
4736
- requestSelectToolAfterUse();
4737
- return;
4738
- }
4739
- if (st.points.length < 1) return;
4740
- const change = onItemsChangeRef.current;
4741
- if (!change) return;
4742
- const id = createShapeId();
4743
- const item = createFreehandStrokeItem(
4744
- id,
4745
- st.points,
4746
- st.tool,
4747
- strokeStyleRef.current
4748
- );
4749
- if (item) {
4750
- change([...itemsRef.current, item]);
4599
+ };
4600
+ });
4601
+ change(nextList);
4602
+ return;
4603
+ }
4604
+ if (st.kind === "rotate") {
4605
+ const change = onItemsChangeRef.current;
4606
+ if (!change) return;
4607
+ const angle = Math.atan2(worldY - st.pivotWorld.y, worldX - st.pivotWorld.x);
4608
+ const next = applyRotationFromPointer(
4609
+ st.snapshot,
4610
+ st.startRotation,
4611
+ st.startPointerAngleRad,
4612
+ angle
4613
+ );
4614
+ change(itemsRef.current.map((item) => item.id === st.id ? next : item));
4615
+ return;
4616
+ }
4617
+ if (st.kind === "resize") {
4618
+ const change = onItemsChangeRef.current;
4619
+ if (!change) return;
4620
+ const next = resizeItemByHandle(st.snapshot, st.start, st.handle, {
4621
+ x: worldX,
4622
+ y: worldY
4623
+ });
4624
+ change(itemsRef.current.map((item) => item.id === st.id ? next : item));
4625
+ return;
4626
+ }
4627
+ if (st.kind === "marquee") {
4628
+ const a = st.startWorld;
4629
+ const b = { x: worldX, y: worldY };
4630
+ const rect = {
4631
+ x: Math.min(a.x, b.x),
4632
+ y: Math.min(a.y, b.y),
4633
+ width: Math.abs(b.x - a.x),
4634
+ height: Math.abs(b.y - a.y)
4635
+ };
4636
+ setPlacementPreview({ kind: "marquee", rect });
4637
+ return;
4638
+ }
4639
+ if (st.kind === "erase") {
4640
+ setEraserTrail((prev) => [...prev, { x: worldX, y: worldY, t: Date.now() }]);
4641
+ const toErase = collectEraserTargetsAtWorldPoint(
4642
+ itemsRef.current,
4643
+ worldX,
4644
+ worldY,
4645
+ { lineHitWorld: 10 / cam.zoom, ignoreLocked: true }
4646
+ );
4647
+ for (const id of toErase) {
4648
+ eraserPreviewIdSetRef.current.add(id);
4649
+ }
4650
+ setEraserPreviewIds(Array.from(eraserPreviewIdSetRef.current));
4651
+ return;
4652
+ }
4653
+ if (st.kind === "place") {
4654
+ setPlacementPreview(
4655
+ placementPreviewForTool(st.tool, st.startWorld, {
4656
+ x: worldX,
4657
+ y: worldY
4658
+ })
4659
+ );
4660
+ return;
4661
+ }
4662
+ if (st.kind === "custom-place") {
4663
+ setPlacementPreview({
4664
+ kind: "rect",
4665
+ rect: rectFromCorners(st.startWorld, { x: worldX, y: worldY })
4666
+ });
4667
+ return;
4668
+ }
4669
+ },
4670
+ [requestRender, screenToWorld, updateToolCursorPoint]
4671
+ );
4672
+ const finishDragAtScreenPoint = useCallback(
4673
+ (point) => {
4674
+ lastPinchDist.current = null;
4675
+ lastPanPoint.current = null;
4676
+ updateToolCursorPoint(point);
4677
+ const st = dragStateRef.current;
4678
+ if (st.kind === "draw") {
4679
+ dragStateRef.current = { kind: "idle" };
4680
+ setPlacementPreview(null);
4681
+ if (st.tool === "laser") {
4682
+ if (laserClearTimerRef.current) {
4683
+ clearTimeout(laserClearTimerRef.current);
4751
4684
  }
4685
+ laserClearTimerRef.current = setTimeout(() => {
4686
+ setLaserTrail([]);
4687
+ laserClearTimerRef.current = null;
4688
+ }, 650);
4752
4689
  requestSelectToolAfterUse();
4753
4690
  return;
4754
4691
  }
4755
- if (st.kind === "move") {
4756
- dragStateRef.current = { kind: "idle" };
4757
- return;
4692
+ if (st.points.length < 1) return;
4693
+ const change = onItemsChangeRef.current;
4694
+ if (!change) return;
4695
+ const id = createShapeId();
4696
+ const item = createFreehandStrokeItem(
4697
+ id,
4698
+ st.points,
4699
+ st.tool,
4700
+ strokeStyleRef.current
4701
+ );
4702
+ if (item) {
4703
+ change([...itemsRef.current, item]);
4758
4704
  }
4759
- if (st.kind === "resize" || st.kind === "rotate") {
4760
- dragStateRef.current = { kind: "idle" };
4761
- return;
4705
+ requestSelectToolAfterUse();
4706
+ return;
4707
+ }
4708
+ if (st.kind === "move") {
4709
+ dragStateRef.current = { kind: "idle" };
4710
+ return;
4711
+ }
4712
+ if (st.kind === "resize" || st.kind === "rotate") {
4713
+ dragStateRef.current = { kind: "idle" };
4714
+ return;
4715
+ }
4716
+ if (st.kind === "marquee") {
4717
+ dragStateRef.current = { kind: "idle" };
4718
+ setPlacementPreview(null);
4719
+ const cam = cameraRef.current;
4720
+ if (!cam) return;
4721
+ const { worldX, worldY } = screenToWorld(point.x, point.y);
4722
+ const a = st.startWorld;
4723
+ if (typeof worldX !== "number" || typeof worldY !== "number") return;
4724
+ const raw = {
4725
+ x: Math.min(a.x, worldX),
4726
+ y: Math.min(a.y, worldY),
4727
+ width: Math.abs(worldX - a.x),
4728
+ height: Math.abs(worldY - a.y)
4729
+ };
4730
+ const picked = collectIdsInRect(itemsRef.current, raw);
4731
+ onSelectionChangeRef.current?.(picked);
4732
+ return;
4733
+ }
4734
+ if (st.kind === "erase") {
4735
+ const change = onItemsChangeRef.current;
4736
+ if (change && eraserPreviewIdSetRef.current.size > 0) {
4737
+ const idSet = new Set(eraserPreviewIdSetRef.current);
4738
+ change(itemsRef.current.filter((i) => !idSet.has(i.id)));
4762
4739
  }
4763
- if (st.kind === "marquee") {
4764
- dragStateRef.current = { kind: "idle" };
4765
- setPlacementPreview(null);
4766
- const cam = cameraRef.current;
4767
- if (!cam) return;
4768
- const { worldX, worldY } = screenToWorld(
4769
- evt.nativeEvent.locationX,
4770
- evt.nativeEvent.locationY
4771
- );
4772
- const a = st.startWorld;
4773
- if (typeof worldX !== "number" || typeof worldY !== "number") return;
4774
- const raw = {
4775
- x: Math.min(a.x, worldX),
4776
- y: Math.min(a.y, worldY),
4777
- width: Math.abs(worldX - a.x),
4778
- height: Math.abs(worldY - a.y)
4779
- };
4780
- const picked = collectIdsInRect(itemsRef.current, raw);
4781
- onSelectionChangeRef.current?.(picked);
4740
+ eraserPreviewIdSetRef.current.clear();
4741
+ setEraserPreviewIds([]);
4742
+ setEraserTrail([]);
4743
+ dragStateRef.current = { kind: "idle" };
4744
+ requestSelectToolAfterUse();
4745
+ return;
4746
+ }
4747
+ if (st.kind === "place") {
4748
+ dragStateRef.current = { kind: "idle" };
4749
+ setPlacementPreview(null);
4750
+ const change = onItemsChangeRef.current;
4751
+ if (!change) return;
4752
+ const { worldX, worldY } = screenToWorld(point.x, point.y);
4753
+ const a = st.startWorld;
4754
+ const b = { x: worldX, y: worldY };
4755
+ const screenDx = point.x - st.startScreen.x;
4756
+ const screenDy = point.y - st.startScreen.y;
4757
+ if (st.tool === "arrow" && Math.hypot(screenDx, screenDy) < MIN_ARROW_DRAG_PX) {
4782
4758
  return;
4783
4759
  }
4784
- if (st.kind === "erase") {
4785
- const change = onItemsChangeRef.current;
4786
- if (change && eraserPreviewIdSetRef.current.size > 0) {
4787
- const idSet = new Set(eraserPreviewIdSetRef.current);
4788
- change(itemsRef.current.filter((i) => !idSet.has(i.id)));
4760
+ let raw = rectFromCorners(a, b);
4761
+ let br = normalizeRect(raw);
4762
+ let lineStart = a;
4763
+ let lineEnd = b;
4764
+ if (br.width < MIN_PLACE_SIZE || br.height < MIN_PLACE_SIZE) {
4765
+ const center = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
4766
+ const defaults = defaultPlacementWorld(st.tool, center);
4767
+ raw = defaults.raw;
4768
+ br = normalizeRect(raw);
4769
+ if (defaults.lineWorld) {
4770
+ const [defaultStart, defaultEnd] = defaults.lineWorld;
4771
+ lineStart = defaultStart;
4772
+ lineEnd = defaultEnd;
4789
4773
  }
4790
- eraserPreviewIdSetRef.current.clear();
4791
- setEraserPreviewIds([]);
4792
- setEraserTrail([]);
4793
- dragStateRef.current = { kind: "idle" };
4774
+ }
4775
+ const id = createShapeId();
4776
+ const style = strokeStyleRef.current;
4777
+ if (st.tool === "rect") {
4778
+ change([...itemsRef.current, createRectangleItem(id, raw, style)]);
4779
+ onSelectionChangeRef.current?.([id]);
4794
4780
  requestSelectToolAfterUse();
4795
4781
  return;
4796
4782
  }
4797
- if (st.kind === "place") {
4798
- dragStateRef.current = { kind: "idle" };
4799
- setPlacementPreview(null);
4800
- const change = onItemsChangeRef.current;
4801
- if (!change) return;
4802
- const { worldX, worldY } = screenToWorld(
4803
- evt.nativeEvent.locationX,
4804
- evt.nativeEvent.locationY
4805
- );
4806
- const a = st.startWorld;
4807
- const b = { x: worldX, y: worldY };
4808
- const screenDx = evt.nativeEvent.locationX - st.startScreen.x;
4809
- const screenDy = evt.nativeEvent.locationY - st.startScreen.y;
4810
- if (st.tool === "arrow" && Math.hypot(screenDx, screenDy) < MIN_ARROW_DRAG_PX) {
4811
- return;
4812
- }
4813
- let raw = rectFromCorners(a, b);
4814
- let br = normalizeRect(raw);
4815
- let lineStart = a;
4816
- let lineEnd = b;
4817
- if (br.width < MIN_PLACE_SIZE || br.height < MIN_PLACE_SIZE) {
4818
- const center = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
4819
- const defaults = defaultPlacementWorld(st.tool, center);
4820
- raw = defaults.raw;
4821
- br = normalizeRect(raw);
4822
- if (defaults.lineWorld) {
4823
- const [defaultStart, defaultEnd] = defaults.lineWorld;
4824
- lineStart = defaultStart;
4825
- lineEnd = defaultEnd;
4826
- }
4827
- }
4828
- const id = createShapeId();
4829
- const style = strokeStyleRef.current;
4830
- if (st.tool === "rect") {
4831
- change([...itemsRef.current, createRectangleItem(id, raw, style)]);
4832
- onSelectionChangeRef.current?.([id]);
4833
- requestSelectToolAfterUse();
4834
- return;
4835
- }
4836
- if (st.tool === "ellipse") {
4837
- change([...itemsRef.current, createEllipseItem(id, raw, style)]);
4838
- onSelectionChangeRef.current?.([id]);
4839
- requestSelectToolAfterUse();
4840
- return;
4841
- }
4842
- if (st.tool === "architectural-cloud") {
4843
- change([
4844
- ...itemsRef.current,
4845
- createArchitecturalCloudItem(id, raw, style)
4846
- ]);
4847
- onSelectionChangeRef.current?.([id]);
4848
- requestSelectToolAfterUse();
4849
- return;
4850
- }
4851
- const line = lineEndpointsToLocal(br, lineStart, lineEnd);
4783
+ if (st.tool === "ellipse") {
4784
+ change([...itemsRef.current, createEllipseItem(id, raw, style)]);
4785
+ onSelectionChangeRef.current?.([id]);
4786
+ requestSelectToolAfterUse();
4787
+ return;
4788
+ }
4789
+ if (st.tool === "architectural-cloud") {
4852
4790
  change([
4853
4791
  ...itemsRef.current,
4854
- createLineItem(id, br, line, st.tool, style)
4792
+ createArchitecturalCloudItem(id, raw, style)
4855
4793
  ]);
4856
4794
  onSelectionChangeRef.current?.([id]);
4857
4795
  requestSelectToolAfterUse();
4858
4796
  return;
4859
4797
  }
4860
- if (st.kind === "custom-place") {
4861
- dragStateRef.current = { kind: "idle" };
4862
- setPlacementPreview(null);
4863
- const change = onItemsChangeRef.current;
4864
- if (!change) return;
4865
- const { worldX, worldY } = screenToWorld(
4866
- evt.nativeEvent.locationX,
4867
- evt.nativeEvent.locationY
4868
- );
4869
- const center = {
4870
- x: (st.startWorld.x + worldX) / 2,
4871
- y: (st.startWorld.y + worldY) / 2
4872
- };
4873
- const raw = rectFromCorners(st.startWorld, { x: worldX, y: worldY });
4874
- const normalized = normalizeRect(raw);
4875
- const bounds = normalized.width < MIN_PLACE_SIZE || normalized.height < MIN_PLACE_SIZE ? {
4876
- x: center.x - 60,
4877
- y: center.y - 40,
4878
- width: 120,
4879
- height: 80
4880
- } : normalized;
4798
+ const line = lineEndpointsToLocal(br, lineStart, lineEnd);
4799
+ change([...itemsRef.current, createLineItem(id, br, line, st.tool, style)]);
4800
+ onSelectionChangeRef.current?.([id]);
4801
+ requestSelectToolAfterUse();
4802
+ return;
4803
+ }
4804
+ if (st.kind === "custom-place") {
4805
+ dragStateRef.current = { kind: "idle" };
4806
+ setPlacementPreview(null);
4807
+ const change = onItemsChangeRef.current;
4808
+ if (!change) return;
4809
+ const { worldX, worldY } = screenToWorld(point.x, point.y);
4810
+ const center = {
4811
+ x: (st.startWorld.x + worldX) / 2,
4812
+ y: (st.startWorld.y + worldY) / 2
4813
+ };
4814
+ const raw = rectFromCorners(st.startWorld, { x: worldX, y: worldY });
4815
+ const normalized = normalizeRect(raw);
4816
+ const bounds = normalized.width < MIN_PLACE_SIZE || normalized.height < MIN_PLACE_SIZE ? {
4817
+ x: center.x - 60,
4818
+ y: center.y - 40,
4819
+ width: 120,
4820
+ height: 80
4821
+ } : normalized;
4822
+ const id = createShapeId();
4823
+ const item = st.placement.createItem({ id, bounds });
4824
+ change([...itemsRef.current, item]);
4825
+ onSelectionChangeRef.current?.([id]);
4826
+ requestSelectToolAfterUse();
4827
+ return;
4828
+ }
4829
+ if (st.kind === "tap") {
4830
+ dragStateRef.current = { kind: "idle" };
4831
+ const screenDx = point.x - st.startScreen.x;
4832
+ const screenDy = point.y - st.startScreen.y;
4833
+ if (Math.hypot(screenDx, screenDy) > TAP_PX) return;
4834
+ const change = onItemsChangeRef.current;
4835
+ if (!change) return;
4836
+ if (st.tool === "text") {
4881
4837
  const id = createShapeId();
4882
- const item = st.placement.createItem({ id, bounds });
4838
+ const item = createTextItem(
4839
+ id,
4840
+ {
4841
+ x: st.startWorld.x - 4,
4842
+ y: st.startWorld.y - 18,
4843
+ width: 160,
4844
+ height: 26
4845
+ },
4846
+ "Text",
4847
+ strokeStyleRef.current,
4848
+ 18
4849
+ );
4883
4850
  change([...itemsRef.current, item]);
4884
4851
  onSelectionChangeRef.current?.([id]);
4885
4852
  requestSelectToolAfterUse();
4886
- return;
4887
4853
  }
4888
- if (st.kind === "tap") {
4889
- dragStateRef.current = { kind: "idle" };
4890
- const screenDx = evt.nativeEvent.locationX - st.startScreen.x;
4891
- const screenDy = evt.nativeEvent.locationY - st.startScreen.y;
4892
- if (Math.hypot(screenDx, screenDy) > TAP_PX) return;
4893
- const change = onItemsChangeRef.current;
4894
- if (!change) return;
4895
- if (st.tool === "text") {
4896
- const id = createShapeId();
4897
- const item = createTextItem(
4898
- id,
4899
- {
4900
- x: st.startWorld.x - 4,
4901
- y: st.startWorld.y - 18,
4902
- width: 160,
4903
- height: 26
4904
- },
4905
- "Text",
4906
- strokeStyleRef.current,
4907
- 18
4908
- );
4909
- change([...itemsRef.current, item]);
4910
- onSelectionChangeRef.current?.([id]);
4911
- requestSelectToolAfterUse();
4912
- }
4913
- if (st.tool === "note") {
4914
- const id = createShapeId();
4915
- const note = {
4916
- id,
4854
+ if (st.tool === "note") {
4855
+ const id = createShapeId();
4856
+ const note = {
4857
+ id,
4858
+ x: st.startWorld.x - 70,
4859
+ y: st.startWorld.y - 40,
4860
+ bounds: {
4917
4861
  x: st.startWorld.x - 70,
4918
4862
  y: st.startWorld.y - 40,
4919
- bounds: {
4920
- x: st.startWorld.x - 70,
4921
- y: st.startWorld.y - 40,
4922
- width: 140,
4923
- height: 80
4924
- },
4925
- childrenSvg: `<rect width="140" height="80" rx="8" fill="#fef08a" stroke="#facc15" stroke-width="1" /><text x="10" y="22" fill="#1f2937" font-size="12" font-family="system-ui">Nota</text>`,
4926
- toolKind: "custom"
4927
- };
4928
- change([...itemsRef.current, note]);
4929
- onSelectionChangeRef.current?.([id]);
4930
- requestSelectToolAfterUse();
4863
+ width: 140,
4864
+ height: 80
4865
+ },
4866
+ childrenSvg: `<rect width="140" height="80" rx="8" fill="#fef08a" stroke="#facc15" stroke-width="1" /><text x="10" y="22" fill="#1f2937" font-size="12" font-family="system-ui">Nota</text>`,
4867
+ toolKind: "custom"
4868
+ };
4869
+ change([...itemsRef.current, note]);
4870
+ onSelectionChangeRef.current?.([id]);
4871
+ requestSelectToolAfterUse();
4872
+ }
4873
+ return;
4874
+ }
4875
+ dragStateRef.current = { kind: "idle" };
4876
+ },
4877
+ [requestSelectToolAfterUse, screenToWorld, updateToolCursorPoint]
4878
+ );
4879
+ const handlePointerDown = useCallback(
4880
+ (event) => {
4881
+ const point = screenPointFromPointerEvent(event);
4882
+ beginDragAtScreenPoint(point);
4883
+ },
4884
+ [beginDragAtScreenPoint]
4885
+ );
4886
+ const handlePointerMove = useCallback(
4887
+ (event) => {
4888
+ const point = screenPointFromPointerEvent(event);
4889
+ if (dragStateRef.current.kind !== "idle") {
4890
+ applyDragMoveAtScreenPoint(point, point);
4891
+ return;
4892
+ }
4893
+ updateToolCursorPoint(point);
4894
+ },
4895
+ [applyDragMoveAtScreenPoint, updateToolCursorPoint]
4896
+ );
4897
+ const handlePointerUp = useCallback(
4898
+ (event) => {
4899
+ const point = screenPointFromPointerEvent(event);
4900
+ finishDragAtScreenPoint(point);
4901
+ },
4902
+ [finishDragAtScreenPoint]
4903
+ );
4904
+ const panResponder = useMemo(
4905
+ () => PanResponder.create({
4906
+ onStartShouldSetPanResponder: () => true,
4907
+ onMoveShouldSetPanResponder: () => true,
4908
+ onPanResponderGrant: (evt) => {
4909
+ const touches = evt.nativeEvent.touches;
4910
+ const sx = evt.nativeEvent.locationX;
4911
+ const sy = evt.nativeEvent.locationY;
4912
+ if (touches && touches.length >= 2) {
4913
+ hideToolCursor();
4914
+ dragStateRef.current = { kind: "pan" };
4915
+ return;
4916
+ }
4917
+ beginDragAtScreenPoint({ x: sx, y: sy });
4918
+ },
4919
+ onPanResponderMove: (evt) => {
4920
+ const cam = cameraRef.current;
4921
+ if (!cam) return;
4922
+ const touches = evt.nativeEvent.touches;
4923
+ const sx = evt.nativeEvent.locationX;
4924
+ const sy = evt.nativeEvent.locationY;
4925
+ const pageX = evt.nativeEvent.pageX;
4926
+ const pageY = evt.nativeEvent.pageY;
4927
+ if (touches && touches.length >= 2) {
4928
+ hideToolCursor();
4929
+ const t0 = touches[0];
4930
+ const t1 = touches[1];
4931
+ if (t0 && t1) {
4932
+ const dx = t1.pageX - t0.pageX;
4933
+ const dy = t1.pageY - t0.pageY;
4934
+ const dist = Math.hypot(dx, dy);
4935
+ if (lastPinchDist.current != null) {
4936
+ const scale = dist / lastPinchDist.current;
4937
+ const cx = (t0.pageX + t1.pageX) / 2;
4938
+ const cy = (t0.pageY + t1.pageY) / 2;
4939
+ cam.setZoom(cam.zoom * scale, { x: cx, y: cy });
4940
+ requestRender();
4941
+ }
4942
+ lastPinchDist.current = dist;
4943
+ lastPanPoint.current = null;
4931
4944
  }
4932
4945
  return;
4933
4946
  }
4934
- dragStateRef.current = { kind: "idle" };
4947
+ lastPinchDist.current = null;
4948
+ applyDragMoveAtScreenPoint({ x: sx, y: sy }, { x: pageX, y: pageY });
4949
+ },
4950
+ onPanResponderRelease: (evt) => {
4951
+ finishDragAtScreenPoint({
4952
+ x: evt.nativeEvent.locationX,
4953
+ y: evt.nativeEvent.locationY
4954
+ });
4935
4955
  },
4936
4956
  onPanResponderTerminate: () => {
4937
4957
  lastPinchDist.current = null;
@@ -4946,11 +4966,10 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4946
4966
  }
4947
4967
  }),
4948
4968
  [
4949
- screenToWorld,
4969
+ applyDragMoveAtScreenPoint,
4970
+ beginDragAtScreenPoint,
4971
+ finishDragAtScreenPoint,
4950
4972
  requestRender,
4951
- requestSelectToolAfterUse,
4952
- interactive,
4953
- updateToolCursorPoint,
4954
4973
  hideToolCursor
4955
4974
  ]
4956
4975
  );
@@ -4983,7 +5002,9 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4983
5002
  {
4984
5003
  style: { flex: 1, overflow: "hidden" },
4985
5004
  onLayout,
5005
+ onPointerDown: handlePointerDown,
4986
5006
  onPointerMove: handlePointerMove,
5007
+ onPointerUp: handlePointerUp,
4987
5008
  onPointerEnter: handlePointerMove,
4988
5009
  onPointerLeave: hideToolCursor,
4989
5010
  ...panResponder.panHandlers,