canvu-react 0.4.52 → 0.4.54
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 +682 -151
- package/dist/native.cjs.map +1 -1
- package/dist/native.d.cts +19 -0
- package/dist/native.d.ts +19 -0
- package/dist/native.js +682 -151
- package/dist/native.js.map +1 -1
- package/package.json +1 -1
package/dist/native.cjs
CHANGED
|
@@ -1199,6 +1199,9 @@ function cloneVectorSceneItemWithNewId(item) {
|
|
|
1199
1199
|
}
|
|
1200
1200
|
return next;
|
|
1201
1201
|
}
|
|
1202
|
+
function cloneVectorSceneItemsWithNewIds(items) {
|
|
1203
|
+
return items.map(cloneVectorSceneItemWithNewId);
|
|
1204
|
+
}
|
|
1202
1205
|
|
|
1203
1206
|
// src/scene/managed-images.ts
|
|
1204
1207
|
var MANAGED_KEY = "managed";
|
|
@@ -1262,8 +1265,68 @@ function rotateManagedImage(items, id) {
|
|
|
1262
1265
|
function deleteManagedImage(items, id) {
|
|
1263
1266
|
return restackManagedImages(items.filter((i) => i.id !== id));
|
|
1264
1267
|
}
|
|
1268
|
+
function reorderManagedImages(items, orderedManagedIds) {
|
|
1269
|
+
const managedSlots = [];
|
|
1270
|
+
for (let i = 0; i < items.length; i++) {
|
|
1271
|
+
const item = items[i];
|
|
1272
|
+
if (item && isManagedImage(item)) managedSlots.push(i);
|
|
1273
|
+
}
|
|
1274
|
+
if (managedSlots.length !== orderedManagedIds.length) {
|
|
1275
|
+
return [...items];
|
|
1276
|
+
}
|
|
1277
|
+
const byId = new Map(items.map((i) => [i.id, i]));
|
|
1278
|
+
const next = [...items];
|
|
1279
|
+
managedSlots.forEach((slot, k) => {
|
|
1280
|
+
const orderedId = orderedManagedIds[k];
|
|
1281
|
+
if (orderedId === void 0) return;
|
|
1282
|
+
const replacement = byId.get(orderedId);
|
|
1283
|
+
if (replacement) next[slot] = replacement;
|
|
1284
|
+
});
|
|
1285
|
+
return restackManagedImages(next);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
// src/native/native-images-menu-reorder.ts
|
|
1289
|
+
function moveNativeManagedImageId(managedIds, fromIndex, toIndex) {
|
|
1290
|
+
const next = [...managedIds];
|
|
1291
|
+
if (fromIndex === toIndex || fromIndex < 0 || toIndex < 0 || fromIndex >= next.length || toIndex >= next.length) {
|
|
1292
|
+
return next;
|
|
1293
|
+
}
|
|
1294
|
+
const [item] = next.splice(fromIndex, 1);
|
|
1295
|
+
if (item === void 0) return next;
|
|
1296
|
+
next.splice(toIndex, 0, item);
|
|
1297
|
+
return next;
|
|
1298
|
+
}
|
|
1299
|
+
function getNativeImagesMenuReorderIndex({
|
|
1300
|
+
activeId,
|
|
1301
|
+
managedIds,
|
|
1302
|
+
rowLayouts,
|
|
1303
|
+
dragDeltaY
|
|
1304
|
+
}) {
|
|
1305
|
+
const currentIndex = managedIds.indexOf(activeId);
|
|
1306
|
+
if (currentIndex < 0) return -1;
|
|
1307
|
+
const layoutById = new Map(rowLayouts.map((layout) => [layout.id, layout]));
|
|
1308
|
+
const activeLayout = layoutById.get(activeId);
|
|
1309
|
+
if (!activeLayout) return currentIndex;
|
|
1310
|
+
const activeCenterY = activeLayout.y + activeLayout.height / 2 + dragDeltaY;
|
|
1311
|
+
let nextIndex = currentIndex;
|
|
1312
|
+
let closestDistance = Infinity;
|
|
1313
|
+
for (let index = 0; index < managedIds.length; index++) {
|
|
1314
|
+
const id = managedIds[index];
|
|
1315
|
+
if (id === void 0) continue;
|
|
1316
|
+
const layout = layoutById.get(id);
|
|
1317
|
+
if (!layout) continue;
|
|
1318
|
+
const centerY = layout.y + layout.height / 2;
|
|
1319
|
+
const distance = Math.abs(activeCenterY - centerY);
|
|
1320
|
+
if (distance < closestDistance) {
|
|
1321
|
+
closestDistance = distance;
|
|
1322
|
+
nextIndex = index;
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
return nextIndex;
|
|
1326
|
+
}
|
|
1265
1327
|
var defaultLabels = {
|
|
1266
1328
|
title: "Images",
|
|
1329
|
+
dragHandle: "Drag to reorder",
|
|
1267
1330
|
focus: "Focus on canvas",
|
|
1268
1331
|
duplicate: "Duplicate",
|
|
1269
1332
|
rotate: "Rotate",
|
|
@@ -1335,61 +1398,110 @@ function NativeImagesMenuRow({
|
|
|
1335
1398
|
item,
|
|
1336
1399
|
items,
|
|
1337
1400
|
labels,
|
|
1401
|
+
isDragging,
|
|
1402
|
+
isDropTarget,
|
|
1403
|
+
dragDeltaY,
|
|
1338
1404
|
onItemsChange,
|
|
1339
1405
|
onFocusItem,
|
|
1406
|
+
onDragStart,
|
|
1407
|
+
onDragMove,
|
|
1408
|
+
onDragEnd,
|
|
1409
|
+
onRowLayout,
|
|
1340
1410
|
getImageUri,
|
|
1341
1411
|
renderThumbnail,
|
|
1342
1412
|
rowStyle,
|
|
1343
1413
|
actionButtonStyle
|
|
1344
1414
|
}) {
|
|
1345
1415
|
const imageUri = getImageUri(item);
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1416
|
+
const panResponder = react.useMemo(
|
|
1417
|
+
() => reactNative.PanResponder.create({
|
|
1418
|
+
onStartShouldSetPanResponder: () => true,
|
|
1419
|
+
onMoveShouldSetPanResponder: () => true,
|
|
1420
|
+
onPanResponderGrant: () => onDragStart(item.id),
|
|
1421
|
+
onPanResponderMove: (_event, gestureState) => onDragMove(item.id, gestureState.dy),
|
|
1422
|
+
onPanResponderRelease: (_event, gestureState) => onDragEnd(item.id, gestureState.dy),
|
|
1423
|
+
onPanResponderTerminate: (_event, gestureState) => onDragEnd(item.id, gestureState.dy),
|
|
1424
|
+
onPanResponderTerminationRequest: () => false,
|
|
1425
|
+
onShouldBlockNativeResponder: () => true
|
|
1426
|
+
}),
|
|
1427
|
+
[item.id, onDragEnd, onDragMove, onDragStart]
|
|
1428
|
+
);
|
|
1429
|
+
const onLayout = react.useCallback(
|
|
1430
|
+
(event) => {
|
|
1431
|
+
const { height, y } = event.nativeEvent.layout;
|
|
1432
|
+
onRowLayout({ id: item.id, y, height });
|
|
1433
|
+
},
|
|
1434
|
+
[item.id, onRowLayout]
|
|
1435
|
+
);
|
|
1436
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1437
|
+
reactNative.View,
|
|
1438
|
+
{
|
|
1439
|
+
onLayout,
|
|
1440
|
+
style: [
|
|
1441
|
+
styles.row,
|
|
1442
|
+
rowStyle,
|
|
1443
|
+
isDropTarget && styles.dropTargetRow,
|
|
1444
|
+
isDragging && styles.draggingRow,
|
|
1445
|
+
isDragging && { transform: [{ translateY: dragDeltaY }] }
|
|
1446
|
+
],
|
|
1447
|
+
children: [
|
|
1448
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1449
|
+
reactNative.View,
|
|
1450
|
+
{
|
|
1451
|
+
accessibilityRole: "button",
|
|
1452
|
+
accessibilityLabel: labels.dragHandle,
|
|
1453
|
+
style: [styles.handle, isDragging && styles.handleDragging],
|
|
1454
|
+
...panResponder.panHandlers,
|
|
1455
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(NativeImagesMenuIcon, { name: "grip", color: "#94a3b8" })
|
|
1456
|
+
}
|
|
1457
|
+
),
|
|
1458
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1459
|
+
reactNative.Pressable,
|
|
1460
|
+
{
|
|
1461
|
+
accessibilityRole: "button",
|
|
1462
|
+
accessibilityLabel: labels.focus,
|
|
1463
|
+
disabled: !onFocusItem,
|
|
1464
|
+
onPress: () => onFocusItem?.(item),
|
|
1465
|
+
style: styles.thumbnailButton,
|
|
1466
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles.thumbnailBox, children: [
|
|
1467
|
+
renderThumbnail ? renderThumbnail(item) : null,
|
|
1468
|
+
!renderThumbnail && imageUri ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Image, { source: { uri: imageUri }, style: styles.thumbnailImage }) : null
|
|
1469
|
+
] })
|
|
1470
|
+
}
|
|
1471
|
+
),
|
|
1472
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles.actionsColumn, children: [
|
|
1473
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1474
|
+
NativeImagesMenuAction,
|
|
1475
|
+
{
|
|
1476
|
+
label: labels.duplicate,
|
|
1477
|
+
onPress: () => onItemsChange(copyManagedImage(items, item.id)),
|
|
1478
|
+
style: actionButtonStyle,
|
|
1479
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(NativeImagesMenuIcon, { name: "copy" })
|
|
1480
|
+
}
|
|
1481
|
+
),
|
|
1482
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1483
|
+
NativeImagesMenuAction,
|
|
1484
|
+
{
|
|
1485
|
+
label: labels.rotate,
|
|
1486
|
+
onPress: () => onItemsChange(rotateManagedImage(items, item.id)),
|
|
1487
|
+
style: actionButtonStyle,
|
|
1488
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(NativeImagesMenuIcon, { name: "rotate" })
|
|
1489
|
+
}
|
|
1490
|
+
),
|
|
1491
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1492
|
+
NativeImagesMenuAction,
|
|
1493
|
+
{
|
|
1494
|
+
label: labels.delete,
|
|
1495
|
+
danger: true,
|
|
1496
|
+
onPress: () => onItemsChange(deleteManagedImage(items, item.id)),
|
|
1497
|
+
style: actionButtonStyle,
|
|
1498
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(NativeImagesMenuIcon, { name: "trash", color: "#b91c1c" })
|
|
1499
|
+
}
|
|
1500
|
+
)
|
|
1359
1501
|
] })
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1364
|
-
NativeImagesMenuAction,
|
|
1365
|
-
{
|
|
1366
|
-
label: labels.duplicate,
|
|
1367
|
-
onPress: () => onItemsChange(copyManagedImage(items, item.id)),
|
|
1368
|
-
style: actionButtonStyle,
|
|
1369
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(NativeImagesMenuIcon, { name: "copy" })
|
|
1370
|
-
}
|
|
1371
|
-
),
|
|
1372
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1373
|
-
NativeImagesMenuAction,
|
|
1374
|
-
{
|
|
1375
|
-
label: labels.rotate,
|
|
1376
|
-
onPress: () => onItemsChange(rotateManagedImage(items, item.id)),
|
|
1377
|
-
style: actionButtonStyle,
|
|
1378
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(NativeImagesMenuIcon, { name: "rotate" })
|
|
1379
|
-
}
|
|
1380
|
-
),
|
|
1381
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1382
|
-
NativeImagesMenuAction,
|
|
1383
|
-
{
|
|
1384
|
-
label: labels.delete,
|
|
1385
|
-
danger: true,
|
|
1386
|
-
onPress: () => onItemsChange(deleteManagedImage(items, item.id)),
|
|
1387
|
-
style: actionButtonStyle,
|
|
1388
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(NativeImagesMenuIcon, { name: "trash", color: "#b91c1c" })
|
|
1389
|
-
}
|
|
1390
|
-
)
|
|
1391
|
-
] })
|
|
1392
|
-
] });
|
|
1502
|
+
]
|
|
1503
|
+
}
|
|
1504
|
+
);
|
|
1393
1505
|
}
|
|
1394
1506
|
function NativeImagesMenu({
|
|
1395
1507
|
items,
|
|
@@ -1406,7 +1518,69 @@ function NativeImagesMenu({
|
|
|
1406
1518
|
renderThumbnail
|
|
1407
1519
|
}) {
|
|
1408
1520
|
const managed = react.useMemo(() => items.filter(isManagedImage), [items]);
|
|
1521
|
+
const managedIds = react.useMemo(() => managed.map((item) => item.id), [managed]);
|
|
1409
1522
|
const [collapsed, setCollapsed] = react.useState(!defaultOpen);
|
|
1523
|
+
const [dragState, setDragState] = react.useState(null);
|
|
1524
|
+
const rowLayoutsRef = react.useRef(/* @__PURE__ */ new Map());
|
|
1525
|
+
const getRowLayouts = react.useCallback(
|
|
1526
|
+
() => managedIds.map((id) => rowLayoutsRef.current.get(id)).filter((layout) => layout != null),
|
|
1527
|
+
[managedIds]
|
|
1528
|
+
);
|
|
1529
|
+
const handleRowLayout = react.useCallback((layout) => {
|
|
1530
|
+
rowLayoutsRef.current.set(layout.id, layout);
|
|
1531
|
+
}, []);
|
|
1532
|
+
const handleDragStart = react.useCallback(
|
|
1533
|
+
(id) => {
|
|
1534
|
+
const fromIndex = managedIds.indexOf(id);
|
|
1535
|
+
if (fromIndex < 0) return;
|
|
1536
|
+
setDragState({
|
|
1537
|
+
activeId: id,
|
|
1538
|
+
fromIndex,
|
|
1539
|
+
currentIndex: fromIndex,
|
|
1540
|
+
dragDeltaY: 0
|
|
1541
|
+
});
|
|
1542
|
+
},
|
|
1543
|
+
[managedIds]
|
|
1544
|
+
);
|
|
1545
|
+
const handleDragMove = react.useCallback(
|
|
1546
|
+
(id, dragDeltaY) => {
|
|
1547
|
+
setDragState((current) => {
|
|
1548
|
+
if (!current || current.activeId !== id) return current;
|
|
1549
|
+
const currentIndex = getNativeImagesMenuReorderIndex({
|
|
1550
|
+
activeId: id,
|
|
1551
|
+
managedIds,
|
|
1552
|
+
rowLayouts: getRowLayouts(),
|
|
1553
|
+
dragDeltaY
|
|
1554
|
+
});
|
|
1555
|
+
return {
|
|
1556
|
+
...current,
|
|
1557
|
+
currentIndex: currentIndex >= 0 ? currentIndex : current.fromIndex,
|
|
1558
|
+
dragDeltaY
|
|
1559
|
+
};
|
|
1560
|
+
});
|
|
1561
|
+
},
|
|
1562
|
+
[getRowLayouts, managedIds]
|
|
1563
|
+
);
|
|
1564
|
+
const handleDragEnd = react.useCallback(
|
|
1565
|
+
(id, dragDeltaY) => {
|
|
1566
|
+
const fromIndex = managedIds.indexOf(id);
|
|
1567
|
+
const currentIndex = getNativeImagesMenuReorderIndex({
|
|
1568
|
+
activeId: id,
|
|
1569
|
+
managedIds,
|
|
1570
|
+
rowLayouts: getRowLayouts(),
|
|
1571
|
+
dragDeltaY
|
|
1572
|
+
});
|
|
1573
|
+
setDragState(null);
|
|
1574
|
+
if (fromIndex < 0 || currentIndex < 0 || fromIndex === currentIndex) return;
|
|
1575
|
+
const orderedIds = moveNativeManagedImageId(
|
|
1576
|
+
managedIds,
|
|
1577
|
+
fromIndex,
|
|
1578
|
+
currentIndex
|
|
1579
|
+
);
|
|
1580
|
+
onItemsChange(reorderManagedImages(items, orderedIds));
|
|
1581
|
+
},
|
|
1582
|
+
[getRowLayouts, items, managedIds, onItemsChange]
|
|
1583
|
+
);
|
|
1410
1584
|
if (managed.length === 0) return null;
|
|
1411
1585
|
const resolvedLabels = resolveLabels(labels);
|
|
1412
1586
|
if (collapsed) {
|
|
@@ -1451,15 +1625,23 @@ function NativeImagesMenu({
|
|
|
1451
1625
|
{
|
|
1452
1626
|
style: styles.listScroll,
|
|
1453
1627
|
contentContainerStyle: styles.listContent,
|
|
1628
|
+
scrollEnabled: dragState == null,
|
|
1454
1629
|
showsVerticalScrollIndicator: false,
|
|
1455
|
-
children: managed.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1630
|
+
children: managed.map((item, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1456
1631
|
NativeImagesMenuRow,
|
|
1457
1632
|
{
|
|
1458
1633
|
item,
|
|
1459
1634
|
items,
|
|
1460
1635
|
labels: resolvedLabels,
|
|
1636
|
+
isDragging: dragState?.activeId === item.id,
|
|
1637
|
+
isDropTarget: dragState != null && dragState.activeId !== item.id && dragState.currentIndex === index,
|
|
1638
|
+
dragDeltaY: dragState?.activeId === item.id ? dragState.dragDeltaY : 0,
|
|
1461
1639
|
onItemsChange,
|
|
1462
1640
|
onFocusItem,
|
|
1641
|
+
onDragStart: handleDragStart,
|
|
1642
|
+
onDragMove: handleDragMove,
|
|
1643
|
+
onDragEnd: handleDragEnd,
|
|
1644
|
+
onRowLayout: handleRowLayout,
|
|
1463
1645
|
getImageUri,
|
|
1464
1646
|
renderThumbnail,
|
|
1465
1647
|
rowStyle,
|
|
@@ -1519,6 +1701,19 @@ var styles = reactNative.StyleSheet.create({
|
|
|
1519
1701
|
dangerActionButton: {
|
|
1520
1702
|
backgroundColor: "#fef2f2"
|
|
1521
1703
|
},
|
|
1704
|
+
draggingRow: {
|
|
1705
|
+
backgroundColor: "#eef2f7",
|
|
1706
|
+
elevation: 4,
|
|
1707
|
+
opacity: 0.85,
|
|
1708
|
+
zIndex: 1
|
|
1709
|
+
},
|
|
1710
|
+
dropTargetRow: {
|
|
1711
|
+
backgroundColor: "#f8fafc"
|
|
1712
|
+
},
|
|
1713
|
+
handleDragging: {
|
|
1714
|
+
backgroundColor: "#e2e8f0",
|
|
1715
|
+
borderRadius: 6
|
|
1716
|
+
},
|
|
1522
1717
|
handle: {
|
|
1523
1718
|
alignItems: "center",
|
|
1524
1719
|
height: 128,
|
|
@@ -3078,22 +3273,6 @@ function resolveNativeStrokePreviewStyle(tool, previewStrokeStyle) {
|
|
|
3078
3273
|
};
|
|
3079
3274
|
}
|
|
3080
3275
|
|
|
3081
|
-
// src/native/native-stroke-preview.ts
|
|
3082
|
-
function buildNativeStrokePreviewPath(points) {
|
|
3083
|
-
if (points.length < 2) return null;
|
|
3084
|
-
const d = smoothFreehandPointsToPathD(points);
|
|
3085
|
-
return d || null;
|
|
3086
|
-
}
|
|
3087
|
-
function buildNativeFreehandStrokePreviewPayload(points, style, tool) {
|
|
3088
|
-
if (tool === "laser") return null;
|
|
3089
|
-
return computeFreehandSvgPayload(
|
|
3090
|
-
points.map((point) => ({ x: point.x, y: point.y })),
|
|
3091
|
-
style,
|
|
3092
|
-
tool,
|
|
3093
|
-
true
|
|
3094
|
-
);
|
|
3095
|
-
}
|
|
3096
|
-
|
|
3097
3276
|
// src/native/native-vector-interactions.ts
|
|
3098
3277
|
var NATIVE_SELECTION_HANDLE_HIT_RADIUS_PX = 24;
|
|
3099
3278
|
function nativeItemPlacementBounds(item) {
|
|
@@ -3161,6 +3340,26 @@ function pointInNativeSelectedItemBounds(item, worldPoint) {
|
|
|
3161
3340
|
);
|
|
3162
3341
|
return local.x >= 0 && local.x <= bounds.width && local.y >= 0 && local.y <= bounds.height;
|
|
3163
3342
|
}
|
|
3343
|
+
function resolveNativeSelectionContextMenuRequest({
|
|
3344
|
+
interactive,
|
|
3345
|
+
toolId,
|
|
3346
|
+
selectedItems,
|
|
3347
|
+
worldPoint,
|
|
3348
|
+
screenPoint
|
|
3349
|
+
}) {
|
|
3350
|
+
if (!interactive || toolId !== "select") return null;
|
|
3351
|
+
const editableSelectedItems = selectedItems.filter((item) => !item.locked);
|
|
3352
|
+
if (editableSelectedItems.length === 0) return null;
|
|
3353
|
+
const pressedSelectedItem = editableSelectedItems.some(
|
|
3354
|
+
(item) => pointInNativeSelectedItemBounds(item, worldPoint)
|
|
3355
|
+
);
|
|
3356
|
+
if (!pressedSelectedItem) return null;
|
|
3357
|
+
return {
|
|
3358
|
+
itemIds: editableSelectedItems.map((item) => item.id),
|
|
3359
|
+
x: screenPoint.x,
|
|
3360
|
+
y: screenPoint.y
|
|
3361
|
+
};
|
|
3362
|
+
}
|
|
3164
3363
|
function resolveNativeCustomPlacement(toolId, customPlacement, customPlacements) {
|
|
3165
3364
|
if (customPlacement?.toolId === toolId) return customPlacement;
|
|
3166
3365
|
return customPlacements?.find((placement) => placement.toolId === toolId) ?? null;
|
|
@@ -3465,90 +3664,58 @@ function NativeInteractionOverlay({
|
|
|
3465
3664
|
p.tool,
|
|
3466
3665
|
p.style ?? previewStrokeStyle
|
|
3467
3666
|
);
|
|
3468
|
-
const
|
|
3469
|
-
|
|
3470
|
-
|
|
3667
|
+
const payload = computeFreehandSvgPayload(
|
|
3668
|
+
p.points,
|
|
3669
|
+
style,
|
|
3670
|
+
isLaser ? "draw" : p.tool,
|
|
3671
|
+
p.points.length === 2
|
|
3471
3672
|
);
|
|
3472
|
-
if (!
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3673
|
+
if (!payload) return null;
|
|
3674
|
+
if (payload.kind === "circle") {
|
|
3675
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3676
|
+
reactNativeSkia.Circle,
|
|
3677
|
+
{
|
|
3678
|
+
cx: payload.cx,
|
|
3679
|
+
cy: payload.cy,
|
|
3680
|
+
r: payload.r,
|
|
3681
|
+
color: colorWithOpacity(payload.fill, payload.fillOpacity),
|
|
3682
|
+
style: "fill",
|
|
3683
|
+
antiAlias: true
|
|
3684
|
+
}
|
|
3477
3685
|
);
|
|
3478
|
-
if (payload?.kind === "circle") {
|
|
3479
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3480
|
-
reactNativeSkia.Circle,
|
|
3481
|
-
{
|
|
3482
|
-
cx: payload.cx,
|
|
3483
|
-
cy: payload.cy,
|
|
3484
|
-
r: payload.r,
|
|
3485
|
-
color: colorWithOpacity(payload.fill, payload.fillOpacity),
|
|
3486
|
-
style: "fill",
|
|
3487
|
-
antiAlias: true
|
|
3488
|
-
}
|
|
3489
|
-
);
|
|
3490
|
-
}
|
|
3491
|
-
if (payload?.kind === "fillPath") {
|
|
3492
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3493
|
-
reactNativeSkia.Path,
|
|
3494
|
-
{
|
|
3495
|
-
path: payload.d,
|
|
3496
|
-
color: colorWithOpacity(payload.fill, payload.fillOpacity),
|
|
3497
|
-
style: "fill",
|
|
3498
|
-
fillType: "winding",
|
|
3499
|
-
antiAlias: true
|
|
3500
|
-
}
|
|
3501
|
-
);
|
|
3502
|
-
}
|
|
3503
|
-
if (payload?.kind === "strokePath") {
|
|
3504
|
-
const intervals = dashIntervalsFromStrokeDasharray3(
|
|
3505
|
-
payload.strokeDasharray
|
|
3506
|
-
);
|
|
3507
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3508
|
-
reactNativeSkia.Path,
|
|
3509
|
-
{
|
|
3510
|
-
path: payload.d,
|
|
3511
|
-
color: colorWithOpacity(payload.stroke, payload.strokeOpacity),
|
|
3512
|
-
style: "stroke",
|
|
3513
|
-
strokeWidth: payload.strokeWidth,
|
|
3514
|
-
strokeCap: "round",
|
|
3515
|
-
strokeJoin: "round",
|
|
3516
|
-
antiAlias: true,
|
|
3517
|
-
children: intervals ? /* @__PURE__ */ jsxRuntime.jsx(reactNativeSkia.DashPathEffect, { intervals }) : null
|
|
3518
|
-
}
|
|
3519
|
-
);
|
|
3520
|
-
}
|
|
3521
|
-
return null;
|
|
3522
3686
|
}
|
|
3523
|
-
if (
|
|
3524
|
-
const point = p.points[0];
|
|
3525
|
-
if (!point) return null;
|
|
3687
|
+
if (payload.kind === "fillPath") {
|
|
3526
3688
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3527
|
-
reactNativeSkia.
|
|
3689
|
+
reactNativeSkia.Path,
|
|
3528
3690
|
{
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
r: Math.max(0.5, style.strokeWidth / 2),
|
|
3532
|
-
color: strokeColor,
|
|
3691
|
+
path: payload.d,
|
|
3692
|
+
color: colorWithOpacity(payload.fill, payload.fillOpacity),
|
|
3533
3693
|
style: "fill",
|
|
3694
|
+
fillType: "winding",
|
|
3534
3695
|
antiAlias: true
|
|
3535
3696
|
}
|
|
3536
3697
|
);
|
|
3537
3698
|
}
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3699
|
+
if (payload.kind === "strokePath") {
|
|
3700
|
+
const intervals = dashIntervalsFromStrokeDasharray3(payload.strokeDasharray);
|
|
3701
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3702
|
+
reactNativeSkia.Path,
|
|
3703
|
+
{
|
|
3704
|
+
path: payload.d,
|
|
3705
|
+
color: colorWithOpacity(
|
|
3706
|
+
payload.stroke,
|
|
3707
|
+
isLaser ? 0.85 : payload.strokeOpacity
|
|
3708
|
+
),
|
|
3709
|
+
style: "stroke",
|
|
3710
|
+
strokeWidth: payload.strokeWidth,
|
|
3711
|
+
strokeCap: "round",
|
|
3712
|
+
strokeJoin: "round",
|
|
3713
|
+
antiAlias: true,
|
|
3714
|
+
children: intervals ? /* @__PURE__ */ jsxRuntime.jsx(reactNativeSkia.DashPathEffect, { intervals }) : null
|
|
3715
|
+
}
|
|
3716
|
+
);
|
|
3717
|
+
}
|
|
3718
|
+
return null;
|
|
3552
3719
|
}
|
|
3553
3720
|
return null;
|
|
3554
3721
|
}, [placementPreview, previewStrokeStyle, overlayStrokeWorld, marqueeDashWorld]);
|
|
@@ -5112,6 +5279,18 @@ function applyRotationFromPointer(item, startRotation, startPointerAngleRad, poi
|
|
|
5112
5279
|
}
|
|
5113
5280
|
return { ...item, rotation: startRotation + delta };
|
|
5114
5281
|
}
|
|
5282
|
+
function moveItemByDelta(item, dx, dy) {
|
|
5283
|
+
return {
|
|
5284
|
+
...item,
|
|
5285
|
+
x: item.x + dx,
|
|
5286
|
+
y: item.y + dy,
|
|
5287
|
+
bounds: {
|
|
5288
|
+
...item.bounds,
|
|
5289
|
+
x: item.bounds.x + dx,
|
|
5290
|
+
y: item.bounds.y + dy
|
|
5291
|
+
}
|
|
5292
|
+
};
|
|
5293
|
+
}
|
|
5115
5294
|
function resizeItemByHandle(item, start, handle, currentWorld) {
|
|
5116
5295
|
const sb = normalizeRect(start.bounds);
|
|
5117
5296
|
if (!itemAllowsResizeHandle(item, handle)) {
|
|
@@ -5336,6 +5515,39 @@ function hitTestNativeRemotePresence(peers, camera, point) {
|
|
|
5336
5515
|
return null;
|
|
5337
5516
|
}
|
|
5338
5517
|
|
|
5518
|
+
// src/native/native-shape-clipboard.ts
|
|
5519
|
+
var NATIVE_SHAPE_CLIPBOARD_PASTE_OFFSET_WORLD = 24;
|
|
5520
|
+
function copyNativeSelectedShapeItems(items, selectedIds) {
|
|
5521
|
+
if (selectedIds.length === 0) return null;
|
|
5522
|
+
const copies = selectedIds.map((id) => items.find((item) => item.id === id)).filter((item) => item != null);
|
|
5523
|
+
if (copies.length === 0) return null;
|
|
5524
|
+
return copies.map((item) => JSON.parse(JSON.stringify(item)));
|
|
5525
|
+
}
|
|
5526
|
+
function pasteNativeShapeClipboard(input) {
|
|
5527
|
+
if (input.clipboard.length === 0) return null;
|
|
5528
|
+
const clones = cloneVectorSceneItemsWithNewIds(input.clipboard);
|
|
5529
|
+
const moved = clones.map(
|
|
5530
|
+
(item) => moveItemByDelta(
|
|
5531
|
+
item,
|
|
5532
|
+
NATIVE_SHAPE_CLIPBOARD_PASTE_OFFSET_WORLD,
|
|
5533
|
+
NATIVE_SHAPE_CLIPBOARD_PASTE_OFFSET_WORLD
|
|
5534
|
+
)
|
|
5535
|
+
);
|
|
5536
|
+
if (moved.length === 0) return null;
|
|
5537
|
+
return {
|
|
5538
|
+
items: [...input.items, ...moved],
|
|
5539
|
+
selectedIds: moved.map((item) => item.id)
|
|
5540
|
+
};
|
|
5541
|
+
}
|
|
5542
|
+
function duplicateNativeSelectedShapes(input) {
|
|
5543
|
+
const clipboard = copyNativeSelectedShapeItems(input.items, input.selectedIds);
|
|
5544
|
+
if (!clipboard) return null;
|
|
5545
|
+
return pasteNativeShapeClipboard({
|
|
5546
|
+
items: input.items,
|
|
5547
|
+
clipboard
|
|
5548
|
+
});
|
|
5549
|
+
}
|
|
5550
|
+
|
|
5339
5551
|
// src/native/native-transient-items.ts
|
|
5340
5552
|
function moveNativeTransientItems({
|
|
5341
5553
|
items,
|
|
@@ -5384,6 +5596,11 @@ var NATIVE_VIEWPORT_OVERLAY_Z_INDEX = 40;
|
|
|
5384
5596
|
var NATIVE_VIEWPORT_OVERLAY_ELEVATION = 40;
|
|
5385
5597
|
var NATIVE_VIEWPORT_EXTERNAL_OVERLAY_Z_INDEX = 20;
|
|
5386
5598
|
var NATIVE_VIEWPORT_EXTERNAL_OVERLAY_ELEVATION = 20;
|
|
5599
|
+
var LONG_PRESS_CONTEXT_MENU_MS = 520;
|
|
5600
|
+
var LONG_PRESS_CONTEXT_MENU_CANCEL_PX = 10;
|
|
5601
|
+
var NATIVE_CONTEXT_MENU_WIDTH = 304;
|
|
5602
|
+
var NATIVE_CONTEXT_MENU_HEIGHT = 52;
|
|
5603
|
+
var NATIVE_CONTEXT_MENU_MARGIN = 12;
|
|
5387
5604
|
function isPlacementTool(toolId) {
|
|
5388
5605
|
return toolId === "rect" || toolId === "ellipse" || toolId === "architectural-cloud" || toolId === "line" || toolId === "arrow";
|
|
5389
5606
|
}
|
|
@@ -5456,6 +5673,102 @@ function fitCameraToWorldRect(camera, viewportW, viewportH, worldRect, padding)
|
|
|
5456
5673
|
camera.x = viewportW / 2 - cx * z;
|
|
5457
5674
|
camera.y = viewportH / 2 - cy * z;
|
|
5458
5675
|
}
|
|
5676
|
+
function clampNativeContextMenuState(menu, size) {
|
|
5677
|
+
return {
|
|
5678
|
+
...menu,
|
|
5679
|
+
x: Math.max(
|
|
5680
|
+
NATIVE_CONTEXT_MENU_MARGIN,
|
|
5681
|
+
Math.min(
|
|
5682
|
+
menu.x,
|
|
5683
|
+
Math.max(NATIVE_CONTEXT_MENU_MARGIN, size.width - NATIVE_CONTEXT_MENU_WIDTH)
|
|
5684
|
+
)
|
|
5685
|
+
),
|
|
5686
|
+
y: Math.max(
|
|
5687
|
+
NATIVE_CONTEXT_MENU_MARGIN,
|
|
5688
|
+
Math.min(
|
|
5689
|
+
menu.y - NATIVE_CONTEXT_MENU_HEIGHT - NATIVE_CONTEXT_MENU_MARGIN,
|
|
5690
|
+
Math.max(
|
|
5691
|
+
NATIVE_CONTEXT_MENU_MARGIN,
|
|
5692
|
+
size.height - NATIVE_CONTEXT_MENU_HEIGHT - NATIVE_CONTEXT_MENU_MARGIN
|
|
5693
|
+
)
|
|
5694
|
+
)
|
|
5695
|
+
)
|
|
5696
|
+
};
|
|
5697
|
+
}
|
|
5698
|
+
function NativeSelectionContextMenu({
|
|
5699
|
+
x,
|
|
5700
|
+
y,
|
|
5701
|
+
canPaste,
|
|
5702
|
+
onCopy,
|
|
5703
|
+
onPaste,
|
|
5704
|
+
onDuplicate,
|
|
5705
|
+
onDelete
|
|
5706
|
+
}) {
|
|
5707
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5708
|
+
reactNative.View,
|
|
5709
|
+
{
|
|
5710
|
+
style: [
|
|
5711
|
+
styles4.nativeSelectionContextMenu,
|
|
5712
|
+
{
|
|
5713
|
+
left: x,
|
|
5714
|
+
top: y
|
|
5715
|
+
}
|
|
5716
|
+
],
|
|
5717
|
+
children: [
|
|
5718
|
+
/* @__PURE__ */ jsxRuntime.jsx(NativeSelectionContextMenuButton, { label: "Copy", onPress: onCopy }),
|
|
5719
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5720
|
+
NativeSelectionContextMenuButton,
|
|
5721
|
+
{
|
|
5722
|
+
label: "Paste",
|
|
5723
|
+
onPress: onPaste,
|
|
5724
|
+
disabled: !canPaste
|
|
5725
|
+
}
|
|
5726
|
+
),
|
|
5727
|
+
/* @__PURE__ */ jsxRuntime.jsx(NativeSelectionContextMenuButton, { label: "Duplicate", onPress: onDuplicate }),
|
|
5728
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5729
|
+
NativeSelectionContextMenuButton,
|
|
5730
|
+
{
|
|
5731
|
+
label: "Delete",
|
|
5732
|
+
onPress: onDelete,
|
|
5733
|
+
destructive: true
|
|
5734
|
+
}
|
|
5735
|
+
)
|
|
5736
|
+
]
|
|
5737
|
+
}
|
|
5738
|
+
);
|
|
5739
|
+
}
|
|
5740
|
+
function NativeSelectionContextMenuButton({
|
|
5741
|
+
label,
|
|
5742
|
+
onPress,
|
|
5743
|
+
disabled = false,
|
|
5744
|
+
destructive = false
|
|
5745
|
+
}) {
|
|
5746
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
5747
|
+
reactNative.Pressable,
|
|
5748
|
+
{
|
|
5749
|
+
accessibilityRole: "button",
|
|
5750
|
+
accessibilityState: { disabled },
|
|
5751
|
+
disabled,
|
|
5752
|
+
onPress,
|
|
5753
|
+
style: ({ pressed }) => [
|
|
5754
|
+
styles4.nativeSelectionContextMenuButton,
|
|
5755
|
+
pressed && !disabled ? styles4.nativeSelectionContextMenuButtonPressed : void 0,
|
|
5756
|
+
disabled ? styles4.nativeSelectionContextMenuButtonDisabled : void 0
|
|
5757
|
+
],
|
|
5758
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
5759
|
+
reactNative.Text,
|
|
5760
|
+
{
|
|
5761
|
+
style: [
|
|
5762
|
+
styles4.nativeSelectionContextMenuButtonText,
|
|
5763
|
+
destructive ? styles4.nativeSelectionContextMenuDeleteText : void 0,
|
|
5764
|
+
disabled ? styles4.nativeSelectionContextMenuButtonTextDisabled : void 0
|
|
5765
|
+
],
|
|
5766
|
+
children: label
|
|
5767
|
+
}
|
|
5768
|
+
)
|
|
5769
|
+
}
|
|
5770
|
+
);
|
|
5771
|
+
}
|
|
5459
5772
|
var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
5460
5773
|
items,
|
|
5461
5774
|
selectedIds = [],
|
|
@@ -5522,6 +5835,16 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
5522
5835
|
selectedIdsRef.current = selectedIds;
|
|
5523
5836
|
const remotePresenceRef = react.useRef(remotePresence);
|
|
5524
5837
|
remotePresenceRef.current = remotePresence;
|
|
5838
|
+
const shapeClipboardRef = react.useRef(null);
|
|
5839
|
+
const [selectionContextMenu, setSelectionContextMenu] = react.useState(null);
|
|
5840
|
+
const selectionContextMenuRef = react.useRef(
|
|
5841
|
+
null
|
|
5842
|
+
);
|
|
5843
|
+
selectionContextMenuRef.current = selectionContextMenu;
|
|
5844
|
+
const contextMenuLongPressTimerRef = react.useRef(
|
|
5845
|
+
null
|
|
5846
|
+
);
|
|
5847
|
+
const contextMenuLongPressStartRef = react.useRef(null);
|
|
5525
5848
|
const dragStateRef = react.useRef({ kind: "idle" });
|
|
5526
5849
|
react.useEffect(() => {
|
|
5527
5850
|
const committedItems = items;
|
|
@@ -5539,6 +5862,16 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
5539
5862
|
transientItemsRef.current = null;
|
|
5540
5863
|
setTransientItems(null);
|
|
5541
5864
|
}, []);
|
|
5865
|
+
const clearNativeContextMenuLongPress = react.useCallback(() => {
|
|
5866
|
+
if (contextMenuLongPressTimerRef.current) {
|
|
5867
|
+
clearTimeout(contextMenuLongPressTimerRef.current);
|
|
5868
|
+
contextMenuLongPressTimerRef.current = null;
|
|
5869
|
+
}
|
|
5870
|
+
contextMenuLongPressStartRef.current = null;
|
|
5871
|
+
}, []);
|
|
5872
|
+
const closeNativeSelectionContextMenu = react.useCallback(() => {
|
|
5873
|
+
setSelectionContextMenu(null);
|
|
5874
|
+
}, []);
|
|
5542
5875
|
const commitTransientItemsPreview = react.useCallback(() => {
|
|
5543
5876
|
const nextItems = transientItemsRef.current;
|
|
5544
5877
|
const change = onItemsChangeRef.current;
|
|
@@ -5571,6 +5904,9 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
5571
5904
|
if (laserClearTimerRef.current) {
|
|
5572
5905
|
clearTimeout(laserClearTimerRef.current);
|
|
5573
5906
|
}
|
|
5907
|
+
if (contextMenuLongPressTimerRef.current) {
|
|
5908
|
+
clearTimeout(contextMenuLongPressTimerRef.current);
|
|
5909
|
+
}
|
|
5574
5910
|
},
|
|
5575
5911
|
[]
|
|
5576
5912
|
);
|
|
@@ -5655,6 +5991,10 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
5655
5991
|
}
|
|
5656
5992
|
const camera = cameraRef.current;
|
|
5657
5993
|
const [cameraTick, setCameraTick] = react.useState(0);
|
|
5994
|
+
const selectedItems = react.useMemo(
|
|
5995
|
+
() => activeItems.filter((item) => selectedIds.includes(item.id)),
|
|
5996
|
+
[activeItems, selectedIds]
|
|
5997
|
+
);
|
|
5658
5998
|
const screenToWorld = react.useCallback(
|
|
5659
5999
|
(sx, sy) => {
|
|
5660
6000
|
const cam = cameraRef.current;
|
|
@@ -5663,6 +6003,59 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
5663
6003
|
},
|
|
5664
6004
|
[]
|
|
5665
6005
|
);
|
|
6006
|
+
const startNativeContextMenuLongPress = react.useCallback(
|
|
6007
|
+
(point) => {
|
|
6008
|
+
clearNativeContextMenuLongPress();
|
|
6009
|
+
if (toolIdRef.current !== "select") return;
|
|
6010
|
+
const { worldX, worldY } = screenToWorld(point.x, point.y);
|
|
6011
|
+
const request = resolveNativeSelectionContextMenuRequest({
|
|
6012
|
+
interactive,
|
|
6013
|
+
toolId: toolIdRef.current,
|
|
6014
|
+
selectedItems,
|
|
6015
|
+
worldPoint: { x: worldX, y: worldY },
|
|
6016
|
+
screenPoint: point
|
|
6017
|
+
});
|
|
6018
|
+
if (!request) return;
|
|
6019
|
+
contextMenuLongPressStartRef.current = point;
|
|
6020
|
+
contextMenuLongPressTimerRef.current = setTimeout(() => {
|
|
6021
|
+
contextMenuLongPressTimerRef.current = null;
|
|
6022
|
+
contextMenuLongPressStartRef.current = null;
|
|
6023
|
+
dragStateRef.current = { kind: "idle" };
|
|
6024
|
+
lastPanPoint.current = null;
|
|
6025
|
+
lastPinchDist.current = null;
|
|
6026
|
+
clearTransientItemsPreview();
|
|
6027
|
+
setRealtimePlacementPreview(null);
|
|
6028
|
+
setSelectionContextMenu(
|
|
6029
|
+
clampNativeContextMenuState(
|
|
6030
|
+
{
|
|
6031
|
+
...request,
|
|
6032
|
+
canPaste: shapeClipboardRef.current !== null
|
|
6033
|
+
},
|
|
6034
|
+
size
|
|
6035
|
+
)
|
|
6036
|
+
);
|
|
6037
|
+
}, LONG_PRESS_CONTEXT_MENU_MS);
|
|
6038
|
+
},
|
|
6039
|
+
[
|
|
6040
|
+
clearNativeContextMenuLongPress,
|
|
6041
|
+
clearTransientItemsPreview,
|
|
6042
|
+
interactive,
|
|
6043
|
+
screenToWorld,
|
|
6044
|
+
selectedItems,
|
|
6045
|
+
setRealtimePlacementPreview,
|
|
6046
|
+
size
|
|
6047
|
+
]
|
|
6048
|
+
);
|
|
6049
|
+
const cancelNativeContextMenuLongPressAfterMove = react.useCallback(
|
|
6050
|
+
(point) => {
|
|
6051
|
+
const start = contextMenuLongPressStartRef.current;
|
|
6052
|
+
if (!start) return;
|
|
6053
|
+
if (Math.hypot(point.x - start.x, point.y - start.y) > LONG_PRESS_CONTEXT_MENU_CANCEL_PX) {
|
|
6054
|
+
clearNativeContextMenuLongPress();
|
|
6055
|
+
}
|
|
6056
|
+
},
|
|
6057
|
+
[clearNativeContextMenuLongPress]
|
|
6058
|
+
);
|
|
5666
6059
|
const notifyWorldPointerMove = react.useCallback(
|
|
5667
6060
|
(point) => {
|
|
5668
6061
|
const { worldX, worldY } = screenToWorld(point.x, point.y);
|
|
@@ -5685,10 +6078,6 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
5685
6078
|
}, []);
|
|
5686
6079
|
const hideToolCursor = react.useCallback(() => {
|
|
5687
6080
|
}, []);
|
|
5688
|
-
const selectedItems = react.useMemo(
|
|
5689
|
-
() => activeItems.filter((item) => selectedIds.includes(item.id)),
|
|
5690
|
-
[activeItems, selectedIds]
|
|
5691
|
-
);
|
|
5692
6081
|
const selectedStyleInspectorState = react.useMemo(() => {
|
|
5693
6082
|
const styleableItems = selectedItems.filter(
|
|
5694
6083
|
(item) => !item.locked && getNativeStyleInspectorToolId(item) !== null
|
|
@@ -5733,10 +6122,77 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
5733
6122
|
},
|
|
5734
6123
|
[patchCurrentStrokeStyle]
|
|
5735
6124
|
);
|
|
6125
|
+
const copySelectedShapes = react.useCallback(() => {
|
|
6126
|
+
if (!interactive) return false;
|
|
6127
|
+
const clipboard = copyNativeSelectedShapeItems(
|
|
6128
|
+
itemsRef.current,
|
|
6129
|
+
selectedIdsRef.current
|
|
6130
|
+
);
|
|
6131
|
+
if (!clipboard) return false;
|
|
6132
|
+
shapeClipboardRef.current = clipboard;
|
|
6133
|
+
return true;
|
|
6134
|
+
}, [interactive]);
|
|
6135
|
+
const pasteCopiedShapes = react.useCallback(() => {
|
|
6136
|
+
if (!interactive) return false;
|
|
6137
|
+
const change = onItemsChangeRef.current;
|
|
6138
|
+
const clipboard = shapeClipboardRef.current;
|
|
6139
|
+
if (!change || !clipboard) return false;
|
|
6140
|
+
const pasted = pasteNativeShapeClipboard({
|
|
6141
|
+
items: itemsRef.current,
|
|
6142
|
+
clipboard
|
|
6143
|
+
});
|
|
6144
|
+
if (!pasted) return false;
|
|
6145
|
+
change(pasted.items);
|
|
6146
|
+
onSelectionChangeRef.current?.(pasted.selectedIds);
|
|
6147
|
+
return true;
|
|
6148
|
+
}, [interactive]);
|
|
6149
|
+
const duplicateSelectedShapes = react.useCallback(() => {
|
|
6150
|
+
if (!interactive) return false;
|
|
6151
|
+
const change = onItemsChangeRef.current;
|
|
6152
|
+
if (!change) return false;
|
|
6153
|
+
const duplicated = duplicateNativeSelectedShapes({
|
|
6154
|
+
items: itemsRef.current,
|
|
6155
|
+
selectedIds: selectedIdsRef.current
|
|
6156
|
+
});
|
|
6157
|
+
if (!duplicated) return false;
|
|
6158
|
+
change(duplicated.items);
|
|
6159
|
+
onSelectionChangeRef.current?.(duplicated.selectedIds);
|
|
6160
|
+
return true;
|
|
6161
|
+
}, [interactive]);
|
|
6162
|
+
const handleCopySelectedShapesFromMenu = react.useCallback(() => {
|
|
6163
|
+
copySelectedShapes();
|
|
6164
|
+
closeNativeSelectionContextMenu();
|
|
6165
|
+
}, [closeNativeSelectionContextMenu, copySelectedShapes]);
|
|
6166
|
+
const handlePasteCopiedShapesFromMenu = react.useCallback(() => {
|
|
6167
|
+
pasteCopiedShapes();
|
|
6168
|
+
closeNativeSelectionContextMenu();
|
|
6169
|
+
}, [closeNativeSelectionContextMenu, pasteCopiedShapes]);
|
|
6170
|
+
const handleDuplicateSelectedShapesFromMenu = react.useCallback(() => {
|
|
6171
|
+
duplicateSelectedShapes();
|
|
6172
|
+
closeNativeSelectionContextMenu();
|
|
6173
|
+
}, [closeNativeSelectionContextMenu, duplicateSelectedShapes]);
|
|
6174
|
+
const handleDeleteSelectedShapes = react.useCallback(() => {
|
|
6175
|
+
const menu = selectionContextMenuRef.current;
|
|
6176
|
+
const ids = menu?.itemIds ?? selectedIdsRef.current;
|
|
6177
|
+
if (ids.length === 0) return false;
|
|
6178
|
+
const change = onItemsChangeRef.current;
|
|
6179
|
+
if (!change) return false;
|
|
6180
|
+
const idSet = new Set(ids);
|
|
6181
|
+
const nextItems = itemsRef.current.filter((item) => !idSet.has(item.id));
|
|
6182
|
+
if (nextItems.length === itemsRef.current.length) return false;
|
|
6183
|
+
change(nextItems);
|
|
6184
|
+
onSelectionChangeRef.current?.(
|
|
6185
|
+
selectedIdsRef.current.filter((id) => !idSet.has(id))
|
|
6186
|
+
);
|
|
6187
|
+
closeNativeSelectionContextMenu();
|
|
6188
|
+
return true;
|
|
6189
|
+
}, [closeNativeSelectionContextMenu]);
|
|
5736
6190
|
const lastPinchDist = react.useRef(null);
|
|
5737
6191
|
const lastPanPoint = react.useRef(null);
|
|
5738
6192
|
const beginDragAtScreenPoint = react.useCallback(
|
|
5739
6193
|
(point) => {
|
|
6194
|
+
closeNativeSelectionContextMenu();
|
|
6195
|
+
startNativeContextMenuLongPress(point);
|
|
5740
6196
|
lastPinchDist.current = null;
|
|
5741
6197
|
lastPanPoint.current = null;
|
|
5742
6198
|
const sx = point.x;
|
|
@@ -5950,15 +6406,18 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
5950
6406
|
dragStateRef.current = { kind: "pan" };
|
|
5951
6407
|
},
|
|
5952
6408
|
[
|
|
6409
|
+
closeNativeSelectionContextMenu,
|
|
5953
6410
|
interactive,
|
|
5954
6411
|
requestSelectToolAfterUse,
|
|
5955
6412
|
screenToWorld,
|
|
5956
6413
|
setRealtimePlacementPreview,
|
|
6414
|
+
startNativeContextMenuLongPress,
|
|
5957
6415
|
updateToolCursorPoint
|
|
5958
6416
|
]
|
|
5959
6417
|
);
|
|
5960
6418
|
const applyDragMoveAtScreenPoint = react.useCallback(
|
|
5961
6419
|
(point, pagePoint) => {
|
|
6420
|
+
cancelNativeContextMenuLongPressAfterMove(point);
|
|
5962
6421
|
const cam = cameraRef.current;
|
|
5963
6422
|
if (!cam) return;
|
|
5964
6423
|
updateToolCursorPoint(point);
|
|
@@ -5984,19 +6443,22 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
5984
6443
|
const dx = worldX - (last?.x ?? worldX);
|
|
5985
6444
|
const dy = worldY - (last?.y ?? worldY);
|
|
5986
6445
|
const shouldAppendPoint = Math.hypot(dx, dy) > 0.5 / cam.zoom;
|
|
5987
|
-
if (
|
|
5988
|
-
|
|
6446
|
+
if (shouldAppendPoint) {
|
|
6447
|
+
pts.push({ x: worldX, y: worldY });
|
|
6448
|
+
}
|
|
5989
6449
|
if (st.tool === "laser") {
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
6450
|
+
if (shouldAppendPoint) {
|
|
6451
|
+
setLaserTrail((prev) => [
|
|
6452
|
+
...prev,
|
|
6453
|
+
{ x: worldX, y: worldY, t: Date.now() }
|
|
6454
|
+
]);
|
|
6455
|
+
}
|
|
5994
6456
|
return;
|
|
5995
6457
|
}
|
|
5996
6458
|
setRealtimePlacementPreview({
|
|
5997
6459
|
kind: "stroke",
|
|
5998
6460
|
tool: st.tool,
|
|
5999
|
-
points: pts
|
|
6461
|
+
points: [...pts],
|
|
6000
6462
|
style: { ...strokeStyleRef.current }
|
|
6001
6463
|
});
|
|
6002
6464
|
return;
|
|
@@ -6094,6 +6556,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
6094
6556
|
}
|
|
6095
6557
|
},
|
|
6096
6558
|
[
|
|
6559
|
+
cancelNativeContextMenuLongPressAfterMove,
|
|
6097
6560
|
requestRender,
|
|
6098
6561
|
screenToWorld,
|
|
6099
6562
|
setRealtimePlacementPreview,
|
|
@@ -6103,6 +6566,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
6103
6566
|
);
|
|
6104
6567
|
const finishDragAtScreenPoint = react.useCallback(
|
|
6105
6568
|
(point) => {
|
|
6569
|
+
clearNativeContextMenuLongPress();
|
|
6106
6570
|
lastPinchDist.current = null;
|
|
6107
6571
|
lastPanPoint.current = null;
|
|
6108
6572
|
updateToolCursorPoint(point);
|
|
@@ -6350,6 +6814,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
6350
6814
|
dragStateRef.current = { kind: "idle" };
|
|
6351
6815
|
},
|
|
6352
6816
|
[
|
|
6817
|
+
clearNativeContextMenuLongPress,
|
|
6353
6818
|
requestSelectToolAfterNativeLinkUse,
|
|
6354
6819
|
requestSelectToolAfterUse,
|
|
6355
6820
|
screenToWorld,
|
|
@@ -6393,6 +6858,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
6393
6858
|
const sx = evt.nativeEvent.locationX;
|
|
6394
6859
|
const sy = evt.nativeEvent.locationY;
|
|
6395
6860
|
if (touches && touches.length >= 2) {
|
|
6861
|
+
clearNativeContextMenuLongPress();
|
|
6396
6862
|
hideToolCursor();
|
|
6397
6863
|
notifyWorldPointerLeave();
|
|
6398
6864
|
dragStateRef.current = { kind: "pan" };
|
|
@@ -6409,6 +6875,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
6409
6875
|
const pageX = evt.nativeEvent.pageX;
|
|
6410
6876
|
const pageY = evt.nativeEvent.pageY;
|
|
6411
6877
|
if (touches && touches.length >= 2) {
|
|
6878
|
+
clearNativeContextMenuLongPress();
|
|
6412
6879
|
hideToolCursor();
|
|
6413
6880
|
notifyWorldPointerLeave();
|
|
6414
6881
|
const t0 = touches[0];
|
|
@@ -6439,6 +6906,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
6439
6906
|
});
|
|
6440
6907
|
},
|
|
6441
6908
|
onPanResponderTerminate: () => {
|
|
6909
|
+
clearNativeContextMenuLongPress();
|
|
6442
6910
|
lastPinchDist.current = null;
|
|
6443
6911
|
lastPanPoint.current = null;
|
|
6444
6912
|
hideToolCursor();
|
|
@@ -6455,6 +6923,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
6455
6923
|
[
|
|
6456
6924
|
applyDragMoveAtScreenPoint,
|
|
6457
6925
|
beginDragAtScreenPoint,
|
|
6926
|
+
clearNativeContextMenuLongPress,
|
|
6458
6927
|
finishDragAtScreenPoint,
|
|
6459
6928
|
requestRender,
|
|
6460
6929
|
hideToolCursor,
|
|
@@ -6480,9 +6949,18 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
6480
6949
|
options?.padding ?? 0.08
|
|
6481
6950
|
);
|
|
6482
6951
|
requestRender();
|
|
6483
|
-
}
|
|
6952
|
+
},
|
|
6953
|
+
copySelectedShapes,
|
|
6954
|
+
pasteCopiedShapes,
|
|
6955
|
+
duplicateSelectedShapes
|
|
6484
6956
|
}),
|
|
6485
|
-
[
|
|
6957
|
+
[
|
|
6958
|
+
copySelectedShapes,
|
|
6959
|
+
duplicateSelectedShapes,
|
|
6960
|
+
pasteCopiedShapes,
|
|
6961
|
+
requestRender,
|
|
6962
|
+
size
|
|
6963
|
+
]
|
|
6486
6964
|
);
|
|
6487
6965
|
const activeStyleToolId = toolId === "draw" || toolId === "marker" ? toolId : null;
|
|
6488
6966
|
const styleInspectorToolId = selectedStyleInspectorState?.toolId ?? activeStyleToolId;
|
|
@@ -6600,7 +7078,17 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
6600
7078
|
pointerEvents: "box-none",
|
|
6601
7079
|
children: toolbar
|
|
6602
7080
|
}
|
|
6603
|
-
)
|
|
7081
|
+
),
|
|
7082
|
+
interactive && selectionContextMenu ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
7083
|
+
NativeSelectionContextMenu,
|
|
7084
|
+
{
|
|
7085
|
+
...selectionContextMenu,
|
|
7086
|
+
onCopy: handleCopySelectedShapesFromMenu,
|
|
7087
|
+
onPaste: handlePasteCopiedShapesFromMenu,
|
|
7088
|
+
onDuplicate: handleDuplicateSelectedShapesFromMenu,
|
|
7089
|
+
onDelete: handleDeleteSelectedShapes
|
|
7090
|
+
}
|
|
7091
|
+
) : null
|
|
6604
7092
|
] }),
|
|
6605
7093
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6606
7094
|
reactNative.Modal,
|
|
@@ -6663,6 +7151,49 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
6663
7151
|
] });
|
|
6664
7152
|
});
|
|
6665
7153
|
var styles4 = reactNative.StyleSheet.create({
|
|
7154
|
+
nativeSelectionContextMenu: {
|
|
7155
|
+
position: "absolute",
|
|
7156
|
+
width: NATIVE_CONTEXT_MENU_WIDTH,
|
|
7157
|
+
minHeight: NATIVE_CONTEXT_MENU_HEIGHT,
|
|
7158
|
+
flexDirection: "row",
|
|
7159
|
+
alignItems: "center",
|
|
7160
|
+
justifyContent: "space-between",
|
|
7161
|
+
padding: 4,
|
|
7162
|
+
borderRadius: 14,
|
|
7163
|
+
borderWidth: reactNative.StyleSheet.hairlineWidth,
|
|
7164
|
+
borderColor: "#d4d4d8",
|
|
7165
|
+
backgroundColor: "#ffffff",
|
|
7166
|
+
shadowColor: "#000000",
|
|
7167
|
+
shadowOpacity: 0.16,
|
|
7168
|
+
shadowRadius: 18,
|
|
7169
|
+
shadowOffset: { width: 0, height: 8 },
|
|
7170
|
+
elevation: NATIVE_VIEWPORT_OVERLAY_ELEVATION + 4,
|
|
7171
|
+
zIndex: NATIVE_VIEWPORT_OVERLAY_Z_INDEX + 4
|
|
7172
|
+
},
|
|
7173
|
+
nativeSelectionContextMenuButton: {
|
|
7174
|
+
minHeight: 42,
|
|
7175
|
+
alignItems: "center",
|
|
7176
|
+
justifyContent: "center",
|
|
7177
|
+
borderRadius: 10,
|
|
7178
|
+
paddingHorizontal: 10
|
|
7179
|
+
},
|
|
7180
|
+
nativeSelectionContextMenuButtonPressed: {
|
|
7181
|
+
backgroundColor: "#f4f4f5"
|
|
7182
|
+
},
|
|
7183
|
+
nativeSelectionContextMenuButtonDisabled: {
|
|
7184
|
+
opacity: 0.45
|
|
7185
|
+
},
|
|
7186
|
+
nativeSelectionContextMenuButtonText: {
|
|
7187
|
+
color: "#18181b",
|
|
7188
|
+
fontSize: 14,
|
|
7189
|
+
fontWeight: "700"
|
|
7190
|
+
},
|
|
7191
|
+
nativeSelectionContextMenuButtonTextDisabled: {
|
|
7192
|
+
color: "#71717a"
|
|
7193
|
+
},
|
|
7194
|
+
nativeSelectionContextMenuDeleteText: {
|
|
7195
|
+
color: "#dc2626"
|
|
7196
|
+
},
|
|
6666
7197
|
nativeLinkDialogBackdrop: {
|
|
6667
7198
|
flex: 1,
|
|
6668
7199
|
alignItems: "center",
|