@nyaruka/temba-components 0.126.0 → 0.128.0

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.
Files changed (72) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/demo/chart/example.html +18 -1
  3. package/demo/data/flows/sample-flow.json +127 -100
  4. package/demo/data/server/opened-tickets-long.json +53 -0
  5. package/demo/sticky-note-demo.html +152 -0
  6. package/dist/locales/es.js +5 -5
  7. package/dist/locales/es.js.map +1 -1
  8. package/dist/locales/fr.js +5 -5
  9. package/dist/locales/fr.js.map +1 -1
  10. package/dist/locales/locale-codes.js +11 -2
  11. package/dist/locales/locale-codes.js.map +1 -1
  12. package/dist/locales/pt.js +5 -5
  13. package/dist/locales/pt.js.map +1 -1
  14. package/dist/temba-components.js +346 -86
  15. package/dist/temba-components.js.map +1 -1
  16. package/out-tsc/src/chart/TembaChart.js +44 -5
  17. package/out-tsc/src/chart/TembaChart.js.map +1 -1
  18. package/out-tsc/src/flow/Editor.js +210 -1
  19. package/out-tsc/src/flow/Editor.js.map +1 -1
  20. package/out-tsc/src/flow/EditorNode.js +98 -142
  21. package/out-tsc/src/flow/EditorNode.js.map +1 -1
  22. package/out-tsc/src/flow/StickyNote.js +272 -0
  23. package/out-tsc/src/flow/StickyNote.js.map +1 -0
  24. package/out-tsc/src/list/RunList.js +2 -1
  25. package/out-tsc/src/list/RunList.js.map +1 -1
  26. package/out-tsc/src/list/SortableList.js +9 -0
  27. package/out-tsc/src/list/SortableList.js.map +1 -1
  28. package/out-tsc/src/locales/es.js +5 -5
  29. package/out-tsc/src/locales/es.js.map +1 -1
  30. package/out-tsc/src/locales/fr.js +5 -5
  31. package/out-tsc/src/locales/fr.js.map +1 -1
  32. package/out-tsc/src/locales/locale-codes.js +11 -2
  33. package/out-tsc/src/locales/locale-codes.js.map +1 -1
  34. package/out-tsc/src/locales/pt.js +5 -5
  35. package/out-tsc/src/locales/pt.js.map +1 -1
  36. package/out-tsc/src/store/AppState.js +33 -0
  37. package/out-tsc/src/store/AppState.js.map +1 -1
  38. package/out-tsc/src/vectoricon/index.js +2 -1
  39. package/out-tsc/src/vectoricon/index.js.map +1 -1
  40. package/out-tsc/temba-modules.js +2 -0
  41. package/out-tsc/temba-modules.js.map +1 -1
  42. package/out-tsc/test/temba-flow-editor-node.test.js +249 -5
  43. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
  44. package/out-tsc/test/temba-select.test.js +9 -14
  45. package/out-tsc/test/temba-select.test.js.map +1 -1
  46. package/out-tsc/test/utils.test.js +62 -0
  47. package/out-tsc/test/utils.test.js.map +1 -1
  48. package/package.json +1 -1
  49. package/screenshots/truth/sticky-note/blue.png +0 -0
  50. package/screenshots/truth/sticky-note/gray.png +0 -0
  51. package/screenshots/truth/sticky-note/green.png +0 -0
  52. package/screenshots/truth/sticky-note/pink.png +0 -0
  53. package/screenshots/truth/sticky-note/yellow.png +0 -0
  54. package/src/chart/TembaChart.ts +47 -5
  55. package/src/flow/Editor.ts +252 -2
  56. package/src/flow/EditorNode.ts +98 -160
  57. package/src/flow/StickyNote.ts +284 -0
  58. package/src/list/RunList.ts +2 -1
  59. package/src/list/SortableList.ts +11 -0
  60. package/src/locales/es.ts +18 -13
  61. package/src/locales/fr.ts +18 -13
  62. package/src/locales/locale-codes.ts +11 -2
  63. package/src/locales/pt.ts +18 -13
  64. package/src/store/AppState.ts +51 -1
  65. package/src/store/flow-definition.d.ts +8 -0
  66. package/src/vectoricon/index.ts +2 -1
  67. package/static/svg/index.pdf +137 -0
  68. package/temba-modules.ts +2 -0
  69. package/test/temba-flow-editor-node.test.ts +322 -6
  70. package/test/temba-select.test.ts +10 -17
  71. package/test/utils.test.ts +98 -0
  72. package/web-dev-server.config.mjs +30 -22
