canvu-react 0.4.19 → 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
@@ -4343,6 +4343,196 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4343
4343
  const showResizeHandles = interactive && selectedItems.length === 1 && !selectedItems[0]?.locked && supportsNativeResizeHandles(selectedItems[0]);
4344
4344
  const lastPinchDist = useRef(null);
4345
4345
  const lastPanPoint = useRef(null);
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
+ });
4380
+ dragStateRef.current = {
4381
+ kind: "rotate",
4382
+ id: selectedItem.id,
4383
+ snapshot: selectedItem,
4384
+ ...rotationStart
4385
+ };
4386
+ return;
4387
+ }
4388
+ dragStateRef.current = {
4389
+ kind: "resize",
4390
+ id: selectedItem.id,
4391
+ handle: selectionHandle.handle,
4392
+ snapshot: selectedItem,
4393
+ start: {
4394
+ bounds: selectedItem.bounds,
4395
+ line: selectedItem.line
4396
+ }
4397
+ };
4398
+ return;
4399
+ }
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;
4411
+ }
4412
+ dragStateRef.current = {
4413
+ kind: "move",
4414
+ ids,
4415
+ snapshots,
4416
+ startWorld: { x: worldX, y: worldY }
4417
+ };
4418
+ if (!cur.includes(hit.id)) {
4419
+ onSelectionChangeRef.current?.([hit.id]);
4420
+ }
4421
+ } else {
4422
+ onSelectionChangeRef.current?.([]);
4423
+ dragStateRef.current = {
4424
+ kind: "marquee",
4425
+ startWorld: { x: worldX, y: worldY }
4426
+ };
4427
+ setPlacementPreview({
4428
+ kind: "marquee",
4429
+ rect: { x: worldX, y: worldY, width: 0, height: 0 }
4430
+ });
4431
+ }
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;
4444
+ }
4445
+ setLaserTrail([{ x: worldX, y: worldY, t: Date.now() }]);
4446
+ } else {
4447
+ setPlacementPreview({
4448
+ kind: "stroke",
4449
+ tool,
4450
+ points: [{ x: worldX, y: worldY }],
4451
+ style: { ...strokeStyleRef.current }
4452
+ });
4453
+ }
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);
4469
+ }
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
+ {
4485
+ x: worldX,
4486
+ y: worldY
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
+ );
4346
4536
  const applyDragMoveAtScreenPoint = useCallback(
4347
4537
  (point, pagePoint) => {
4348
4538
  const cam = cameraRef.current;
@@ -4479,6 +4669,220 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4479
4669
  },
4480
4670
  [requestRender, screenToWorld, updateToolCursorPoint]
4481
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);
4684
+ }
4685
+ laserClearTimerRef.current = setTimeout(() => {
4686
+ setLaserTrail([]);
4687
+ laserClearTimerRef.current = null;
4688
+ }, 650);
4689
+ requestSelectToolAfterUse();
4690
+ return;
4691
+ }
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]);
4704
+ }
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)));
4739
+ }
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) {
4758
+ return;
4759
+ }
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;
4773
+ }
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]);
4780
+ requestSelectToolAfterUse();
4781
+ return;
4782
+ }
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") {
4790
+ change([
4791
+ ...itemsRef.current,
4792
+ createArchitecturalCloudItem(id, raw, style)
4793
+ ]);
4794
+ onSelectionChangeRef.current?.([id]);
4795
+ requestSelectToolAfterUse();
4796
+ return;
4797
+ }
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") {
4837
+ const id = createShapeId();
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
+ );
4850
+ change([...itemsRef.current, item]);
4851
+ onSelectionChangeRef.current?.([id]);
4852
+ requestSelectToolAfterUse();
4853
+ }
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: {
4861
+ x: st.startWorld.x - 70,
4862
+ y: st.startWorld.y - 40,
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
+ );
4482
4886
  const handlePointerMove = useCallback(
4483
4887
  (event) => {
4484
4888
  const point = screenPointFromPointerEvent(event);
@@ -4490,13 +4894,18 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4490
4894
  },
4491
4895
  [applyDragMoveAtScreenPoint, updateToolCursorPoint]
4492
4896
  );
