@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
|
@@ -9,7 +9,8 @@ import {
|
|
|
9
9
|
MessageEditorFieldConfig,
|
|
10
10
|
KeyValueFieldConfig,
|
|
11
11
|
ArrayFieldConfig,
|
|
12
|
-
MediaFieldConfig
|
|
12
|
+
MediaFieldConfig,
|
|
13
|
+
TemplateEditorFieldConfig
|
|
13
14
|
} from '../flow/types';
|
|
14
15
|
import { Attachment } from '../interfaces';
|
|
15
16
|
import { DEFAULT_MEDIA_ENDPOINT } from '../utils';
|
|
@@ -101,6 +102,14 @@ export class FieldRenderer {
|
|
|
101
102
|
context
|
|
102
103
|
);
|
|
103
104
|
|
|
105
|
+
case 'template-editor':
|
|
106
|
+
return FieldRenderer.renderTemplateEditor(
|
|
107
|
+
fieldName,
|
|
108
|
+
config as TemplateEditorFieldConfig,
|
|
109
|
+
value,
|
|
110
|
+
context
|
|
111
|
+
);
|
|
112
|
+
|
|
104
113
|
default:
|
|
105
114
|
return html`<div>Unsupported field type: ${(config as any).type}</div>`;
|
|
106
115
|
}
|
|
@@ -129,7 +138,7 @@ export class FieldRenderer {
|
|
|
129
138
|
.errors="${errors}"
|
|
130
139
|
.value="${value || ''}"
|
|
131
140
|
placeholder="${config.placeholder || ''}"
|
|
132
|
-
|
|
141
|
+
session
|
|
133
142
|
.helpText="${config.helpText || ''}"
|
|
134
143
|
class="${extraClasses}"
|
|
135
144
|
style="${style}"
|
|
@@ -181,7 +190,7 @@ export class FieldRenderer {
|
|
|
181
190
|
.value="${value || ''}"
|
|
182
191
|
placeholder="${config.placeholder || ''}"
|
|
183
192
|
textarea
|
|
184
|
-
|
|
193
|
+
session
|
|
185
194
|
.helpText="${config.helpText || ''}"
|
|
186
195
|
class="${extraClasses}"
|
|
187
196
|
style="${combinedStyle}"
|
|
@@ -462,6 +471,7 @@ export class FieldRenderer {
|
|
|
462
471
|
?autogrow="${config.autogrow}"
|
|
463
472
|
?gsm="${config.gsm}"
|
|
464
473
|
?disableCompletion="${config.disableCompletion}"
|
|
474
|
+
session
|
|
465
475
|
counter="${config.counter || ''}"
|
|
466
476
|
accept="${config.accept || ''}"
|
|
467
477
|
endpoint="${config.endpoint || ''}"
|
|
@@ -472,6 +482,25 @@ export class FieldRenderer {
|
|
|
472
482
|
@change="${onChange || (() => {})}"
|
|
473
483
|
></temba-message-editor>`;
|
|
474
484
|
}
|
|
485
|
+
|
|
486
|
+
private static renderTemplateEditor(
|
|
487
|
+
_fieldName: string,
|
|
488
|
+
config: TemplateEditorFieldConfig,
|
|
489
|
+
value: any,
|
|
490
|
+
context: FieldRenderContext
|
|
491
|
+
): TemplateResult {
|
|
492
|
+
const { onChange, additionalData = {} } = context;
|
|
493
|
+
const templateUuid = value?.uuid || '';
|
|
494
|
+
const variables = JSON.stringify(additionalData.template_variables || []);
|
|
495
|
+
|
|
496
|
+
return html`<temba-template-editor
|
|
497
|
+
url="${config.endpoint || '/api/internal/templates.json'}"
|
|
498
|
+
template="${templateUuid}"
|
|
499
|
+
variables="${variables}"
|
|
500
|
+
@temba-context-changed="${onChange || (() => {})}"
|
|
501
|
+
@temba-content-changed="${onChange || (() => {})}"
|
|
502
|
+
></temba-template-editor>`;
|
|
503
|
+
}
|
|
475
504
|
}
|
|
476
505
|
|
|
477
506
|
export interface FieldRenderContext {
|
package/src/interfaces.ts
CHANGED
|
@@ -305,5 +305,6 @@ export enum CustomEventType {
|
|
|
305
305
|
NodeEditCancelled = 'temba-node-edit-cancelled',
|
|
306
306
|
FollowSimulation = 'temba-follow-simulation',
|
|
307
307
|
ContactClicked = 'temba-contact-clicked',
|
|
308
|
+
FlowClicked = 'temba-flow-clicked',
|
|
308
309
|
ShowIssue = 'temba-show-issue'
|
|
309
310
|
}
|
package/src/list/SortableList.ts
CHANGED
|
@@ -44,6 +44,7 @@ export class SortableList extends RapidElement {
|
|
|
44
44
|
|
|
45
45
|
slot > * {
|
|
46
46
|
user-select: none;
|
|
47
|
+
touch-action: none;
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
temba-icon {
|
|
@@ -103,6 +104,9 @@ export class SortableList extends RapidElement {
|
|
|
103
104
|
this.handleMouseMove = this.handleMouseMove.bind(this);
|
|
104
105
|
this.handleMouseUp = this.handleMouseUp.bind(this);
|
|
105
106
|
this.handleMouseDown = this.handleMouseDown.bind(this);
|
|
107
|
+
this.handleTouchMove = this.handleTouchMove.bind(this);
|
|
108
|
+
this.handleTouchEnd = this.handleTouchEnd.bind(this);
|
|
109
|
+
this.handleTouchStart = this.handleTouchStart.bind(this);
|
|
106
110
|
}
|
|
107
111
|
|
|
108
112
|
private getSortableElements(): Element[] {
|
|
@@ -369,48 +373,81 @@ export class SortableList extends RapidElement {
|
|
|
369
373
|
this.downEle.insertAdjacentElement('afterend', this.dropPlaceholder);
|
|
370
374
|
}
|
|
371
375
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
376
|
+
/**
|
|
377
|
+
* Shared drag-start logic for both mouse and touch.
|
|
378
|
+
* Returns true if a drag target was found and state was initialised.
|
|
379
|
+
*/
|
|
380
|
+
private beginDrag(
|
|
381
|
+
target: HTMLElement,
|
|
382
|
+
clientX: number,
|
|
383
|
+
clientY: number
|
|
384
|
+
): boolean {
|
|
375
385
|
// if we have a drag handle, only allow dragging from that element
|
|
376
386
|
if (this.dragHandle) {
|
|
377
|
-
if (!
|
|
378
|
-
return;
|
|
387
|
+
if (!target.classList.contains(this.dragHandle)) {
|
|
388
|
+
return false;
|
|
379
389
|
}
|
|
380
390
|
}
|
|
381
391
|
|
|
382
|
-
ele =
|
|
383
|
-
if (ele)
|
|
392
|
+
const ele = target.closest('.sortable') as HTMLDivElement;
|
|
393
|
+
if (!ele) return false;
|
|
394
|
+
|
|
395
|
+
this.downEle = ele;
|
|
396
|
+
this.draggingId = ele.id;
|
|
397
|
+
this.draggingIdx = this.getRowIndex(ele.id);
|
|
398
|
+
this.draggingEle = ele;
|
|
399
|
+
|
|
400
|
+
const rect = ele.getBoundingClientRect();
|
|
401
|
+
this.originalElementRect = rect;
|
|
402
|
+
this.originalLayoutSize = {
|
|
403
|
+
width: ele.offsetWidth,
|
|
404
|
+
height: ele.offsetHeight
|
|
405
|
+
};
|
|
406
|
+
this.xOffset = clientX - rect.left;
|
|
407
|
+
this.yOffset = clientY - rect.top;
|
|
408
|
+
this.yDown = clientY;
|
|
409
|
+
this.xDown = clientX;
|
|
410
|
+
|
|
411
|
+
return true;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
private handleMouseDown(event: MouseEvent) {
|
|
415
|
+
if (
|
|
416
|
+
this.beginDrag(event.target as HTMLElement, event.clientX, event.clientY)
|
|
417
|
+
) {
|
|
384
418
|
event.preventDefault();
|
|
385
419
|
event.stopPropagation();
|
|
386
|
-
this.downEle = ele;
|
|
387
|
-
this.draggingId = ele.id;
|
|
388
|
-
this.draggingIdx = this.getRowIndex(ele.id);
|
|
389
|
-
this.draggingEle = ele;
|
|
390
|
-
|
|
391
|
-
// Use getBoundingClientRect for accurate offsets and store original dimensions
|
|
392
|
-
const rect = ele.getBoundingClientRect();
|
|
393
|
-
this.originalElementRect = rect; // Store viewport rect for ghost positioning
|
|
394
|
-
this.originalLayoutSize = {
|
|
395
|
-
width: ele.offsetWidth,
|
|
396
|
-
height: ele.offsetHeight
|
|
397
|
-
}; // Store layout dimensions for placeholders
|
|
398
|
-
this.xOffset = event.clientX - rect.left;
|
|
399
|
-
this.yOffset = event.clientY - rect.top;
|
|
400
|
-
this.yDown = event.clientY;
|
|
401
|
-
this.xDown = event.clientX;
|
|
402
|
-
|
|
403
420
|
document.addEventListener('mousemove', this.handleMouseMove);
|
|
404
421
|
document.addEventListener('mouseup', this.handleMouseUp);
|
|
405
422
|
}
|
|
406
423
|
}
|
|
407
424
|
|
|
408
|
-
|
|
425
|
+
/* c8 ignore start -- touch-only handlers */
|
|
426
|
+
private handleTouchStart(event: TouchEvent) {
|
|
427
|
+
const touch = event.touches[0];
|
|
428
|
+
if (!touch) return;
|
|
429
|
+
if (
|
|
430
|
+
this.beginDrag(event.target as HTMLElement, touch.clientX, touch.clientY)
|
|
431
|
+
) {
|
|
432
|
+
event.stopPropagation();
|
|
433
|
+
document.addEventListener('touchmove', this.handleTouchMove, {
|
|
434
|
+
passive: false
|
|
435
|
+
});
|
|
436
|
+
document.addEventListener('touchend', this.handleTouchEnd);
|
|
437
|
+
document.addEventListener('touchcancel', this.handleTouchEnd);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
/* c8 ignore stop */
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Shared drag-move logic for both mouse and touch.
|
|
444
|
+
*/
|
|
445
|
+
private processDragMove(clientX: number, clientY: number) {
|
|
409
446
|
if (
|
|
410
447
|
!this.ghostElement &&
|
|
411
448
|
this.downEle &&
|
|
412
|
-
(Math.abs(
|
|
413
|
-
Math.abs(
|
|
449
|
+
(Math.abs(clientY - this.yDown) > DRAG_THRESHOLD ||
|
|
450
|
+
Math.abs(clientX - this.xDown) > DRAG_THRESHOLD)
|
|
414
451
|
) {
|
|
415
452
|
this.fireCustomEvent(CustomEventType.DragStart, {
|
|
416
453
|
id: this.downEle.id
|
|
@@ -440,8 +477,8 @@ export class SortableList extends RapidElement {
|
|
|
440
477
|
const hasAncestorScale = Math.abs(ancestorScale - 1) > 0.001;
|
|
441
478
|
|
|
442
479
|
this.ghostElement.style.position = 'fixed';
|
|
443
|
-
this.ghostElement.style.left =
|
|
444
|
-
this.ghostElement.style.top =
|
|
480
|
+
this.ghostElement.style.left = clientX - this.xOffset + 'px';
|
|
481
|
+
this.ghostElement.style.top = clientY - this.yOffset + 'px';
|
|
445
482
|
this.ghostElement.style.zIndex = '99999';
|
|
446
483
|
this.ghostElement.style.opacity = '0.8';
|
|
447
484
|
|
|
@@ -480,12 +517,12 @@ export class SortableList extends RapidElement {
|
|
|
480
517
|
}
|
|
481
518
|
|
|
482
519
|
if (this.ghostElement) {
|
|
483
|
-
this.ghostElement.style.left =
|
|
484
|
-
this.ghostElement.style.top =
|
|
520
|
+
this.ghostElement.style.left = clientX - this.xOffset + 'px';
|
|
521
|
+
this.ghostElement.style.top = clientY - this.yOffset + 'px';
|
|
485
522
|
|
|
486
523
|
// check if the drag is over the container (only if external dragging is allowed)
|
|
487
524
|
const isOverContainer = this.externalDrag
|
|
488
|
-
? this.isMouseOverContainer(
|
|
525
|
+
? this.isMouseOverContainer(clientX, clientY)
|
|
489
526
|
: true; // always consider "over container" if external drag is disabled
|
|
490
527
|
|
|
491
528
|
// detect transition between internal and external drag (only if allowed)
|
|
@@ -501,8 +538,8 @@ export class SortableList extends RapidElement {
|
|
|
501
538
|
|
|
502
539
|
this.fireCustomEvent(CustomEventType.DragExternal, {
|
|
503
540
|
id: this.downEle.id,
|
|
504
|
-
mouseX:
|
|
505
|
-
mouseY:
|
|
541
|
+
mouseX: clientX,
|
|
542
|
+
mouseY: clientY
|
|
506
543
|
});
|
|
507
544
|
} else if (this.externalDrag && isOverContainer && this.isExternalDrag) {
|
|
508
545
|
// transitioning back to internal drag
|
|
@@ -520,7 +557,7 @@ export class SortableList extends RapidElement {
|
|
|
520
557
|
|
|
521
558
|
// only show drop placeholder and calculate drop position if internal drag
|
|
522
559
|
if (!this.isExternalDrag) {
|
|
523
|
-
const targetInfo = this.getDropTargetInfo(
|
|
560
|
+
const targetInfo = this.getDropTargetInfo(clientX, clientY);
|
|
524
561
|
if (targetInfo) {
|
|
525
562
|
const { element: targetElement, insertAfter } = targetInfo;
|
|
526
563
|
const targetIdx = this.getRowIndex(targetElement.id);
|
|
@@ -537,9 +574,6 @@ export class SortableList extends RapidElement {
|
|
|
537
574
|
dropIdx = insertAfter ? targetIdx + 1 : targetIdx;
|
|
538
575
|
} else {
|
|
539
576
|
// Target was originally after the drag position - moving forward
|
|
540
|
-
// When moving the dragged element forward (i.e., to a higher index), the targetIdx is based on the current DOM,
|
|
541
|
-
// which no longer includes the dragged element. This means all elements after the original position have shifted left by one,
|
|
542
|
-
// so we need to subtract 1 from targetIdx to get the correct insertion index. If inserting after the target, we use targetIdx as is.
|
|
543
577
|
dropIdx = insertAfter ? targetIdx : targetIdx - 1;
|
|
544
578
|
}
|
|
545
579
|
|
|
@@ -560,18 +594,30 @@ export class SortableList extends RapidElement {
|
|
|
560
594
|
// external drag - continue firing external drag events with updated position
|
|
561
595
|
this.fireCustomEvent(CustomEventType.DragExternal, {
|
|
562
596
|
id: this.downEle.id,
|
|
563
|
-
mouseX:
|
|
564
|
-
mouseY:
|
|
597
|
+
mouseX: clientX,
|
|
598
|
+
mouseY: clientY
|
|
565
599
|
});
|
|
566
600
|
}
|
|
567
601
|
}
|
|
568
602
|
}
|
|
569
603
|
|
|
570
|
-
private
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
604
|
+
private handleMouseMove(event: MouseEvent) {
|
|
605
|
+
this.processDragMove(event.clientX, event.clientY);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/* c8 ignore next 6 -- touch-only */
|
|
609
|
+
private handleTouchMove(event: TouchEvent) {
|
|
610
|
+
const touch = event.touches[0];
|
|
611
|
+
if (!touch) return;
|
|
612
|
+
event.preventDefault();
|
|
613
|
+
this.processDragMove(touch.clientX, touch.clientY);
|
|
614
|
+
}
|
|
574
615
|
|
|
616
|
+
/**
|
|
617
|
+
* Shared drag-end logic for both mouse and touch.
|
|
618
|
+
*/
|
|
619
|
+
private processDragEnd(clientX: number, clientY: number) {
|
|
620
|
+
if (this.draggingId && this.ghostElement) {
|
|
575
621
|
// Remove the ghost clone from document.body
|
|
576
622
|
if (this.ghostElement) {
|
|
577
623
|
this.ghostElement.remove();
|
|
@@ -610,8 +656,8 @@ export class SortableList extends RapidElement {
|
|
|
610
656
|
this.fireCustomEvent(CustomEventType.DragStop, {
|
|
611
657
|
id: this.draggingId,
|
|
612
658
|
isExternal: this.isExternalDrag,
|
|
613
|
-
mouseX:
|
|
614
|
-
mouseY:
|
|
659
|
+
mouseX: clientX,
|
|
660
|
+
mouseY: clientY
|
|
615
661
|
});
|
|
616
662
|
|
|
617
663
|
this.draggingId = null;
|
|
@@ -639,18 +685,42 @@ export class SortableList extends RapidElement {
|
|
|
639
685
|
}, 100);
|
|
640
686
|
}
|
|
641
687
|
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
private handleMouseUp(evt: MouseEvent) {
|
|
691
|
+
if (this.draggingId && this.ghostElement) {
|
|
692
|
+
evt.preventDefault();
|
|
693
|
+
evt.stopPropagation();
|
|
694
|
+
}
|
|
695
|
+
this.processDragEnd(evt.clientX, evt.clientY);
|
|
642
696
|
document.removeEventListener('mousemove', this.handleMouseMove);
|
|
643
697
|
document.removeEventListener('mouseup', this.handleMouseUp);
|
|
644
698
|
this.dispatchEvent(new Event('change'));
|
|
645
699
|
}
|
|
646
700
|
|
|
701
|
+
/* c8 ignore start -- touch-only */
|
|
702
|
+
private handleTouchEnd(evt: TouchEvent) {
|
|
703
|
+
const touch = evt.changedTouches[0];
|
|
704
|
+
const clientX = touch?.clientX ?? 0;
|
|
705
|
+
const clientY = touch?.clientY ?? 0;
|
|
706
|
+
this.processDragEnd(clientX, clientY);
|
|
707
|
+
document.removeEventListener('touchmove', this.handleTouchMove);
|
|
708
|
+
document.removeEventListener('touchend', this.handleTouchEnd);
|
|
709
|
+
document.removeEventListener('touchcancel', this.handleTouchEnd);
|
|
710
|
+
this.dispatchEvent(new Event('change'));
|
|
711
|
+
}
|
|
712
|
+
/* c8 ignore stop */
|
|
713
|
+
|
|
647
714
|
public render(): TemplateResult {
|
|
648
715
|
return html`
|
|
649
716
|
<div
|
|
650
717
|
class="container ${this.horizontal ? 'horizontal' : ''}"
|
|
651
718
|
style="gap: ${this.gap}"
|
|
652
719
|
>
|
|
653
|
-
<slot
|
|
720
|
+
<slot
|
|
721
|
+
@mousedown=${this.handleMouseDown}
|
|
722
|
+
@touchstart=${this.handleTouchStart}
|
|
723
|
+
></slot>
|
|
654
724
|
</div>
|
|
655
725
|
`;
|
|
656
726
|
}
|
|
@@ -582,6 +582,7 @@ export class Simulator extends RapidElement {
|
|
|
582
582
|
flex-wrap: wrap;
|
|
583
583
|
justify-content: center;
|
|
584
584
|
gap: 6px;
|
|
585
|
+
padding: 0 12px;
|
|
585
586
|
z-index: 9;
|
|
586
587
|
}
|
|
587
588
|
|
|
@@ -594,7 +595,10 @@ export class Simulator extends RapidElement {
|
|
|
594
595
|
font-size: 11px;
|
|
595
596
|
cursor: pointer;
|
|
596
597
|
transition: all 0.2s ease;
|
|
597
|
-
|
|
598
|
+
min-width: 0;
|
|
599
|
+
overflow: hidden;
|
|
600
|
+
text-overflow: ellipsis;
|
|
601
|
+
white-space: nowrap;
|
|
598
602
|
}
|
|
599
603
|
|
|
600
604
|
.quick-reply-btn:hover:not(:disabled) {
|
|
@@ -1944,7 +1948,7 @@ export class Simulator extends RapidElement {
|
|
|
1944
1948
|
</button>
|
|
1945
1949
|
|
|
1946
1950
|
<button class="option-btn" @click=${this.handleReset} title="Reset">
|
|
1947
|
-
<temba-icon name="
|
|
1951
|
+
<temba-icon name="refresh" size="1.5"></temba-icon>
|
|
1948
1952
|
</button>
|
|
1949
1953
|
</div>
|
|
1950
1954
|
</div>
|
|
@@ -48,19 +48,21 @@ describe('temba-canvas-menu', () => {
|
|
|
48
48
|
await assertScreenshot('canvas-menu/open', getClip(menu));
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
-
it('has
|
|
51
|
+
it('has five menu items', async () => {
|
|
52
52
|
const menu = await createCanvasMenu();
|
|
53
53
|
menu.show(100, 100, { x: 50, y: 50 });
|
|
54
54
|
await menu.updateComplete;
|
|
55
55
|
|
|
56
56
|
const menuItems = menu.shadowRoot?.querySelectorAll('.menu-item');
|
|
57
|
-
expect(menuItems?.length).to.equal(
|
|
57
|
+
expect(menuItems?.length).to.equal(5);
|
|
58
58
|
|
|
59
59
|
// check menu item titles
|
|
60
60
|
const titles = Array.from(menuItems || []).map(
|
|
61
61
|
(item) => item.querySelector('.menu-item-title')?.textContent
|
|
62
62
|
);
|
|
63
63
|
expect(titles).to.deep.equal([
|
|
64
|
+
'Send Message',
|
|
65
|
+
'Wait for Response',
|
|
64
66
|
'Add Action',
|
|
65
67
|
'Add Split',
|
|
66
68
|
'Add Sticky Note'
|
|
@@ -93,9 +95,9 @@ describe('temba-canvas-menu', () => {
|
|
|
93
95
|
selectionDetail = event.detail;
|
|
94
96
|
});
|
|
95
97
|
|
|
96
|
-
// click on sticky note option (now the
|
|
98
|
+
// click on sticky note option (now the fifth item)
|
|
97
99
|
const menuItems = menu.shadowRoot?.querySelectorAll('.menu-item');
|
|
98
|
-
const stickyItem = menuItems?.[
|
|
100
|
+
const stickyItem = menuItems?.[4] as HTMLElement;
|
|
99
101
|
stickyItem.click();
|
|
100
102
|
await menu.updateComplete;
|
|
101
103
|
|
|
@@ -113,12 +115,14 @@ describe('temba-canvas-menu', () => {
|
|
|
113
115
|
await menu.updateComplete;
|
|
114
116
|
|
|
115
117
|
const menuItems = menu.shadowRoot?.querySelectorAll('.menu-item');
|
|
116
|
-
expect(menuItems?.length).to.equal(
|
|
118
|
+
expect(menuItems?.length).to.equal(6);
|
|
117
119
|
|
|
118
120
|
const titles = Array.from(menuItems || []).map(
|
|
119
121
|
(item) => item.querySelector('.menu-item-title')?.textContent
|
|
120
122
|
);
|
|
121
123
|
expect(titles).to.deep.equal([
|
|
124
|
+
'Send Message',
|
|
125
|
+
'Wait for Response',
|
|
122
126
|
'Add Action',
|
|
123
127
|
'Add Split',
|
|
124
128
|
'Add Sticky Note',
|
|
@@ -137,7 +141,7 @@ describe('temba-canvas-menu', () => {
|
|
|
137
141
|
});
|
|
138
142
|
|
|
139
143
|
const menuItems = menu.shadowRoot?.querySelectorAll('.menu-item');
|
|
140
|
-
const reflowItem = menuItems?.[
|
|
144
|
+
const reflowItem = menuItems?.[5] as HTMLElement;
|
|
141
145
|
reflowItem.click();
|
|
142
146
|
await menu.updateComplete;
|
|
143
147
|
|
|
@@ -154,7 +158,7 @@ describe('temba-canvas-menu', () => {
|
|
|
154
158
|
await menu.updateComplete;
|
|
155
159
|
|
|
156
160
|
const menuItems = menu.shadowRoot?.querySelectorAll('.menu-item');
|
|
157
|
-
expect(menuItems?.length).to.equal(
|
|
161
|
+
expect(menuItems?.length).to.equal(5);
|
|
158
162
|
|
|
159
163
|
const titles = Array.from(menuItems || []).map(
|
|
160
164
|
(item) => item.querySelector('.menu-item-title')?.textContent
|
|
@@ -200,8 +204,8 @@ describe('temba-canvas-menu', () => {
|
|
|
200
204
|
});
|
|
201
205
|
|
|
202
206
|
const menuItems = menu.shadowRoot?.querySelectorAll('.menu-item');
|
|
203
|
-
const
|
|
204
|
-
|
|
207
|
+
const sendMsgItem = menuItems?.[0] as HTMLElement;
|
|
208
|
+
sendMsgItem.click();
|
|
205
209
|
await menu.updateComplete;
|
|
206
210
|
|
|
207
211
|
expect(selectionFired).to.be.true;
|
|
@@ -375,8 +375,10 @@ describe('Reflow Layout', () => {
|
|
|
375
375
|
),
|
|
376
376
|
...childIds.map((id) => makeNode(id, []))
|
|
377
377
|
];
|
|
378
|
-
const nodeUIs: Record<
|
|
379
|
-
|
|
378
|
+
const nodeUIs: Record<
|
|
379
|
+
string,
|
|
380
|
+
{ position: { left: number; top: number } }
|
|
381
|
+
> = { A: { position: { left: 0, top: 0 } } };
|
|
380
382
|
childIds.forEach((id, i) => {
|
|
381
383
|
nodeUIs[id] = { position: { left: i * 260, top: 200 } };
|
|
382
384
|
});
|
|
@@ -343,7 +343,7 @@ describe('temba-node-editor', () => {
|
|
|
343
343
|
expect(shadowRoot).to.not.be.null;
|
|
344
344
|
});
|
|
345
345
|
|
|
346
|
-
it('displays bubble count for
|
|
346
|
+
it('displays bubble count for accordion value counts', async () => {
|
|
347
347
|
const action = {
|
|
348
348
|
uuid: 'test-action-uuid',
|
|
349
349
|
type: 'send_msg',
|
|
@@ -362,11 +362,11 @@ describe('temba-node-editor', () => {
|
|
|
362
362
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
363
363
|
await el.updateComplete;
|
|
364
364
|
|
|
365
|
-
// Check that bubble counts are displayed
|
|
365
|
+
// Check that bubble counts are displayed in accordion sections
|
|
366
366
|
const shadowRoot = el.shadowRoot;
|
|
367
|
-
const bubbles = shadowRoot.querySelectorAll('.
|
|
367
|
+
const bubbles = shadowRoot.querySelectorAll('.accordion-count-bubble');
|
|
368
368
|
|
|
369
|
-
// Should have bubbles for
|
|
369
|
+
// Should have bubbles for sections with values
|
|
370
370
|
expect(bubbles.length).to.be.greaterThan(0);
|
|
371
371
|
|
|
372
372
|
// Check specific bubble values (trim to handle whitespace in rendered text)
|
|
@@ -374,12 +374,11 @@ describe('temba-node-editor', () => {
|
|
|
374
374
|
bubble.textContent?.trim()
|
|
375
375
|
);
|
|
376
376
|
|
|
377
|
-
// Runtime attachments
|
|
377
|
+
// Runtime attachments section should show bubble when collapsed and has values
|
|
378
378
|
expect(bubbleTexts).to.include('2'); // 2 runtime attachments
|
|
379
|
-
// Note: Quick replies group auto-expands when it has content, so no bubble is shown
|
|
380
379
|
});
|
|
381
380
|
|
|
382
|
-
it('shows arrow when
|
|
381
|
+
it('shows arrow when accordion section has no values', async () => {
|
|
383
382
|
const action = {
|
|
384
383
|
uuid: 'test-action-uuid',
|
|
385
384
|
type: 'send_msg',
|
|
@@ -399,13 +398,13 @@ describe('temba-node-editor', () => {
|
|
|
399
398
|
|
|
400
399
|
// Check that arrows are displayed instead of bubbles
|
|
401
400
|
const shadowRoot = el.shadowRoot;
|
|
402
|
-
const bubbles = shadowRoot.querySelectorAll('.
|
|
403
|
-
const arrows = shadowRoot.querySelectorAll('.
|
|
401
|
+
const bubbles = shadowRoot.querySelectorAll('.accordion-count-bubble');
|
|
402
|
+
const arrows = shadowRoot.querySelectorAll('.accordion-toggle-icon');
|
|
404
403
|
|
|
405
404
|
// Should have no bubbles when counts are 0
|
|
406
405
|
expect(bubbles.length).to.equal(0);
|
|
407
406
|
|
|
408
|
-
// Should have arrows for
|
|
407
|
+
// Should have arrows for accordion sections
|
|
409
408
|
expect(arrows.length).to.be.greaterThan(0);
|
|
410
409
|
});
|
|
411
410
|
|
|
@@ -186,7 +186,7 @@ describe('temba-node-type-selector', () => {
|
|
|
186
186
|
expect(titles).to.not.include('Play Recording');
|
|
187
187
|
});
|
|
188
188
|
|
|
189
|
-
it('filters splits by flow type - message flow should show wait for response', async () => {
|
|
189
|
+
it('filters splits by flow type - message flow should not show wait for response in split dialog', async () => {
|
|
190
190
|
const selector = await createSelector();
|
|
191
191
|
selector.flowType = 'message';
|
|
192
192
|
await selector.updateComplete;
|
|
@@ -199,8 +199,8 @@ describe('temba-node-type-selector', () => {
|
|
|
199
199
|
item.textContent?.trim()
|
|
200
200
|
);
|
|
201
201
|
|
|
202
|
-
//
|
|
203
|
-
expect(titles).to.include('Wait for Response');
|
|
202
|
+
// Wait for Response is now promoted to the context menu, not in split dialog
|
|
203
|
+
expect(titles).to.not.include('Wait for Response');
|
|
204
204
|
});
|
|
205
205
|
|
|
206
206
|
it('filters splits by flow type - voice flow should not show wait for response', async () => {
|
|
@@ -734,12 +734,12 @@ describe('temba-simulator', () => {
|
|
|
734
734
|
// mock the start response for reset
|
|
735
735
|
mockSimulatorStart();
|
|
736
736
|
|
|
737
|
-
// click the reset button (has
|
|
737
|
+
// click the reset button (has refresh icon)
|
|
738
738
|
const optionButtons = Array.from(
|
|
739
739
|
simulator.shadowRoot.querySelectorAll('.option-btn')
|
|
740
740
|
);
|
|
741
741
|
const resetButton = optionButtons.find((btn) =>
|
|
742
|
-
btn.querySelector('temba-icon[name="
|
|
742
|
+
btn.querySelector('temba-icon[name="refresh"]')
|
|
743
743
|
) as HTMLElement;
|
|
744
744
|
expect(resetButton).to.exist;
|
|
745
745
|
resetButton.click();
|