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