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