@@ -21,26 +21,10 @@ export class EditorNode extends RapidElement {
21
21
  @property({ type: Object })
22
22
  private ui: NodeUI;
23
23
 
24
- // Drag state properties
25
- private isDragging = false;
26
- private dragStartPos = { x: 0, y: 0 };
27
- private nodeStartPos = { left: 0, top: 0 };
28
-
29
- // Bound event handlers to maintain proper 'this' context
30
- private boundMouseMove = this.handleMouseMove.bind(this);
31
- private boundMouseUp = this.handleMouseUp.bind(this);
32
-
33
- /**
34
- * Snaps a coordinate value to the nearest 20px grid position
35
- */
36
- private snapToGrid(value: number): number {
37
- return Math.round(value / 20) * 20;
38
- }
39
-
40
24
  static get styles() {
41
25
  return css`
26
+
42
27
  .node {
43
- position: absolute;
44
28
  background-color: #fff;
45
29
  box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
46
30
  min-width: 200px;
@@ -49,7 +33,7 @@ export class EditorNode extends RapidElement {
49
33
  color: #333;
50
34
  cursor: move;
51
35
  user-select: none;
52
- z-index: 500;
36
+
53
37
  }
54
38
 
55
39
  .node:hover {
@@ -64,21 +48,59 @@ export class EditorNode extends RapidElement {
64
48
 
65
49
  .action {
66
50
  max-width: 200px;
51
+ position: relative;
52
+ }
53
+
54
+ .action.sortable {
55
+ display: flex;
56
+ align-items: stretch;
57
+ }
58
+
59
+ .action .action-content {
60
+ flex-grow: 1;
61
+ display: flex;
62
+ flex-direction: column;
67
63
  }
68
64
 
69
65
  .action .body {
70
66
  padding: 1em;
71
67
  }
72
68
 
69
+ .action .drag-handle {
70
+ opacity: 0;
71
+ transition: all 200ms ease-in-out;
72
+ cursor: move;
73
+ background: rgba(0, 0, 0, 0.02);
74
+ max-width:0px;
75
+ position: absolute;
76
+ }
77
+
78
+ .action:hover .drag-handle {
79
+ opacity: 0.5;
80
+ padding: 0.25em;
81
+ max-width: 20px;
82
+ }
83
+
84
+ .action .drag-handle:hover {
85
+ opacity: 1;
86
+
87
+ }
88
+
73
89
  .action .title,
74
90
  .router .title {
91
+ display: flex;
75
92
  color: #fff;
76
93
  padding: 5px 1px;
77
94
  text-align: center;
78
95
  font-size: 1em;
79
96
  font-weight: normal;
97
+
80
98
  }
81
99
 
100
+ .title .name {
101
+ flex-grow: 1;
102
+ }
103
+
82
104
  .quick-replies {
83
105
  margin-top: 0.5em;
84
106
  }
@@ -133,180 +155,89 @@ export class EditorNode extends RapidElement {
133
155
  }`;
134
156
  }
135
157
 
158
+ constructor() {
159
+ super();
160
+ this.handleActionOrderChanged = this.handleActionOrderChanged.bind(this);
161
+ }
162
+
136
163
  protected updated(
137
164
  changes: PropertyValueMap<any> | Map<PropertyKey, unknown>
138
165
  ): void {
139
166
  super.updated(changes);
140
167
  if (changes.has('node')) {
141
- this.plumber.makeTarget(this.node.uuid);
142
-
143
- // our node was changed, see if we have new destinations
144
- for (const exit of this.node.exits) {
145
- if (!exit.destination_uuid) {
146
- this.plumber.makeSource(exit.uuid);
147
- } else {
148
- this.plumber.connectIds(exit.uuid, exit.destination_uuid);
168
+ // make our initial connections
169
+ if (changes.get('node') === undefined) {
170
+ // this.plumber.makeTarget(this.node.uuid);
171
+ for (const exit of this.node.exits) {
172
+ if (!exit.destination_uuid) {
173
+ this.plumber.makeSource(exit.uuid);
174
+ } else {
175
+ this.plumber.connectIds(exit.uuid, exit.destination_uuid);
176
+ }
149
177
  }
150
178
  }
151
179
 
152
- const ele = this.querySelector('.node');
180
+ const ele = this.parentElement;
153
181
  const rect = ele.getBoundingClientRect();
154
182
 
155
183
  getStore()
156
- .getState()
184
+ ?.getState()
157
185
  .expandCanvas(
158
186
  this.ui.position.left + rect.width,
159
187
  this.ui.position.top + rect.height
160
188
  );
161
-
162
- // Add drag event listeners to the node
163
- this.addDragEventListeners();
164
- }
165
- }
166
-
167
- private addDragEventListeners(): void {
168
- const nodeElement = this.querySelector('.node') as HTMLElement;
169
- if (!nodeElement) return;
170
-
171
- nodeElement.addEventListener('mousedown', this.handleMouseDown.bind(this));
172
- document.addEventListener('mousemove', this.boundMouseMove);
173
- document.addEventListener('mouseup', this.boundMouseUp);
174
- }
175
-
176
- private handleMouseDown(event: MouseEvent): void {
177
- // Only start dragging if clicking on the node itself, not on exits or other interactive elements
178
- const target = event.target as HTMLElement;
179
- if (target.classList.contains('exit') || target.closest('.exit')) {
180
- return;
181
- }
182
-
183
- this.isDragging = true;
184
- this.dragStartPos = { x: event.clientX, y: event.clientY };
185
- this.nodeStartPos = {
186
- left: this.ui.position.left,
187
- top: this.ui.position.top
188
- };
189
-
190
- // Add dragging class for visual feedback
191
- const nodeElement = this.querySelector('.node') as HTMLElement;
192
- if (nodeElement) {
193
- nodeElement.classList.add('dragging');
194
- }
195
-
196
- // Elevate connections for this node during dragging
197
- if (this.plumber) {
198
- this.plumber.elevateNodeConnections(this.node.uuid);
199
- }
200
-
201
- event.preventDefault();
202
- event.stopPropagation();
203
- }
204
-
205
- private handleMouseMove(event: MouseEvent): void {
206
- if (!this.isDragging) return;
207
-
208
- const deltaX = event.clientX - this.dragStartPos.x;
209
- const deltaY = event.clientY - this.dragStartPos.y;
210
-
211
- const newLeft = this.nodeStartPos.left + deltaX;
212
- const newTop = this.nodeStartPos.top + deltaY;
213
-
214
- // Snap to 20px grid
215
- const snappedLeft = this.snapToGrid(newLeft);
216
- const snappedTop = this.snapToGrid(newTop);
217
-
218
- // Update the UI position temporarily (for visual feedback)
219
- const nodeElement = this.querySelector('.node') as HTMLElement;
220
- if (nodeElement) {
221
- nodeElement.style.left = `${snappedLeft}px`;
222
- nodeElement.style.top = `${snappedTop}px`;
223
- }
224
-
225
- // Repaint connections during dragging for smooth updates
226
- if (this.plumber) {
227
- this.plumber.repaintEverything();
228
189
  }
229
190
  }
230
191
 
231
- private handleMouseUp(event: MouseEvent): void {
232
- if (!this.isDragging) return;
233
-
234
- this.isDragging = false;
192
+ private handleActionOrderChanged(event: CustomEvent) {
193
+ const [fromIdx, toIdx] = event.detail.swap;
235
194
 
236
- // Remove dragging class
237
- const nodeElement = this.querySelector('.node') as HTMLElement;
238
- if (nodeElement) {
239
- nodeElement.classList.remove('dragging');
240
- }
241
-
242
- // Restore normal z-index for connections
243
- if (this.plumber) {
244
- this.plumber.restoreNodeConnections(this.node.uuid);
245
- }
246
-
247
- const deltaX = event.clientX - this.dragStartPos.x;
248
- const deltaY = event.clientY - this.dragStartPos.y;
195
+ // swap our actions
196
+ const newActions = [...this.node.actions];
197
+ const movedAction = newActions.splice(fromIdx, 1)[0];
198
+ newActions.splice(toIdx, 0, movedAction);
249
199
 
250
- const newLeft = this.nodeStartPos.left + deltaX;
251
- const newTop = this.nodeStartPos.top + deltaY;
200
+ // udate our internal reprensentation, this isn't strictly necessary
201
+ // since the editor will update us from it's definition subscription
202
+ // but it makes testing a lot easier
203
+ this.node = { ...this.node, actions: newActions };
252
204
 
253
- // Snap to 20px grid for final position
254
- const snappedLeft = this.snapToGrid(newLeft);
255
- const snappedTop = this.snapToGrid(newTop);
256
-
257
- // Update the store with the new snapped position
258
- const newPosition = { left: snappedLeft, top: snappedTop };
259
205
  getStore()
260
- .getState()
261
- .updateCanvasPositions({
262
- [this.node.uuid]: newPosition
263
- });
264
-
265
- // Repaint connections if plumber is available
266
- if (this.plumber) {
267
- this.plumber.repaintEverything();
268
- }
269
-
270
- getStore().getState().updateNodePosition(this.node.uuid, newPosition);
271
-
272
- // Fire a custom event with the new coordinates
273
- /*this.fireCustomEvent(CustomEventType.Moved, {
274
- nodeId: this.node.uuid,
275
- position: newPosition,
276
- oldPosition: {
277
- left: this.nodeStartPos.left,
278
- top: this.nodeStartPos.top
279
- }
280
- });*/
281
- }
282
-
283
- disconnectedCallback(): void {
284
- super.disconnectedCallback();
285
- // Clean up event listeners
286
- document.removeEventListener('mousemove', this.boundMouseMove);
287
- document.removeEventListener('mouseup', this.boundMouseUp);
206
+ ?.getState()
207
+ .updateNode(this.node.uuid, { ...this.node, actions: newActions });
288
208
  }
289
209
 
290
210
  private renderTitle(config: UIConfig) {
291
211
  return html`<div class="title" style="background:${config.color}">
292
- ${config.name}
212
+ ${this.node?.actions?.length > 1
213
+ ? html`<temba-icon class="drag-handle" name="sort"></temba-icon>`
214
+ : null}
215
+
216
+ <div class="name">${config.name}</div>
293
217
  </div>`;
294
218
  }
295
219
 
296
- private renderAction(node: Node, action: Action) {
220
+ private renderAction(node: Node, action: Action, index: number) {
297
221
  const config = EDITOR_CONFIG[action.type];
298
222
 
299
223
  if (config) {
300
- return html`<div class="action ${action.type}">
301
- ${this.renderTitle(config)}
302
- <div class="body">
303
- ${config.render
304
- ? config.render(node, action)
305
- : html`<pre>${action.type}</pre>`}
224
+ return html`<div
225
+ class="action sortable ${action.type}"
226
+ id="action-${index}"
227
+ >
228
+ <div class="action-content">
229
+ ${this.renderTitle(config)}
230
+ <div class="body">
231
+ ${config.render
232
+ ? config.render(node, action)
233
+ : html`<pre>${action.type}</pre>`}
234
+ </div>
306
235
  </div>
307
236
  </div>`;
308
237
  }
309
- return html`<div>${action.type}</div>`;
238
+ return html`<div class="action sortable" id="action-${index}">
239
+ ${action.type}
240
+ </div>`;
310
241
  }
311
242
 
312
243
  private renderRouter(router: Router, ui: NodeUI) {
@@ -363,9 +294,16 @@ export class EditorNode extends RapidElement {
363
294
  class="node"
364
295
  style="left:${this.ui.position.left}px;top:${this.ui.position.top}px"
365
296
  >
366
- ${this.node.actions.map((actionSpec) => {
367
- return this.renderAction(this.node, actionSpec);
368
- })}
297
+ ${this.node.actions.length > 0
298
+ ? html`<temba-sortable-list
299
+ dragHandle="drag-handle"
300
+ @temba-order-changed="${this.handleActionOrderChanged}"
301
+ >
302
+ ${this.node.actions.map((actionSpec, index) => {
303
+ return this.renderAction(this.node, actionSpec, index);
304
+ })}
305
+ </temba-sortable-list>`
306
+ : ''}
369
307
  ${this.node.router
370
308
  ? html` ${this.renderRouter(this.node.router, this.ui)}
371
309
  ${this.renderCategories(this.node)}`
@@ -0,0 +1,284 @@
1
+ import { css, html, PropertyValueMap, TemplateResult } from 'lit';
2
+ import { property } from 'lit/decorators.js';
3
+ import { RapidElement } from '../RapidElement';
4
+ import { StickyNote as StickyNoteData } from '../store/flow-definition';
5
+ import { getStore } from '../store/Store';
6
+
7
+ export class StickyNote extends RapidElement {
8
+ @property({ type: String })
9
+ public uuid: string;
10
+
11
+ @property({ type: Object })
12
+ public data: StickyNoteData;
13
+
14
+ @property({ type: Boolean })
15
+ private dragging = false;
16
+
17
+ static get styles() {
18
+ return css`
19
+ :host {
20
+ --sticky-color: #fef08a;
21
+ --sticky-border-color: #facc15;
22
+ --sticky-text-color: #451a03;
23
+ --curvature: 8px;
24
+ }
25
+
26
+ .sticky-note {
27
+ width: 200px;
28
+ background-color: var(--sticky-color);
29
+ border: 1px solid var(--sticky-border-color);
30
+ border-radius: var(--curvature);
31
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
32
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
33
+ sans-serif;
34
+ font-size: 12px;
35
+ overflow: hidden;
36
+ transition: transform 0.1s ease, box-shadow 0.2s ease;
37
+ color: var(--sticky-text-color);
38
+ }
39
+
40
+ .sticky-note.dragging {
41
+ opacity: 0.5;
42
+ z-index: 1000;
43
+ transform: rotate(0deg);
44
+ box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);
45
+ }
46
+
47
+ .sticky-note:hover {
48
+ transform: translateY(0px);
49
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
50
+ }
51
+
52
+ /* Color themes */
53
+ .sticky-note.yellow {
54
+ --sticky-color: #fef08a;
55
+ --sticky-border-color: #facc15;
56
+ --sticky-text-color: #451a03;
57
+ }
58
+ .sticky-note.blue {
59
+ --sticky-color: #bfdbfe;
60
+ --sticky-border-color: #3b82f6;
61
+ --sticky-text-color: #1e3a8a;
62
+ }
63
+ .sticky-note.pink {
64
+ --sticky-color: #fce7f3;
65
+ --sticky-border-color: #ec4899;
66
+ --sticky-text-color: #831843;
67
+ }
68
+ .sticky-note.green {
69
+ --sticky-color: #d1fae5;
70
+ --sticky-border-color: #10b981;
71
+ --sticky-text-color: #064e3b;
72
+ }
73
+ .sticky-note.gray {
74
+ --sticky-color: #f3f4f6;
75
+ --sticky-border-color: #6b7280;
76
+ --sticky-text-color: #374151;
77
+ }
78
+
79
+ /* Title and body containers */
80
+ .sticky-title-container {
81
+ position: relative;
82
+ border-bottom: 1px solid var(--sticky-border-color);
83
+ background-color: rgba(255, 255, 255, 0.5);
84
+ display: flex;
85
+ align-items: center;
86
+ }
87
+ .sticky-body-container {
88
+ position: relative;
89
+ }
90
+
91
+ /* Editable fields */
92
+ [contenteditable='true'] {
93
+ margin: 2px;
94
+ padding: 4px 8px;
95
+ outline: none;
96
+ border-radius: var(--curvature);
97
+ transition: background 0.2s;
98
+ }
99
+ [contenteditable='true']:focus {
100
+ background-color: rgba(255, 255, 255, 0.8);
101
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
102
+ outline-color: var(--sticky-border-color);
103
+ }
104
+
105
+ /* Title */
106
+ .sticky-title {
107
+ font-weight: 600;
108
+ font-size: 13px;
109
+ color: var(--sticky-text-color);
110
+ min-height: 20px;
111
+ line-height: 20px;
112
+ border-top-left-radius: var(--curvature);
113
+ border-top-right-radius: var(--curvature);
114
+ flex-grow: 1;
115
+ padding-left: 8px;
116
+ }
117
+ .sticky-title:empty::before {
118
+ content: 'Click to add title';
119
+ opacity: 0.5;
120
+ font-style: italic;
121
+ }
122
+ .sticky-title:focus {
123
+ border-bottom-left-radius: 0px;
124
+ border-bottom-right-radius: 0px;
125
+ }
126
+
127
+ /* Body */
128
+ .sticky-body {
129
+ padding: 8px 10px;
130
+ color: var(--sticky-text-color);
131
+ line-height: 1.4;
132
+ min-height: 48px;
133
+ word-wrap: break-word;
134
+ white-space: pre-wrap;
135
+ }
136
+ .sticky-body:empty::before {
137
+ content: 'Click to add note';
138
+ opacity: 0.5;
139
+ font-style: italic;
140
+ }
141
+ .sticky-body:focus {
142
+ border-top-left-radius: 0px;
143
+ border-top-right-radius: 0px;
144
+ }
145
+
146
+ /* Drag icon */
147
+ .sticky-title-container > .drag-handle {
148
+ --icon-color: var(--sticky-border-color);
149
+ cursor: move;
150
+ max-width: 0px;
151
+ overflow: hidden;
152
+ transition: all 0.2s ease;
153
+ padding-left: 0;
154
+ }
155
+
156
+ .sticky-note:hover .drag-handle {
157
+ max-width: 20px;
158
+ padding-left: 8px;
159
+ }
160
+
161
+ .sticky-note:focus-within .sticky-title-container > .drag-handle {
162
+ max-width: 0px;
163
+ padding-left: 0px;
164
+ }
165
+
166
+ /* Focus/active states */
167
+ .sticky-note:focus-within {
168
+ box-shadow: 0 0 0 1px var(--sticky-border-color),
169
+ 0 10px 20px rgba(0, 0, 0, 0.3);
170
+ }
171
+
172
+ .sticky-note:focus-within .drag-handle {
173
+ max-width: 0px;
174
+ }
175
+ `;
176
+ }
177
+
178
+ protected updated(
179
+ changes: PropertyValueMap<any> | Map<PropertyKey, unknown>
180
+ ): void {
181
+ super.updated(changes);
182
+ if (changes.has('data') || changes.has('uuid')) {
183
+ this.updateCanvasSize();
184
+ }
185
+ }
186
+
187
+ private updateCanvasSize(): void {
188
+ if (!this.data) {
189
+ return;
190
+ }
191
+
192
+ const element = this.querySelector('.sticky-note');
193
+ if (element) {
194
+ const rect = element.getBoundingClientRect();
195
+ getStore()
196
+ .getState()
197
+ .expandCanvas(
198
+ this.data.position.left + rect.width,
199
+ this.data.position.top + rect.height
200
+ );
201
+ }
202
+ }
203
+
204
+ private handleTitleBlur(event: FocusEvent): void {
205
+ const target = event.target as HTMLElement;
206
+ const newTitle = target.textContent || '';
207
+
208
+ if (this.data && newTitle !== this.data.title) {
209
+ getStore()
210
+ .getState()
211
+ .updateStickyNote(this.uuid, {
212
+ ...this.data,
213
+ title: newTitle
214
+ });
215
+ }
216
+ this.requestUpdate();
217
+ }
218
+
219
+ private handleBodyBlur(event: FocusEvent): void {
220
+ const target = event.target as HTMLElement;
221
+ const newBody = target.textContent || '';
222
+
223
+ if (this.data && newBody !== this.data.body) {
224
+ getStore()
225
+ .getState()
226
+ .updateStickyNote(this.uuid, {
227
+ ...this.data,
228
+ body: newBody
229
+ });
230
+ }
231
+ this.requestUpdate();
232
+ }
233
+
234
+ private handleKeyDown(event: KeyboardEvent): void {
235
+ if (event.key === 'Enter' && !event.shiftKey) {
236
+ event.preventDefault();
237
+ (event.target as HTMLElement).blur();
238
+ }
239
+ if (event.key === 'Escape') {
240
+ (event.target as HTMLElement).blur();
241
+ }
242
+ }
243
+
244
+ public render(): TemplateResult {
245
+ if (!this.data) {
246
+ return html`<div class="sticky-note" style="display: none;"></div>`;
247
+ }
248
+
249
+ const style = `left: ${this.data.position.left}px; top: ${this.data.position.top}px;`;
250
+
251
+ return html`
252
+ <div
253
+ class="sticky-note ${this.data.color} ${this.dragging
254
+ ? 'dragging'
255
+ : ''}"
256
+ style="${style}"
257
+ data-uuid="${this.uuid}"
258
+ >
259
+ <div class="sticky-title-container">
260
+ <temba-icon name="drag" class="drag-handle"></temba-icon>
261
+ <div
262
+ class="sticky-title"
263
+ contenteditable="true"
264
+ @blur="${this.handleTitleBlur}"
265
+ @keydown="${this.handleKeyDown}"
266
+ @mousedown="${(e: MouseEvent) => e.stopPropagation()}"
267
+ .textContent="${this.data.title}"
268
+ ></div>
269
+ </div>
270
+ <div class="sticky-body-container">
271
+ <div
272
+ class="sticky-body"
273
+ contenteditable="true"
274
+ @blur="${this.handleBodyBlur}"
275
+ @keydown="${this.handleKeyDown}"
276
+ @mousedown="${(e: MouseEvent) => e.stopPropagation()}"
277
+ .textContent="${this.data.body}"
278
+ ></div>
279
+ <div class="edit-icon" title="Edit note"></div>
280
+ </div>
281
+ </div>
282
+ `;
283
+ }
284
+ }
@@ -217,7 +217,8 @@ export class RunList extends TembaList {
217
217
  placeholder="Result Preview"
218
218
  valueKey="key"
219
219
  @change=${this.handleColumnChanged}
220
- />
220
+ style="z-index: 10;"
221
+ ></temba-select>
221
222
  `
222
223
  : null}
223
224
  </div>
@@ -108,6 +108,9 @@ export class SortableList extends RapidElement {
108
108
  @property({ type: String })
109
109
  dropTargetId: string;
110
110
 
111
+ @property({ type: String })
112
+ dragHandle: string;
113
+
111
114
  /**
112
115
  * Optional callback to allow parent components to customize the ghost node.
113
116
  * Called after the ghost node is cloned but before it is appended to the DOM.
@@ -260,6 +263,14 @@ export class SortableList extends RapidElement {
260
263
 
261
264
  private handleMouseDown(event: MouseEvent) {
262
265
  let ele = event.target as HTMLDivElement;
266
+
267
+ // if we have a drag handle, only allow dragging from that element
268
+ if (this.dragHandle) {
269
+ if (!ele.classList.contains(this.dragHandle)) {
270
+ return;
271
+ }
272
+ }
273
+
263
274
  ele = ele.closest('.sortable');
264
275
  if (ele) {
265
276
  event.preventDefault();
package/src/locales/es.ts CHANGED
@@ -1,13 +1,18 @@
1
- // Do not modify this file by hand!
2
- // Re-generate this file by running lit-localize
3
-
4
- /* eslint-disable no-irregular-whitespace */
5
- /* eslint-disable @typescript-eslint/no-explicit-any */
6
-
7
- export const templates = {
8
- scf1453991c986b25: `Tab para completar, enter para seleccionar`,
9
- s73b4d70c02f4b4e0: `No options`,
10
- s8f02e3a18ffc083a: `Are not currently in a flow`,
11
- s638236250662c6b3: `Have sent a message in the last`,
12
- s4788ee206c4570c7: `Have not started this flow in the last 90 days`
13
- };
1
+
2
+ // Do not modify this file by hand!
3
+ // Re-generate this file by running lit-localize
4
+
5
+
6
+
7
+
8
+ /* eslint-disable no-irregular-whitespace */
9
+ /* eslint-disable @typescript-eslint/no-explicit-any */
10
+
11
+ export const templates = {
12
+ 'scf1453991c986b25': `Tab para completar, enter para seleccionar`,
13
+ 's73b4d70c02f4b4e0': `No options`,
14
+ 's8f02e3a18ffc083a': `Are not currently in a flow`,
15
+ 's638236250662c6b3': `Have sent a message in the last`,
16
+ 's4788ee206c4570c7': `Have not started this flow in the last 90 days`,
17
+ };
18
+