4897
+ const handlePointerUp = useCallback(
4898
+ (event) => {
4899
+ const point = screenPointFromPointerEvent(event);
4900
+ finishDragAtScreenPoint(point);
4901
+ },
4902
+ [finishDragAtScreenPoint]
4903
+ );
4493
4904
  const panResponder = useMemo(
4494
4905
  () => PanResponder.create({
4495
4906
  onStartShouldSetPanResponder: () => true,
4496
4907
  onMoveShouldSetPanResponder: () => true,
4497
4908
  onPanResponderGrant: (evt) => {
4498
- lastPinchDist.current = null;
4499
- lastPanPoint.current = null;
4500
4909
  const touches = evt.nativeEvent.touches;
4501
4910
  const sx = evt.nativeEvent.locationX;
4502
4911
  const sy = evt.nativeEvent.locationY;
@@ -4505,187 +4914,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4505
4914
  dragStateRef.current = { kind: "pan" };
4506
4915
  return;
4507
4916
  }
4508
- updateToolCursorPoint({ x: sx, y: sy });
4509
- if (!interactive) {
4510
- dragStateRef.current = { kind: "pan" };
4511
- return;
4512
- }
4513
- const tool = toolIdRef.current;
4514
- const cam = cameraRef.current;
4515
- if (!cam) return;
4516
- const { worldX, worldY } = screenToWorld(sx, sy);
4517
- if (tool === "hand") {
4518
- dragStateRef.current = { kind: "pan" };
4519
- return;
4520
- }
4521
- if (tool === "select") {
4522
- const currentSelectedIds = selectedIdsRef.current;
4523
- const selectedItem = currentSelectedIds.length === 1 ? itemsRef.current.find((item) => item.id === currentSelectedIds[0]) : void 0;
4524
- const selectionHandle = hitTestNativeSelectionHandle({
4525
- selectedItem,
4526
- selectedCount: currentSelectedIds.length,
4527
- worldPoint: { x: worldX, y: worldY },
4528
- zoom: cam.zoom
4529
- });
4530
- if (selectionHandle && selectedItem) {
4531
- if (selectionHandle.kind === "rotate") {
4532
- const rotationStart = nativeRotationDragStart({
4533
- item: selectedItem,
4534
- worldPoint: { x: worldX, y: worldY }
4535
- });
4536
- dragStateRef.current = {
4537
- kind: "rotate",
4538
- id: selectedItem.id,
4539
- snapshot: selectedItem,
4540
- ...rotationStart
4541
- };
4542
- return;
4543
- }
4544
- dragStateRef.current = {
4545
- kind: "resize",
4546
- id: selectedItem.id,
4547
- handle: selectionHandle.handle,
4548
- snapshot: selectedItem,
4549
- start: {
4550
- bounds: selectedItem.bounds,
4551
- line: selectedItem.line
4552
- }
4553
- };
4554
- return;
4555
- }
4556
- const hit = hitTestWorldPoint(itemsRef.current, worldX, worldY, {
4557
- lineHitWorld: 10 / cam.zoom,
4558
- ignoreLocked: true
4559
- });
4560
- if (hit) {
4561
- const cur = selectedIdsRef.current;
4562
- const ids = cur.includes(hit.id) ? [...cur] : [hit.id];
4563
- const snapshots = {};
4564
- for (const id of ids) {
4565
- const it = itemsRef.current.find((i) => i.id === id);
4566
- if (it) snapshots[id] = it;
4567
- }
4568
- dragStateRef.current = {
4569
- kind: "move",
4570
- ids,
4571
- snapshots,
4572
- startWorld: { x: worldX, y: worldY }
4573
- };
4574
- if (!cur.includes(hit.id)) {
4575
- onSelectionChangeRef.current?.([hit.id]);
4576
- }
4577
- } else {
4578
- onSelectionChangeRef.current?.([]);
4579
- dragStateRef.current = {
4580
- kind: "marquee",
4581
- startWorld: { x: worldX, y: worldY }
4582
- };
4583
- setPlacementPreview({
4584
- kind: "marquee",
4585
- rect: { x: worldX, y: worldY, width: 0, height: 0 }
4586
- });
4587
- }
4588
- return;
4589
- }
4590
- if (tool === "draw" || tool === "marker" || tool === "laser") {
4591
- dragStateRef.current = {
4592
- kind: "draw",
4593
- tool,
4594
- points: [{ x: worldX, y: worldY }]
4595
- };
4596
- if (tool === "laser") {
4597
- if (laserClearTimerRef.current) {
4598
- clearTimeout(laserClearTimerRef.current);
4599
- laserClearTimerRef.current = null;
4600
- }
4601
- setLaserTrail([{ x: worldX, y: worldY, t: Date.now() }]);
4602
- } else {
4603
- setPlacementPreview({
4604
- kind: "stroke",
4605
- tool,
4606
- points: [{ x: worldX, y: worldY }],
4607
- style: { ...strokeStyleRef.current }
4608
- });
4609
- }
4610
- return;
4611
- }
4612
- if (tool === "eraser") {
4613
- dragStateRef.current = { kind: "erase" };
4614
- eraserPreviewIdSetRef.current = /* @__PURE__ */ new Set();
4615
- setEraserPreviewIds([]);
4616
- setEraserTrail([{ x: worldX, y: worldY, t: Date.now() }]);
4617
- const toErase = collectEraserTargetsAtWorldPoint(
4618
- itemsRef.current,
4619
- worldX,
4620
- worldY,
4621
- { lineHitWorld: 10 / cam.zoom, ignoreLocked: true }
4622
- );
4623
- for (const id of toErase) {
4624
- eraserPreviewIdSetRef.current.add(id);
4625
- }
4626
- setEraserPreviewIds(Array.from(eraserPreviewIdSetRef.current));
4627
- return;
4628
- }
4629
- if (isPlacementTool(tool)) {
4630
- dragStateRef.current = {
4631
- kind: "place",
4632
- tool,
4633
- startWorld: { x: worldX, y: worldY },
4634
- startScreen: { x: sx, y: sy }
4635
- };
4636
- setPlacementPreview(
4637
- placementPreviewForTool(
4638
- tool,
4639
- { x: worldX, y: worldY },
4640
- {
4641
- x: worldX,
4642
- y: worldY
4643
- }
4644
- )
4645
- );
4646
- return;
4647
- }
4648
- const customPlacement2 = resolveNativeCustomPlacement(
4649
- tool,
4650
- customPlacementRef.current,
4651
- customPlacementsRef.current
4652
- );
4653
- if (customPlacement2) {
4654
- dragStateRef.current = {
4655
- kind: "custom-place",
4656
- tool,
4657
- placement: customPlacement2,
4658
- startWorld: { x: worldX, y: worldY },
4659
- startScreen: { x: sx, y: sy }
4660
- };
4661
- setPlacementPreview({
4662
- kind: "rect",
4663
- rect: { x: worldX, y: worldY, width: 0, height: 0 }
4664
- });
4665
- return;
4666
- }
4667
- if (tool === "note" || tool === "text") {
4668
- dragStateRef.current = {
4669
- kind: "tap",
4670
- tool,
4671
- startWorld: { x: worldX, y: worldY },
4672
- startScreen: { x: sx, y: sy }
4673
- };
4674
- return;
4675
- }
4676
- const handleWorldPointerDown = onWorldPointerDownRef.current;
4677
- if (handleWorldPointerDown) {
4678
- handleWorldPointerDown({
4679
- toolId: tool,
4680
- worldX,
4681
- worldY,
4682
- screenX: sx,
4683
- screenY: sy
4684
- });
4685
- requestSelectToolAfterUse();
4686
- return;
4687
- }
4688
- dragStateRef.current = { kind: "pan" };
4917
+ beginDragAtScreenPoint({ x: sx, y: sy });
4689
4918
  },
4690
4919
  onPanResponderMove: (evt) => {
4691
4920
  const cam = cameraRef.current;
@@ -4719,223 +4948,10 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4719
4948
  applyDragMoveAtScreenPoint({ x: sx, y: sy }, { x: pageX, y: pageY });
4720
4949
  },
4721
4950
  onPanResponderRelease: (evt) => {
4722
- lastPinchDist.current = null;
4723
- lastPanPoint.current = null;
4724
- updateToolCursorPoint({
4951
+ finishDragAtScreenPoint({
4725
4952
  x: evt.nativeEvent.locationX,
4726
4953
  y: evt.nativeEvent.locationY
4727
4954
  });
4728
- const st = dragStateRef.current;
4729
- if (st.kind === "draw") {
4730
- dragStateRef.current = { kind: "idle" };
4731
- setPlacementPreview(null);
4732
- if (st.tool === "laser") {
4733
- if (laserClearTimerRef.current) {
4734
- clearTimeout(laserClearTimerRef.current);
4735
- }
4736
- laserClearTimerRef.current = setTimeout(() => {
4737
- setLaserTrail([]);
4738
- laserClearTimerRef.current = null;
4739
- }, 650);
4740
- requestSelectToolAfterUse();
4741
- return;
4742
- }
4743
- if (st.points.length < 1) return;
4744
- const change = onItemsChangeRef.current;
4745
- if (!change) return;
4746
- const id = createShapeId();
4747
- const item = createFreehandStrokeItem(
4748
- id,
4749
- st.points,
4750
- st.tool,
4751
- strokeStyleRef.current
4752
- );
4753
- if (item) {
4754
- change([...itemsRef.current, item]);
4755
- }
4756
- requestSelectToolAfterUse();
4757
- return;
4758
- }
4759
- if (st.kind === "move") {
4760
- dragStateRef.current = { kind: "idle" };
4761
- return;
4762
- }
4763
- if (st.kind === "resize" || st.kind === "rotate") {
4764
- dragStateRef.current = { kind: "idle" };
4765
- return;
4766
- }
4767
- if (st.kind === "marquee") {
4768
- dragStateRef.current = { kind: "idle" };
4769
- setPlacementPreview(null);
4770
- const cam = cameraRef.current;
4771
- if (!cam) return;
4772
- const { worldX, worldY } = screenToWorld(
4773
- evt.nativeEvent.locationX,
4774
- evt.nativeEvent.locationY
4775
- );
4776
- const a = st.startWorld;
4777
- if (typeof worldX !== "number" || typeof worldY !== "number") return;
4778
- const raw = {
4779
- x: Math.min(a.x, worldX),
4780
- y: Math.min(a.y, worldY),
4781
- width: Math.abs(worldX - a.x),
4782
- height: Math.abs(worldY - a.y)
4783
- };
4784
- const picked = collectIdsInRect(itemsRef.current, raw);
4785
- onSelectionChangeRef.current?.(picked);
4786
- return;
4787
- }
4788
- if (st.kind === "erase") {
4789
- const change = onItemsChangeRef.current;
4790
- if (change && eraserPreviewIdSetRef.current.size > 0) {
4791
- const idSet = new Set(eraserPreviewIdSetRef.current);
4792
- change(itemsRef.current.filter((i) => !idSet.has(i.id)));
4793
- }
4794
- eraserPreviewIdSetRef.current.clear();
4795
- setEraserPreviewIds([]);
4796
- setEraserTrail([]);
4797
- dragStateRef.current = { kind: "idle" };
4798
- requestSelectToolAfterUse();
4799
- return;
4800
- }
4801
- if (st.kind === "place") {
4802
- dragStateRef.current = { kind: "idle" };
4803
- setPlacementPreview(null);
4804
- const change = onItemsChangeRef.current;
4805
- if (!change) return;
4806
- const { worldX, worldY } = screenToWorld(
4807
- evt.nativeEvent.locationX,
4808
- evt.nativeEvent.locationY
4809
- );
4810
- const a = st.startWorld;
4811
- const b = { x: worldX, y: worldY };
4812
- const screenDx = evt.nativeEvent.locationX - st.startScreen.x;
4813
- const screenDy = evt.nativeEvent.locationY - st.startScreen.y;
4814
- if (st.tool === "arrow" && Math.hypot(screenDx, screenDy) < MIN_ARROW_DRAG_PX) {
4815
- return;
4816
- }
4817
- let raw = rectFromCorners(a, b);
4818
- let br = normalizeRect(raw);
4819
- let lineStart = a;
4820
- let lineEnd = b;
4821
- if (br.width < MIN_PLACE_SIZE || br.height < MIN_PLACE_SIZE) {
4822
- const center = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
4823
- const defaults = defaultPlacementWorld(st.tool, center);
4824
- raw = defaults.raw;
4825
- br = normalizeRect(raw);
4826
- if (defaults.lineWorld) {
4827
- const [defaultStart, defaultEnd] = defaults.lineWorld;
4828
- lineStart = defaultStart;
4829
- lineEnd = defaultEnd;
4830
- }
4831
- }
4832
- const id = createShapeId();
4833
- const style = strokeStyleRef.current;
4834
- if (st.tool === "rect") {
4835
- change([...itemsRef.current, createRectangleItem(id, raw, style)]);
4836
- onSelectionChangeRef.current?.([id]);
4837
- requestSelectToolAfterUse();
4838
- return;
4839
- }
4840
- if (st.tool === "ellipse") {
4841
- change([...itemsRef.current, createEllipseItem(id, raw, style)]);
4842
- onSelectionChangeRef.current?.([id]);
4843
- requestSelectToolAfterUse();
4844
- return;
4845
- }
4846
- if (st.tool === "architectural-cloud") {
4847
- change([
4848
- ...itemsRef.current,
4849
- createArchitecturalCloudItem(id, raw, style)
4850
- ]);
4851
- onSelectionChangeRef.current?.([id]);
4852
- requestSelectToolAfterUse();
4853
- return;
4854
- }
4855
- const line = lineEndpointsToLocal(br, lineStart, lineEnd);
4856
- change([
4857
- ...itemsRef.current,
4858
- createLineItem(id, br, line, st.tool, style)
4859
- ]);
4860
- onSelectionChangeRef.current?.([id]);
4861
- requestSelectToolAfterUse();
4862
- return;
4863
- }
4864
- if (st.kind === "custom-place") {
4865
- dragStateRef.current = { kind: "idle" };
4866
- setPlacementPreview(null);
4867
- const change = onItemsChangeRef.current;
4868
- if (!change) return;
4869
- const { worldX, worldY } = screenToWorld(
4870
- evt.nativeEvent.locationX,
4871
- evt.nativeEvent.locationY
4872
- );
4873
- const center = {
4874
- x: (st.startWorld.x + worldX) / 2,
4875
- y: (st.startWorld.y + worldY) / 2
4876
- };
4877
- const raw = rectFromCorners(st.startWorld, { x: worldX, y: worldY });
4878
- const normalized = normalizeRect(raw);
4879
- const bounds = normalized.width < MIN_PLACE_SIZE || normalized.height < MIN_PLACE_SIZE ? {
4880
- x: center.x - 60,
4881
- y: center.y - 40,
4882
- width: 120,
4883
- height: 80
4884
- } : normalized;
4885
- const id = createShapeId();
4886
- const item = st.placement.createItem({ id, bounds });
4887
- change([...itemsRef.current, item]);
4888
- onSelectionChangeRef.current?.([id]);
4889
- requestSelectToolAfterUse();
4890
- return;
4891
- }
4892
- if (st.kind === "tap") {
4893
- dragStateRef.current = { kind: "idle" };
4894
- const screenDx = evt.nativeEvent.locationX - st.startScreen.x;
4895
- const screenDy = evt.nativeEvent.locationY - st.startScreen.y;
4896
- if (Math.hypot(screenDx, screenDy) > TAP_PX) return;
4897
- const change = onItemsChangeRef.current;
4898
- if (!change) return;
4899
- if (st.tool === "text") {
4900
- const id = createShapeId();
4901
- const item = createTextItem(
4902
- id,
4903
- {
4904
- x: st.startWorld.x - 4,
4905
- y: st.startWorld.y - 18,
4906
- width: 160,
4907
- height: 26
4908
- },
4909
- "Text",
4910
- strokeStyleRef.current,
4911
- 18
4912
- );
4913
- change([...itemsRef.current, item]);
4914
- onSelectionChangeRef.current?.([id]);
4915
- requestSelectToolAfterUse();
4916
- }
4917
- if (st.tool === "note") {
4918
- const id = createShapeId();
4919
- const note = {
4920
- id,
4921
- x: st.startWorld.x - 70,
4922
- y: st.startWorld.y - 40,
4923
- bounds: {
4924
- x: st.startWorld.x - 70,
4925
- y: st.startWorld.y - 40,
4926
- width: 140,
4927
- height: 80
4928
- },
4929
- 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>`,
4930
- toolKind: "custom"
4931
- };
4932
- change([...itemsRef.current, note]);
4933
- onSelectionChangeRef.current?.([id]);
4934
- requestSelectToolAfterUse();
4935
- }
4936
- return;
4937
- }
4938
- dragStateRef.current = { kind: "idle" };
4939
4955
  },
4940
4956
  onPanResponderTerminate: () => {
4941
4957
  lastPinchDist.current = null;
@@ -4951,11 +4967,9 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4951
4967
  }),
4952
4968
  [
4953
4969
  applyDragMoveAtScreenPoint,
4954
- screenToWorld,
4970
+ beginDragAtScreenPoint,
4971
+ finishDragAtScreenPoint,
4955
4972
  requestRender,
4956
- requestSelectToolAfterUse,
4957
- interactive,
4958
- updateToolCursorPoint,
4959
4973
  hideToolCursor
4960
4974
  ]
4961
4975
  );
@@ -4988,7 +5002,9 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4988
5002
  {
4989
5003
  style: { flex: 1, overflow: "hidden" },
4990
5004
  onLayout,
5005
+ onPointerDown: handlePointerDown,
4991
5006
  onPointerMove: handlePointerMove,
5007
+ onPointerUp: handlePointerUp,
4992
5008
  onPointerEnter: handlePointerMove,
4993
5009
  onPointerLeave: hideToolCursor,
4994
5010
  ...panResponder.panHandlers,