@nyaruka/temba-components 0.142.1 → 0.142.3
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/CHANGELOG.md +19 -0
- package/dist/temba-components.js +953 -708
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/Icons.js +1 -0
- package/out-tsc/src/Icons.js.map +1 -1
- package/out-tsc/src/flow/CanvasMenu.js +38 -38
- package/out-tsc/src/flow/CanvasMenu.js.map +1 -1
- package/out-tsc/src/flow/CanvasNode.js +171 -17
- package/out-tsc/src/flow/CanvasNode.js.map +1 -1
- package/out-tsc/src/flow/Editor.js +491 -22
- package/out-tsc/src/flow/Editor.js.map +1 -1
- package/out-tsc/src/flow/NodeEditor.js +346 -10
- package/out-tsc/src/flow/NodeEditor.js.map +1 -1
- package/out-tsc/src/flow/NodeTypeSelector.js +2 -0
- package/out-tsc/src/flow/NodeTypeSelector.js.map +1 -1
- package/out-tsc/src/flow/Plumber.js +92 -28
- package/out-tsc/src/flow/Plumber.js.map +1 -1
- package/out-tsc/src/flow/StickyNote.js +63 -3
- package/out-tsc/src/flow/StickyNote.js.map +1 -1
- package/out-tsc/src/flow/actions/add_contact_urn.js +2 -6
- package/out-tsc/src/flow/actions/add_contact_urn.js.map +1 -1
- package/out-tsc/src/flow/actions/enter_flow.js +2 -2
- package/out-tsc/src/flow/actions/enter_flow.js.map +1 -1
- package/out-tsc/src/flow/actions/say_msg.js +2 -1
- package/out-tsc/src/flow/actions/say_msg.js.map +1 -1
- package/out-tsc/src/flow/actions/send_broadcast.js +2 -6
- package/out-tsc/src/flow/actions/send_broadcast.js.map +1 -1
- package/out-tsc/src/flow/actions/send_email.js +2 -6
- package/out-tsc/src/flow/actions/send_email.js.map +1 -1
- package/out-tsc/src/flow/actions/send_msg.js +55 -35
- package/out-tsc/src/flow/actions/send_msg.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_channel.js +2 -1
- package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_field.js +4 -5
- package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_language.js +3 -3
- package/out-tsc/src/flow/actions/set_contact_language.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_name.js +2 -1
- package/out-tsc/src/flow/actions/set_contact_name.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_status.js +2 -1
- package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -1
- package/out-tsc/src/flow/actions/set_run_result.js +3 -3
- package/out-tsc/src/flow/actions/set_run_result.js.map +1 -1
- package/out-tsc/src/flow/actions/start_session.js +2 -2
- package/out-tsc/src/flow/actions/start_session.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_llm.js +4 -5
- package/out-tsc/src/flow/nodes/split_by_llm.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_resthook.js +3 -8
- package/out-tsc/src/flow/nodes/split_by_resthook.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_subflow.js +4 -2
- package/out-tsc/src/flow/nodes/split_by_subflow.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_webhook.js +25 -33
- package/out-tsc/src/flow/nodes/split_by_webhook.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_response.js +1 -0
- package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
- package/out-tsc/src/flow/types.js.map +1 -1
- package/out-tsc/src/flow/utils.js +66 -0
- package/out-tsc/src/flow/utils.js.map +1 -1
- package/out-tsc/src/form/FieldRenderer.js +17 -2
- package/out-tsc/src/form/FieldRenderer.js.map +1 -1
- package/out-tsc/src/interfaces.js +1 -0
- package/out-tsc/src/interfaces.js.map +1 -1
- package/out-tsc/src/list/SortableList.js +104 -43
- package/out-tsc/src/list/SortableList.js.map +1 -1
- package/out-tsc/src/simulator/Simulator.js +6 -2
- package/out-tsc/src/simulator/Simulator.js.map +1 -1
- package/out-tsc/test/temba-canvas-menu.test.js +13 -9
- package/out-tsc/test/temba-canvas-menu.test.js.map +1 -1
- package/out-tsc/test/temba-flow-reflow.test.js.map +1 -1
- package/out-tsc/test/temba-node-editor.test.js +9 -10
- package/out-tsc/test/temba-node-editor.test.js.map +1 -1
- package/out-tsc/test/temba-node-type-selector.test.js +3 -3
- package/out-tsc/test/temba-node-type-selector.test.js.map +1 -1
- package/out-tsc/test/temba-simulator.test.js +2 -2
- package/out-tsc/test/temba-simulator.test.js.map +1 -1
- package/package.json +1 -1
- package/screenshots/truth/actions/enter_flow/render/basic-flow.png +0 -0
- package/screenshots/truth/actions/enter_flow/render/long-flow-name.png +0 -0
- package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
- package/screenshots/truth/actions/start_session/render/contact-query.png +0 -0
- package/screenshots/truth/actions/start_session/render/contacts-only.png +0 -0
- package/screenshots/truth/actions/start_session/render/create-contact.png +0 -0
- package/screenshots/truth/actions/start_session/render/groups-and-contacts.png +0 -0
- package/screenshots/truth/actions/start_session/render/groups-only.png +0 -0
- package/screenshots/truth/actions/start_session/render/many-recipients.png +0 -0
- package/screenshots/truth/canvas-menu/open.png +0 -0
- package/screenshots/truth/node-type-selector/action-mode.png +0 -0
- package/screenshots/truth/node-type-selector/split-mode.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
- package/src/Icons.ts +1 -0
- package/src/flow/CanvasMenu.ts +50 -43
- package/src/flow/CanvasNode.ts +201 -17
- package/src/flow/Editor.ts +585 -25
- package/src/flow/NodeEditor.ts +373 -10
- package/src/flow/NodeTypeSelector.ts +2 -0
- package/src/flow/Plumber.ts +104 -37
- package/src/flow/StickyNote.ts +76 -4
- package/src/flow/actions/add_contact_urn.ts +5 -6
- package/src/flow/actions/enter_flow.ts +2 -2
- package/src/flow/actions/say_msg.ts +2 -1
- package/src/flow/actions/send_broadcast.ts +2 -6
- package/src/flow/actions/send_email.ts +2 -6
- package/src/flow/actions/send_msg.ts +59 -38
- package/src/flow/actions/set_contact_channel.ts +5 -1
- package/src/flow/actions/set_contact_field.ts +10 -5
- package/src/flow/actions/set_contact_language.ts +6 -3
- package/src/flow/actions/set_contact_name.ts +5 -1
- package/src/flow/actions/set_contact_status.ts +5 -1
- package/src/flow/actions/set_run_result.ts +6 -3
- package/src/flow/actions/start_session.ts +2 -2
- package/src/flow/nodes/split_by_llm.ts +5 -5
- package/src/flow/nodes/split_by_resthook.ts +3 -8
- package/src/flow/nodes/split_by_subflow.ts +4 -2
- package/src/flow/nodes/split_by_webhook.ts +26 -34
- package/src/flow/nodes/wait_for_response.ts +1 -0
- package/src/flow/types.ts +25 -2
- package/src/flow/utils.ts +79 -1
- package/src/form/FieldRenderer.ts +32 -3
- package/src/interfaces.ts +1 -0
- package/src/list/SortableList.ts +117 -47
- package/src/simulator/Simulator.ts +6 -2
- package/test/temba-canvas-menu.test.ts +13 -9
- package/test/temba-flow-reflow.test.ts +4 -2
- package/test/temba-node-editor.test.ts +9 -10
- package/test/temba-node-type-selector.test.ts +3 -3
- package/test/temba-simulator.test.ts +2 -2
|
@@ -96,6 +96,29 @@ export class Editor extends RapidElement {
|
|
|
96
96
|
-webkit-font-smoothing: antialiased;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
/* On touch devices, disable native scroll-by-touch so canvas
|
|
100
|
+
drag draws a selection rectangle. Users scroll via scrollbars. */
|
|
101
|
+
#editor.touch-device {
|
|
102
|
+
touch-action: none;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
#editor.touch-device::-webkit-scrollbar {
|
|
106
|
+
-webkit-appearance: none;
|
|
107
|
+
width: 12px;
|
|
108
|
+
height: 12px;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
#editor.touch-device::-webkit-scrollbar-thumb {
|
|
112
|
+
background: rgba(0, 0, 0, 0.3);
|
|
113
|
+
border-radius: 6px;
|
|
114
|
+
border: 2px solid transparent;
|
|
115
|
+
background-clip: padding-box;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
#editor.touch-device::-webkit-scrollbar-track {
|
|
119
|
+
background: rgba(0, 0, 0, 0.05);
|
|
120
|
+
}
|
|
121
|
+
|
|
99
122
|
temba-floating-tab {
|
|
100
123
|
--floating-tab-right: 15px;
|
|
101
124
|
}
|
|
@@ -126,6 +149,7 @@ export class Editor extends RapidElement {
|
|
|
126
149
|
#canvas > .draggable {
|
|
127
150
|
position: absolute;
|
|
128
151
|
z-index: 100;
|
|
152
|
+
touch-action: none;
|
|
129
153
|
}
|
|
130
154
|
|
|
131
155
|
#canvas > .dragging {
|
|
@@ -625,7 +649,7 @@ export class Editor extends RapidElement {
|
|
|
625
649
|
top: 8px;
|
|
626
650
|
right: 240px;
|
|
627
651
|
padding: 6px 10px;
|
|
628
|
-
z-index:
|
|
652
|
+
z-index: 4999;
|
|
629
653
|
pointer-events: none;
|
|
630
654
|
opacity: 0;
|
|
631
655
|
transition: opacity 0.15s ease-in-out;
|
|
@@ -783,11 +807,20 @@ export class Editor extends RapidElement {
|
|
|
783
807
|
this.autoScrollAnimationId = null;
|
|
784
808
|
this.autoScrollDeltaX = 0;
|
|
785
809
|
this.autoScrollDeltaY = 0;
|
|
786
|
-
this.
|
|
810
|
+
this.lastPointerPos = null;
|
|
787
811
|
// Selection state
|
|
788
812
|
this.selectedItems = new Set();
|
|
789
813
|
this.isSelecting = false;
|
|
790
814
|
this.selectionBox = null;
|
|
815
|
+
// Touch device state
|
|
816
|
+
this.isTouchDevice = false;
|
|
817
|
+
this.isTwoFingerPanning = false;
|
|
818
|
+
this.twoFingerDidPan = false;
|
|
819
|
+
this.twoFingerStartMidX = 0;
|
|
820
|
+
this.twoFingerStartMidY = 0;
|
|
821
|
+
this.twoFingerOnCanvas = false;
|
|
822
|
+
this.lastPanX = 0;
|
|
823
|
+
this.lastPanY = 0;
|
|
791
824
|
this.targetId = null;
|
|
792
825
|
this.sourceId = null;
|
|
793
826
|
this.dragFromNodeId = null;
|
|
@@ -846,11 +879,20 @@ export class Editor extends RapidElement {
|
|
|
846
879
|
this.boundKeyDown = this.handleKeyDown.bind(this);
|
|
847
880
|
this.boundCanvasContextMenu = this.handleCanvasContextMenu.bind(this);
|
|
848
881
|
this.boundWheel = this.handleWheel.bind(this);
|
|
882
|
+
this.boundTouchMove = this.handleTouchMove.bind(this);
|
|
883
|
+
this.boundTouchEnd = this.handleTouchEnd.bind(this);
|
|
884
|
+
this.boundTouchCancel = this.handleTouchCancel.bind(this);
|
|
885
|
+
this.boundCanvasTouchStart = this.handleCanvasTouchStart.bind(this);
|
|
849
886
|
}
|
|
850
887
|
firstUpdated(changes) {
|
|
851
888
|
super.firstUpdated(changes);
|
|
852
889
|
this.plumber = new Plumber(this.querySelector('#canvas'), this);
|
|
853
890
|
this.setupGlobalEventListeners();
|
|
891
|
+
// Eagerly detect touch capability so hover-only controls are visible
|
|
892
|
+
// from the start and scrollbar/touch-action CSS is applied immediately.
|
|
893
|
+
if (navigator.maxTouchPoints > 0) {
|
|
894
|
+
this.markTouchDevice();
|
|
895
|
+
}
|
|
854
896
|
this.updateZoomControlPositioning();
|
|
855
897
|
if (changes.has('flow')) {
|
|
856
898
|
getStore().getState().fetchRevision(`/flow/revisions/${this.flow}`);
|
|
@@ -914,7 +956,8 @@ export class Editor extends RapidElement {
|
|
|
914
956
|
canvasMenu.show(menuX, menuY, {
|
|
915
957
|
x: snappedPosition.left,
|
|
916
958
|
y: snappedPosition.top
|
|
917
|
-
}, false
|
|
959
|
+
}, false, // Don't show sticky note option for connection drops
|
|
960
|
+
false, this.flowType === 'message');
|
|
918
961
|
}
|
|
919
962
|
}
|
|
920
963
|
// Request update to render the connection line
|
|
@@ -1187,9 +1230,13 @@ export class Editor extends RapidElement {
|
|
|
1187
1230
|
document.removeEventListener('mouseup', this.boundMouseUp);
|
|
1188
1231
|
document.removeEventListener('mousedown', this.boundGlobalMouseDown);
|
|
1189
1232
|
document.removeEventListener('keydown', this.boundKeyDown);
|
|
1233
|
+
document.removeEventListener('touchmove', this.boundTouchMove);
|
|
1234
|
+
document.removeEventListener('touchend', this.boundTouchEnd);
|
|
1235
|
+
document.removeEventListener('touchcancel', this.boundTouchCancel);
|
|
1190
1236
|
const canvas = this.querySelector('#canvas');
|
|
1191
1237
|
if (canvas) {
|
|
1192
1238
|
canvas.removeEventListener('contextmenu', this.boundCanvasContextMenu);
|
|
1239
|
+
canvas.removeEventListener('touchstart', this.boundCanvasTouchStart);
|
|
1193
1240
|
}
|
|
1194
1241
|
const editor = this.querySelector('#editor');
|
|
1195
1242
|
if (editor) {
|
|
@@ -1204,9 +1251,24 @@ export class Editor extends RapidElement {
|
|
|
1204
1251
|
document.addEventListener('mouseup', this.boundMouseUp);
|
|
1205
1252
|
document.addEventListener('mousedown', this.boundGlobalMouseDown);
|
|
1206
1253
|
document.addEventListener('keydown', this.boundKeyDown);
|
|
1254
|
+
document.addEventListener('touchmove', this.boundTouchMove, {
|
|
1255
|
+
passive: false
|
|
1256
|
+
});
|
|
1257
|
+
document.addEventListener('touchend', this.boundTouchEnd);
|
|
1258
|
+
document.addEventListener('touchcancel', this.boundTouchCancel);
|
|
1259
|
+
// Fallback: on first touch, mark as touch device in case
|
|
1260
|
+
// navigator.maxTouchPoints wasn't detected in firstUpdated.
|
|
1261
|
+
const markTouchOnce = () => {
|
|
1262
|
+
this.markTouchDevice();
|
|
1263
|
+
document.removeEventListener('touchstart', markTouchOnce);
|
|
1264
|
+
};
|
|
1265
|
+
document.addEventListener('touchstart', markTouchOnce);
|
|
1207
1266
|
const canvas = this.querySelector('#canvas');
|
|
1208
1267
|
if (canvas) {
|
|
1209
1268
|
canvas.addEventListener('contextmenu', this.boundCanvasContextMenu);
|
|
1269
|
+
canvas.addEventListener('touchstart', this.boundCanvasTouchStart, {
|
|
1270
|
+
passive: false
|
|
1271
|
+
});
|
|
1210
1272
|
}
|
|
1211
1273
|
const editor = this.querySelector('#editor');
|
|
1212
1274
|
if (editor) {
|
|
@@ -1267,7 +1329,9 @@ export class Editor extends RapidElement {
|
|
|
1267
1329
|
const element = event.currentTarget;
|
|
1268
1330
|
// Only start dragging if clicking on the element itself, not on exits or other interactive elements
|
|
1269
1331
|
const target = event.target;
|
|
1270
|
-
if (target.classList.contains('exit') ||
|
|
1332
|
+
if (target.classList.contains('exit') ||
|
|
1333
|
+
target.closest('.exit') ||
|
|
1334
|
+
target.closest('.linked-name')) {
|
|
1271
1335
|
return;
|
|
1272
1336
|
}
|
|
1273
1337
|
const uuid = element.getAttribute('uuid');
|
|
@@ -1298,6 +1362,62 @@ export class Editor extends RapidElement {
|
|
|
1298
1362
|
event.preventDefault();
|
|
1299
1363
|
event.stopPropagation();
|
|
1300
1364
|
}
|
|
1365
|
+
/**
|
|
1366
|
+
* Mirror of handleMouseDown for touch devices.
|
|
1367
|
+
* Sets up the same drag state so handleTouchMove/End can drive the drag.
|
|
1368
|
+
*/
|
|
1369
|
+
/* c8 ignore start -- touch-only handlers untestable in headless Chromium */
|
|
1370
|
+
/**
|
|
1371
|
+
* Mark the editor as a touch device — adds classes to #canvas and
|
|
1372
|
+
* #editor so touch-specific CSS activates (visible controls,
|
|
1373
|
+
* always-on scrollbars, touch-action: none).
|
|
1374
|
+
*/
|
|
1375
|
+
markTouchDevice() {
|
|
1376
|
+
var _b, _c;
|
|
1377
|
+
if (this.isTouchDevice)
|
|
1378
|
+
return;
|
|
1379
|
+
this.isTouchDevice = true;
|
|
1380
|
+
(_b = this.querySelector('#canvas')) === null || _b === void 0 ? void 0 : _b.classList.add('touch-device');
|
|
1381
|
+
(_c = this.querySelector('#editor')) === null || _c === void 0 ? void 0 : _c.classList.add('touch-device');
|
|
1382
|
+
}
|
|
1383
|
+
handleItemTouchStart(event) {
|
|
1384
|
+
this.markTouchDevice();
|
|
1385
|
+
if (this.isReadOnly())
|
|
1386
|
+
return;
|
|
1387
|
+
this.blurActiveContentEditable();
|
|
1388
|
+
const touch = event.touches[0];
|
|
1389
|
+
if (!touch)
|
|
1390
|
+
return;
|
|
1391
|
+
const element = event.currentTarget;
|
|
1392
|
+
const target = event.target;
|
|
1393
|
+
if (target.classList.contains('exit') ||
|
|
1394
|
+
target.closest('.exit') ||
|
|
1395
|
+
target.closest('.linked-name')) {
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
const uuid = element.getAttribute('uuid');
|
|
1399
|
+
const type = element.tagName === 'TEMBA-FLOW-NODE' ? 'node' : 'sticky';
|
|
1400
|
+
const position = this.getPosition(uuid, type);
|
|
1401
|
+
if (!position)
|
|
1402
|
+
return;
|
|
1403
|
+
// Touch doesn't support Ctrl/Cmd selection — just clear
|
|
1404
|
+
if (!this.selectedItems.has(uuid)) {
|
|
1405
|
+
this.selectedItems.clear();
|
|
1406
|
+
}
|
|
1407
|
+
this.isMouseDown = true;
|
|
1408
|
+
this.dragStartPos = { x: touch.clientX, y: touch.clientY };
|
|
1409
|
+
this.startPos = { left: position.left, top: position.top };
|
|
1410
|
+
this.currentDragItem = {
|
|
1411
|
+
uuid,
|
|
1412
|
+
position,
|
|
1413
|
+
element,
|
|
1414
|
+
type
|
|
1415
|
+
};
|
|
1416
|
+
// Don't preventDefault here — allow the threshold check in touchmove
|
|
1417
|
+
// to decide whether this is a drag or a tap
|
|
1418
|
+
event.stopPropagation();
|
|
1419
|
+
}
|
|
1420
|
+
/* c8 ignore stop */
|
|
1301
1421
|
handleGlobalMouseDown(event) {
|
|
1302
1422
|
var _b;
|
|
1303
1423
|
if (isRightClick(event))
|
|
@@ -1887,6 +2007,72 @@ export class Editor extends RapidElement {
|
|
|
1887
2007
|
getStore().getState().updateCanvasPositions(positions);
|
|
1888
2008
|
}
|
|
1889
2009
|
}
|
|
2010
|
+
/* c8 ignore start -- touch-only handlers */
|
|
2011
|
+
/**
|
|
2012
|
+
* Find the temba-flow-node element at the given viewport coordinates.
|
|
2013
|
+
* Uses elementFromPoint which works for both mouse and touch input.
|
|
2014
|
+
*/
|
|
2015
|
+
findTargetNodeAt(clientX, clientY) {
|
|
2016
|
+
var _b;
|
|
2017
|
+
const el = document.elementFromPoint(clientX, clientY);
|
|
2018
|
+
return (_b = el === null || el === void 0 ? void 0 : el.closest('temba-flow-node')) !== null && _b !== void 0 ? _b : null;
|
|
2019
|
+
}
|
|
2020
|
+
/**
|
|
2021
|
+
* Handle touchstart on the canvas element. Mirrors handleGlobalMouseDown
|
|
2022
|
+
* + handleCanvasMouseDown for touch: starts selection on empty canvas,
|
|
2023
|
+
* and detects double-tap to show the context menu.
|
|
2024
|
+
*/
|
|
2025
|
+
handleCanvasTouchStart(event) {
|
|
2026
|
+
var _b;
|
|
2027
|
+
this.markTouchDevice();
|
|
2028
|
+
const touch = event.touches[0];
|
|
2029
|
+
if (!touch)
|
|
2030
|
+
return;
|
|
2031
|
+
// Only handle touches directly on canvas/grid (not on nodes)
|
|
2032
|
+
const target = event.target;
|
|
2033
|
+
if (target.closest('.draggable'))
|
|
2034
|
+
return;
|
|
2035
|
+
if (target.id !== 'canvas' && target.id !== 'grid')
|
|
2036
|
+
return;
|
|
2037
|
+
// Two-finger touch on canvas — record start position and enter the
|
|
2038
|
+
// two-finger state immediately (even before any touchmove). If the
|
|
2039
|
+
// fingers lift without panning, we show the context menu (handleTouchEnd).
|
|
2040
|
+
if (event.touches.length >= 2) {
|
|
2041
|
+
// Cancel any single-finger selection that the first touch started
|
|
2042
|
+
this.canvasMouseDown = false;
|
|
2043
|
+
this.isSelecting = false;
|
|
2044
|
+
this.selectionBox = null;
|
|
2045
|
+
this.isTwoFingerPanning = true;
|
|
2046
|
+
this.twoFingerOnCanvas = true;
|
|
2047
|
+
this.twoFingerDidPan = false;
|
|
2048
|
+
this.twoFingerStartMidX =
|
|
2049
|
+
(event.touches[0].clientX + event.touches[1].clientX) / 2;
|
|
2050
|
+
this.twoFingerStartMidY =
|
|
2051
|
+
(event.touches[0].clientY + event.touches[1].clientY) / 2;
|
|
2052
|
+
this.lastPanX = this.twoFingerStartMidX;
|
|
2053
|
+
this.lastPanY = this.twoFingerStartMidY;
|
|
2054
|
+
return;
|
|
2055
|
+
}
|
|
2056
|
+
// Start selection box (mirrors handleCanvasMouseDown)
|
|
2057
|
+
if (this.isReadOnly())
|
|
2058
|
+
return;
|
|
2059
|
+
this.canvasMouseDown = true;
|
|
2060
|
+
this.dragStartPos = { x: touch.clientX, y: touch.clientY };
|
|
2061
|
+
const canvasRect = (_b = this.querySelector('#canvas')) === null || _b === void 0 ? void 0 : _b.getBoundingClientRect();
|
|
2062
|
+
if (canvasRect) {
|
|
2063
|
+
this.selectedItems.clear();
|
|
2064
|
+
const relativeX = (touch.clientX - canvasRect.left) / this.zoom;
|
|
2065
|
+
const relativeY = (touch.clientY - canvasRect.top) / this.zoom;
|
|
2066
|
+
this.selectionBox = {
|
|
2067
|
+
startX: relativeX,
|
|
2068
|
+
startY: relativeY,
|
|
2069
|
+
endX: relativeX,
|
|
2070
|
+
endY: relativeY
|
|
2071
|
+
};
|
|
2072
|
+
}
|
|
2073
|
+
event.preventDefault();
|
|
2074
|
+
}
|
|
2075
|
+
/* c8 ignore stop */
|
|
1890
2076
|
handleMouseMove(event) {
|
|
1891
2077
|
// Handle selection box drawing
|
|
1892
2078
|
if (this.canvasMouseDown && !this.isMouseDown) {
|
|
@@ -1959,7 +2145,7 @@ export class Editor extends RapidElement {
|
|
|
1959
2145
|
// Handle item dragging
|
|
1960
2146
|
if (!this.isMouseDown || !this.currentDragItem)
|
|
1961
2147
|
return;
|
|
1962
|
-
this.
|
|
2148
|
+
this.lastPointerPos = { clientX: event.clientX, clientY: event.clientY };
|
|
1963
2149
|
const deltaX = event.clientX - this.dragStartPos.x + this.autoScrollDeltaX;
|
|
1964
2150
|
const deltaY = event.clientY - this.dragStartPos.y + this.autoScrollDeltaY;
|
|
1965
2151
|
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
|
@@ -1974,14 +2160,14 @@ export class Editor extends RapidElement {
|
|
|
1974
2160
|
}
|
|
1975
2161
|
}
|
|
1976
2162
|
updateDragPositions() {
|
|
1977
|
-
if (!this.currentDragItem || !this.
|
|
2163
|
+
if (!this.currentDragItem || !this.lastPointerPos)
|
|
1978
2164
|
return;
|
|
1979
2165
|
// Convert screen + scroll delta to canvas delta
|
|
1980
|
-
const deltaX = (this.
|
|
2166
|
+
const deltaX = (this.lastPointerPos.clientX -
|
|
1981
2167
|
this.dragStartPos.x +
|
|
1982
2168
|
this.autoScrollDeltaX) /
|
|
1983
2169
|
this.zoom;
|
|
1984
|
-
const deltaY = (this.
|
|
2170
|
+
const deltaY = (this.lastPointerPos.clientY -
|
|
1985
2171
|
this.dragStartPos.y +
|
|
1986
2172
|
this.autoScrollDeltaY) /
|
|
1987
2173
|
this.zoom;
|
|
@@ -2010,13 +2196,13 @@ export class Editor extends RapidElement {
|
|
|
2010
2196
|
if (!editor)
|
|
2011
2197
|
return;
|
|
2012
2198
|
const tick = () => {
|
|
2013
|
-
if (!this.isDragging || !this.
|
|
2199
|
+
if (!this.isDragging || !this.lastPointerPos) {
|
|
2014
2200
|
this.autoScrollAnimationId = null;
|
|
2015
2201
|
return;
|
|
2016
2202
|
}
|
|
2017
2203
|
const editorRect = editor.getBoundingClientRect();
|
|
2018
|
-
const mouseX = this.
|
|
2019
|
-
const mouseY = this.
|
|
2204
|
+
const mouseX = this.lastPointerPos.clientX;
|
|
2205
|
+
const mouseY = this.lastPointerPos.clientY;
|
|
2020
2206
|
let scrollDx = 0;
|
|
2021
2207
|
let scrollDy = 0;
|
|
2022
2208
|
// Left edge
|
|
@@ -2155,8 +2341,274 @@ export class Editor extends RapidElement {
|
|
|
2155
2341
|
this.canvasMouseDown = false;
|
|
2156
2342
|
this.autoScrollDeltaX = 0;
|
|
2157
2343
|
this.autoScrollDeltaY = 0;
|
|
2158
|
-
this.
|
|
2344
|
+
this.lastPointerPos = null;
|
|
2345
|
+
}
|
|
2346
|
+
/* c8 ignore start -- touch-only handlers */
|
|
2347
|
+
/**
|
|
2348
|
+
* Handle touch move on the document — mirrors handleMouseMove for
|
|
2349
|
+
* both connection dragging and node/sticky dragging on touch devices.
|
|
2350
|
+
*/
|
|
2351
|
+
handleTouchMove(event) {
|
|
2352
|
+
var _b;
|
|
2353
|
+
// --- Two-finger panning ---
|
|
2354
|
+
if (event.touches.length >= 2) {
|
|
2355
|
+
event.preventDefault();
|
|
2356
|
+
const midX = (event.touches[0].clientX + event.touches[1].clientX) / 2;
|
|
2357
|
+
const midY = (event.touches[0].clientY + event.touches[1].clientY) / 2;
|
|
2358
|
+
if (this.isTwoFingerPanning) {
|
|
2359
|
+
const dx = this.lastPanX - midX;
|
|
2360
|
+
const dy = this.lastPanY - midY;
|
|
2361
|
+
if (Math.abs(dx) > 2 || Math.abs(dy) > 2) {
|
|
2362
|
+
this.twoFingerDidPan = true;
|
|
2363
|
+
}
|
|
2364
|
+
const editor = this.querySelector('#editor');
|
|
2365
|
+
if (editor) {
|
|
2366
|
+
editor.scrollBy(dx, dy);
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
// Cancel any in-progress single-finger actions
|
|
2370
|
+
this.canvasMouseDown = false;
|
|
2371
|
+
this.isSelecting = false;
|
|
2372
|
+
this.selectionBox = null;
|
|
2373
|
+
this.isTwoFingerPanning = true;
|
|
2374
|
+
this.lastPanX = midX;
|
|
2375
|
+
this.lastPanY = midY;
|
|
2376
|
+
return;
|
|
2377
|
+
}
|
|
2378
|
+
const touch = event.touches[0];
|
|
2379
|
+
if (!touch)
|
|
2380
|
+
return;
|
|
2381
|
+
// --- Selection box drawing ---
|
|
2382
|
+
if (this.canvasMouseDown && !this.isMouseDown) {
|
|
2383
|
+
event.preventDefault();
|
|
2384
|
+
this.isSelecting = true;
|
|
2385
|
+
const canvasRect = (_b = this.querySelector('#canvas')) === null || _b === void 0 ? void 0 : _b.getBoundingClientRect();
|
|
2386
|
+
if (canvasRect && this.selectionBox) {
|
|
2387
|
+
this.selectionBox = {
|
|
2388
|
+
...this.selectionBox,
|
|
2389
|
+
endX: (touch.clientX - canvasRect.left) / this.zoom,
|
|
2390
|
+
endY: (touch.clientY - canvasRect.top) / this.zoom
|
|
2391
|
+
};
|
|
2392
|
+
this.updateSelectedItemsFromBox();
|
|
2393
|
+
}
|
|
2394
|
+
this.requestUpdate();
|
|
2395
|
+
return;
|
|
2396
|
+
}
|
|
2397
|
+
// --- Connection dragging ---
|
|
2398
|
+
if (this.plumber.connectionDragging) {
|
|
2399
|
+
event.preventDefault();
|
|
2400
|
+
const targetNode = this.findTargetNodeAt(touch.clientX, touch.clientY);
|
|
2401
|
+
// Clear previous target styles
|
|
2402
|
+
document.querySelectorAll('temba-flow-node').forEach((node) => {
|
|
2403
|
+
node.classList.remove('connection-target-valid', 'connection-target-invalid');
|
|
2404
|
+
});
|
|
2405
|
+
if (targetNode) {
|
|
2406
|
+
this.targetId = targetNode.getAttribute('uuid');
|
|
2407
|
+
this.isValidTarget = this.targetId !== this.dragFromNodeId;
|
|
2408
|
+
if (this.isValidTarget) {
|
|
2409
|
+
targetNode.classList.add('connection-target-valid');
|
|
2410
|
+
}
|
|
2411
|
+
else {
|
|
2412
|
+
targetNode.classList.add('connection-target-invalid');
|
|
2413
|
+
}
|
|
2414
|
+
this.connectionPlaceholder = null;
|
|
2415
|
+
}
|
|
2416
|
+
else {
|
|
2417
|
+
this.targetId = null;
|
|
2418
|
+
this.isValidTarget = true;
|
|
2419
|
+
const canvas = this.querySelector('#canvas');
|
|
2420
|
+
if (canvas) {
|
|
2421
|
+
const canvasRect = canvas.getBoundingClientRect();
|
|
2422
|
+
const relativeX = (touch.clientX - canvasRect.left) / this.zoom;
|
|
2423
|
+
const relativeY = (touch.clientY - canvasRect.top) / this.zoom;
|
|
2424
|
+
const placeholderWidth = 200;
|
|
2425
|
+
const placeholderHeight = 64;
|
|
2426
|
+
const arrowLength = ARROW_LENGTH;
|
|
2427
|
+
const cursorGap = CURSOR_GAP;
|
|
2428
|
+
const dragUp = this.connectionSourceY != null
|
|
2429
|
+
? relativeY < this.connectionSourceY
|
|
2430
|
+
: false;
|
|
2431
|
+
let top;
|
|
2432
|
+
if (dragUp) {
|
|
2433
|
+
top = relativeY + cursorGap - placeholderHeight;
|
|
2434
|
+
}
|
|
2435
|
+
else {
|
|
2436
|
+
top = relativeY - cursorGap + arrowLength;
|
|
2437
|
+
}
|
|
2438
|
+
this.connectionPlaceholder = {
|
|
2439
|
+
position: {
|
|
2440
|
+
left: relativeX - placeholderWidth / 2,
|
|
2441
|
+
top
|
|
2442
|
+
},
|
|
2443
|
+
visible: true,
|
|
2444
|
+
dragUp
|
|
2445
|
+
};
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
this.requestUpdate();
|
|
2449
|
+
return;
|
|
2450
|
+
}
|
|
2451
|
+
// --- Node/sticky dragging ---
|
|
2452
|
+
if (!this.isMouseDown || !this.currentDragItem)
|
|
2453
|
+
return;
|
|
2454
|
+
this.lastPointerPos = { clientX: touch.clientX, clientY: touch.clientY };
|
|
2455
|
+
const deltaX = touch.clientX - this.dragStartPos.x + this.autoScrollDeltaX;
|
|
2456
|
+
const deltaY = touch.clientY - this.dragStartPos.y + this.autoScrollDeltaY;
|
|
2457
|
+
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
|
2458
|
+
if (!this.isDragging && distance > DRAG_THRESHOLD) {
|
|
2459
|
+
this.isDragging = true;
|
|
2460
|
+
this.startAutoScroll();
|
|
2461
|
+
}
|
|
2462
|
+
// Only prevent default scrolling once we're actually dragging.
|
|
2463
|
+
// Before the threshold, allow the browser to fire synthetic click
|
|
2464
|
+
// events for taps on buttons (remove, add-action, etc.).
|
|
2465
|
+
if (this.isDragging) {
|
|
2466
|
+
event.preventDefault();
|
|
2467
|
+
this.updateDragPositions();
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
/**
|
|
2471
|
+
* Handle touch end on the document — mirrors handleMouseUp for
|
|
2472
|
+
* both connection dragging and node/sticky dragging on touch devices.
|
|
2473
|
+
*/
|
|
2474
|
+
handleTouchEnd(event) {
|
|
2475
|
+
// --- Two-finger gesture end ---
|
|
2476
|
+
if (this.isTwoFingerPanning) {
|
|
2477
|
+
if (event.touches.length === 0) {
|
|
2478
|
+
const didPan = this.twoFingerDidPan;
|
|
2479
|
+
const onCanvas = this.twoFingerOnCanvas;
|
|
2480
|
+
const midX = this.twoFingerStartMidX;
|
|
2481
|
+
const midY = this.twoFingerStartMidY;
|
|
2482
|
+
// Reset state
|
|
2483
|
+
this.isTwoFingerPanning = false;
|
|
2484
|
+
this.twoFingerOnCanvas = false;
|
|
2485
|
+
this.twoFingerDidPan = false;
|
|
2486
|
+
// Two-finger tap (no pan) on canvas → show context menu
|
|
2487
|
+
if (!didPan && onCanvas) {
|
|
2488
|
+
this.showContextMenuAt(midX, midY);
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
return;
|
|
2492
|
+
}
|
|
2493
|
+
const touch = event.changedTouches[0];
|
|
2494
|
+
// --- Selection box completion ---
|
|
2495
|
+
if (this.canvasMouseDown && this.isSelecting) {
|
|
2496
|
+
this.isSelecting = false;
|
|
2497
|
+
this.selectionBox = null;
|
|
2498
|
+
this.canvasMouseDown = false;
|
|
2499
|
+
this.requestUpdate();
|
|
2500
|
+
return;
|
|
2501
|
+
}
|
|
2502
|
+
// --- Canvas tap (no drag) — clear selection ---
|
|
2503
|
+
if (this.canvasMouseDown && !this.isSelecting) {
|
|
2504
|
+
this.canvasMouseDown = false;
|
|
2505
|
+
return;
|
|
2506
|
+
}
|
|
2507
|
+
// --- Connection dragging ---
|
|
2508
|
+
if (this.plumber.connectionDragging) {
|
|
2509
|
+
if (touch) {
|
|
2510
|
+
const targetNode = this.findTargetNodeAt(touch.clientX, touch.clientY);
|
|
2511
|
+
if (targetNode) {
|
|
2512
|
+
this.targetId = targetNode.getAttribute('uuid');
|
|
2513
|
+
this.isValidTarget = this.targetId !== this.dragFromNodeId;
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
return;
|
|
2517
|
+
}
|
|
2518
|
+
// --- Node/sticky dragging ---
|
|
2519
|
+
if (!this.isMouseDown || !this.currentDragItem)
|
|
2520
|
+
return;
|
|
2521
|
+
this.stopAutoScroll();
|
|
2522
|
+
if (this.isDragging && touch) {
|
|
2523
|
+
const deltaX = (touch.clientX - this.dragStartPos.x + this.autoScrollDeltaX) /
|
|
2524
|
+
this.zoom;
|
|
2525
|
+
const deltaY = (touch.clientY - this.dragStartPos.y + this.autoScrollDeltaY) /
|
|
2526
|
+
this.zoom;
|
|
2527
|
+
const itemsToMove = this.selectedItems.has(this.currentDragItem.uuid) &&
|
|
2528
|
+
this.selectedItems.size > 1
|
|
2529
|
+
? Array.from(this.selectedItems)
|
|
2530
|
+
: [this.currentDragItem.uuid];
|
|
2531
|
+
const newPositions = {};
|
|
2532
|
+
itemsToMove.forEach((uuid) => {
|
|
2533
|
+
const type = this.definition.nodes.find((node) => node.uuid === uuid)
|
|
2534
|
+
? 'node'
|
|
2535
|
+
: 'sticky';
|
|
2536
|
+
const position = this.getPosition(uuid, type);
|
|
2537
|
+
if (position) {
|
|
2538
|
+
const newLeft = position.left + deltaX;
|
|
2539
|
+
const newTop = position.top + deltaY;
|
|
2540
|
+
const snappedLeft = snapToGrid(newLeft);
|
|
2541
|
+
const snappedTop = snapToGrid(newTop);
|
|
2542
|
+
newPositions[uuid] = { left: snappedLeft, top: snappedTop };
|
|
2543
|
+
const element = this.querySelector(`[uuid="${uuid}"]`);
|
|
2544
|
+
if (element) {
|
|
2545
|
+
element.classList.remove('dragging');
|
|
2546
|
+
element.style.left = `${snappedLeft}px`;
|
|
2547
|
+
element.style.top = `${snappedTop}px`;
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
});
|
|
2551
|
+
if (Object.keys(newPositions).length > 0) {
|
|
2552
|
+
getStore().getState().updateCanvasPositions(newPositions);
|
|
2553
|
+
const nodeUuids = itemsToMove.filter((uuid) => this.definition.nodes.find((node) => node.uuid === uuid));
|
|
2554
|
+
if (nodeUuids.length > 0) {
|
|
2555
|
+
setTimeout(() => {
|
|
2556
|
+
this.checkCollisionsAndReflow(nodeUuids);
|
|
2557
|
+
}, 0);
|
|
2558
|
+
}
|
|
2559
|
+
else {
|
|
2560
|
+
setTimeout(() => {
|
|
2561
|
+
this.plumber.repaintEverything();
|
|
2562
|
+
}, 0);
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
this.selectedItems.clear();
|
|
2566
|
+
}
|
|
2567
|
+
// Reset all drag state
|
|
2568
|
+
this.isDragging = false;
|
|
2569
|
+
this.isMouseDown = false;
|
|
2570
|
+
this.currentDragItem = null;
|
|
2571
|
+
this.canvasMouseDown = false;
|
|
2572
|
+
this.autoScrollDeltaX = 0;
|
|
2573
|
+
this.autoScrollDeltaY = 0;
|
|
2574
|
+
this.lastPointerPos = null;
|
|
2159
2575
|
}
|
|
2576
|
+
/**
|
|
2577
|
+
* Handle touchcancel — reset all touch-related state so the editor
|
|
2578
|
+
* doesn't get stuck in a partial drag/selection mode.
|
|
2579
|
+
*/
|
|
2580
|
+
handleTouchCancel() {
|
|
2581
|
+
this.isTwoFingerPanning = false;
|
|
2582
|
+
this.isSelecting = false;
|
|
2583
|
+
this.selectionBox = null;
|
|
2584
|
+
this.canvasMouseDown = false;
|
|
2585
|
+
if (this.isDragging && this.currentDragItem) {
|
|
2586
|
+
// Remove dragging class from all moved items
|
|
2587
|
+
const itemsToReset = this.selectedItems.has(this.currentDragItem.uuid) &&
|
|
2588
|
+
this.selectedItems.size > 1
|
|
2589
|
+
? Array.from(this.selectedItems)
|
|
2590
|
+
: [this.currentDragItem.uuid];
|
|
2591
|
+
itemsToReset.forEach((uuid) => {
|
|
2592
|
+
const el = this.querySelector(`[uuid="${uuid}"]`);
|
|
2593
|
+
if (el)
|
|
2594
|
+
el.classList.remove('dragging');
|
|
2595
|
+
});
|
|
2596
|
+
}
|
|
2597
|
+
this.stopAutoScroll();
|
|
2598
|
+
this.isDragging = false;
|
|
2599
|
+
this.isMouseDown = false;
|
|
2600
|
+
this.currentDragItem = null;
|
|
2601
|
+
this.autoScrollDeltaX = 0;
|
|
2602
|
+
this.autoScrollDeltaY = 0;
|
|
2603
|
+
this.lastPointerPos = null;
|
|
2604
|
+
// Clear connection drag visual state
|
|
2605
|
+
document.querySelectorAll('temba-flow-node').forEach((node) => {
|
|
2606
|
+
node.classList.remove('connection-target-valid', 'connection-target-invalid');
|
|
2607
|
+
});
|
|
2608
|
+
this.connectionPlaceholder = null;
|
|
2609
|
+
this.requestUpdate();
|
|
2610
|
+
}
|
|
2611
|
+
/* c8 ignore stop */
|
|
2160
2612
|
updateCanvasSize() {
|
|
2161
2613
|
var _b;
|
|
2162
2614
|
if (!this.definition)
|
|
@@ -2216,25 +2668,30 @@ export class Editor extends RapidElement {
|
|
|
2216
2668
|
// Prevent the default browser context menu
|
|
2217
2669
|
event.preventDefault();
|
|
2218
2670
|
event.stopPropagation();
|
|
2219
|
-
|
|
2671
|
+
this.showContextMenuAt(event.clientX, event.clientY);
|
|
2672
|
+
}
|
|
2673
|
+
/**
|
|
2674
|
+
* Show the canvas context menu at the given viewport coordinates.
|
|
2675
|
+
* Shared by right-click (mouse) and double-tap (touch).
|
|
2676
|
+
*/
|
|
2677
|
+
showContextMenuAt(clientX, clientY) {
|
|
2678
|
+
if (this.isReadOnly())
|
|
2679
|
+
return;
|
|
2220
2680
|
const canvas = this.querySelector('#canvas');
|
|
2221
|
-
if (!canvas)
|
|
2681
|
+
if (!canvas)
|
|
2222
2682
|
return;
|
|
2223
|
-
}
|
|
2224
2683
|
const canvasRect = canvas.getBoundingClientRect();
|
|
2225
|
-
const relativeX = (
|
|
2226
|
-
const relativeY = (
|
|
2227
|
-
// Snap position to grid
|
|
2684
|
+
const relativeX = (clientX - canvasRect.left) / this.zoom - 10;
|
|
2685
|
+
const relativeY = (clientY - canvasRect.top) / this.zoom - 10;
|
|
2228
2686
|
const snappedLeft = snapToGrid(relativeX);
|
|
2229
2687
|
const snappedTop = snapToGrid(relativeY);
|
|
2230
|
-
// Show the canvas menu at the mouse position (use viewport coordinates)
|
|
2231
2688
|
const canvasMenu = this.querySelector('temba-canvas-menu');
|
|
2232
2689
|
if (canvasMenu) {
|
|
2233
2690
|
const hasNodes = this.definition && this.definition.nodes.length > 0;
|
|
2234
|
-
canvasMenu.show(
|
|
2691
|
+
canvasMenu.show(clientX, clientY, {
|
|
2235
2692
|
x: snappedLeft,
|
|
2236
2693
|
y: snappedTop
|
|
2237
|
-
}, true, hasNodes);
|
|
2694
|
+
}, true, hasNodes, this.flowType === 'message');
|
|
2238
2695
|
}
|
|
2239
2696
|
}
|
|
2240
2697
|
handleEmptyFlowClick(event) {
|
|
@@ -2253,7 +2710,7 @@ export class Editor extends RapidElement {
|
|
|
2253
2710
|
const menuWidth = 265;
|
|
2254
2711
|
const menuX = rect.left + rect.width / 2 - menuWidth / 2;
|
|
2255
2712
|
const menuY = rect.bottom + 8;
|
|
2256
|
-
canvasMenu.show(menuX, menuY, { x: nodeLeft, y: nodeTop }, false);
|
|
2713
|
+
canvasMenu.show(menuX, menuY, { x: nodeLeft, y: nodeTop }, false, false, this.flowType === 'message');
|
|
2257
2714
|
}
|
|
2258
2715
|
}
|
|
2259
2716
|
handleCanvasMenuSelection(event) {
|
|
@@ -2277,6 +2734,16 @@ export class Editor extends RapidElement {
|
|
|
2277
2734
|
this.connectionSourceY = null;
|
|
2278
2735
|
this.dragFromNodeId = null;
|
|
2279
2736
|
}
|
|
2737
|
+
else if (selection.action === 'send_msg' ||
|
|
2738
|
+
selection.action === 'wait_for_response') {
|
|
2739
|
+
// Go directly to the node editor (skip node type selector)
|
|
2740
|
+
this.handleNodeTypeSelection(new CustomEvent(CustomEventType.Selection, {
|
|
2741
|
+
detail: {
|
|
2742
|
+
nodeType: selection.action,
|
|
2743
|
+
position: selection.position
|
|
2744
|
+
}
|
|
2745
|
+
}));
|
|
2746
|
+
}
|
|
2280
2747
|
else {
|
|
2281
2748
|
// Show node type selector
|
|
2282
2749
|
const selector = this.querySelector('temba-node-type-selector');
|
|
@@ -3763,6 +4230,7 @@ export class Editor extends RapidElement {
|
|
|
3763
4230
|
? 'flow-start'
|
|
3764
4231
|
: ''}"
|
|
3765
4232
|
@mousedown=${this.handleMouseDown.bind(this)}
|
|
4233
|
+
@touchstart=${this.handleItemTouchStart.bind(this)}
|
|
3766
4234
|
uuid=${node.uuid}
|
|
3767
4235
|
data-node-uuid=${node.uuid}
|
|
3768
4236
|
style="left:${position.left}px; top:${position.top}px;transition: all 0.2s ease-in-out;"
|
|
@@ -3785,6 +4253,7 @@ export class Editor extends RapidElement {
|
|
|
3785
4253
|
? 'selected'
|
|
3786
4254
|
: ''}"
|
|
3787
4255
|
@mousedown=${this.handleMouseDown.bind(this)}
|
|
4256
|
+
@touchstart=${this.handleItemTouchStart.bind(this)}
|
|
3788
4257
|
style="left:${position.left}px; top:${position.top}px;"
|
|
3789
4258
|
uuid=${uuid}
|
|
3790
4259
|
.data=${sticky}
|