@nyaruka/temba-components 0.138.6 → 0.140.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 (196) hide show
  1. package/.github/workflows/cla.yml +1 -1
  2. package/.github/workflows/copilot-setup-steps.yml +6 -1
  3. package/CHANGELOG.md +26 -0
  4. package/demo/data/flows/sample-flow.json +24 -0
  5. package/dist/locales/es.js +5 -5
  6. package/dist/locales/es.js.map +1 -1
  7. package/dist/locales/fr.js +5 -5
  8. package/dist/locales/fr.js.map +1 -1
  9. package/dist/locales/locale-codes.js +2 -11
  10. package/dist/locales/locale-codes.js.map +1 -1
  11. package/dist/locales/pt.js +5 -5
  12. package/dist/locales/pt.js.map +1 -1
  13. package/dist/temba-components.js +1112 -882
  14. package/dist/temba-components.js.map +1 -1
  15. package/out-tsc/src/display/Chat.js +10 -7
  16. package/out-tsc/src/display/Chat.js.map +1 -1
  17. package/out-tsc/src/display/Dropdown.js +3 -1
  18. package/out-tsc/src/display/Dropdown.js.map +1 -1
  19. package/out-tsc/src/display/FloatingTab.js +25 -32
  20. package/out-tsc/src/display/FloatingTab.js.map +1 -1
  21. package/out-tsc/src/display/Thumbnail.js +163 -5
  22. package/out-tsc/src/display/Thumbnail.js.map +1 -1
  23. package/out-tsc/src/flow/CanvasMenu.js +5 -3
  24. package/out-tsc/src/flow/CanvasMenu.js.map +1 -1
  25. package/out-tsc/src/flow/CanvasNode.js +70 -29
  26. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  27. package/out-tsc/src/flow/Editor.js +290 -239
  28. package/out-tsc/src/flow/Editor.js.map +1 -1
  29. package/out-tsc/src/flow/NodeEditor.js +118 -10
  30. package/out-tsc/src/flow/NodeEditor.js.map +1 -1
  31. package/out-tsc/src/flow/Plumber.js +757 -403
  32. package/out-tsc/src/flow/Plumber.js.map +1 -1
  33. package/out-tsc/src/flow/StickyNote.js +13 -4
  34. package/out-tsc/src/flow/StickyNote.js.map +1 -1
  35. package/out-tsc/src/flow/actions/audio-player.js +112 -0
  36. package/out-tsc/src/flow/actions/audio-player.js.map +1 -0
  37. package/out-tsc/src/flow/actions/enter_flow.js +43 -0
  38. package/out-tsc/src/flow/actions/enter_flow.js.map +1 -0
  39. package/out-tsc/src/flow/actions/play_audio.js +57 -4
  40. package/out-tsc/src/flow/actions/play_audio.js.map +1 -1
  41. package/out-tsc/src/flow/actions/say_msg.js +86 -3
  42. package/out-tsc/src/flow/actions/say_msg.js.map +1 -1
  43. package/out-tsc/src/flow/config.js +11 -3
  44. package/out-tsc/src/flow/config.js.map +1 -1
  45. package/out-tsc/src/flow/nodes/shared-rules.js +1 -1
  46. package/out-tsc/src/flow/nodes/shared-rules.js.map +1 -1
  47. package/out-tsc/src/flow/nodes/terminal.js +7 -0
  48. package/out-tsc/src/flow/nodes/terminal.js.map +1 -0
  49. package/out-tsc/src/flow/nodes/wait_for_audio.js +77 -0
  50. package/out-tsc/src/flow/nodes/wait_for_audio.js.map +1 -0
  51. package/out-tsc/src/flow/nodes/wait_for_dial.js +151 -0
  52. package/out-tsc/src/flow/nodes/wait_for_dial.js.map +1 -0
  53. package/out-tsc/src/flow/nodes/wait_for_digits.js +61 -1
  54. package/out-tsc/src/flow/nodes/wait_for_digits.js.map +1 -1
  55. package/out-tsc/src/flow/nodes/wait_for_menu.js +173 -2
  56. package/out-tsc/src/flow/nodes/wait_for_menu.js.map +1 -1
  57. package/out-tsc/src/flow/operators.js +21 -5
  58. package/out-tsc/src/flow/operators.js.map +1 -1
  59. package/out-tsc/src/flow/types.js.map +1 -1
  60. package/out-tsc/src/flow/utils.js +213 -65
  61. package/out-tsc/src/flow/utils.js.map +1 -1
  62. package/out-tsc/src/form/ArrayEditor.js +4 -2
  63. package/out-tsc/src/form/ArrayEditor.js.map +1 -1
  64. package/out-tsc/src/form/FieldRenderer.js +49 -0
  65. package/out-tsc/src/form/FieldRenderer.js.map +1 -1
  66. package/out-tsc/src/interfaces.js +2 -0
  67. package/out-tsc/src/interfaces.js.map +1 -1
  68. package/out-tsc/src/layout/Dialog.js +52 -7
  69. package/out-tsc/src/layout/Dialog.js.map +1 -1
  70. package/out-tsc/src/list/TicketList.js +4 -1
  71. package/out-tsc/src/list/TicketList.js.map +1 -1
  72. package/out-tsc/src/live/TembaChart.js.map +1 -1
  73. package/out-tsc/src/locales/es.js +5 -5
  74. package/out-tsc/src/locales/es.js.map +1 -1
  75. package/out-tsc/src/locales/fr.js +5 -5
  76. package/out-tsc/src/locales/fr.js.map +1 -1
  77. package/out-tsc/src/locales/locale-codes.js +2 -11
  78. package/out-tsc/src/locales/locale-codes.js.map +1 -1
  79. package/out-tsc/src/locales/pt.js +5 -5
  80. package/out-tsc/src/locales/pt.js.map +1 -1
  81. package/out-tsc/src/simulator/Simulator.js +10 -3
  82. package/out-tsc/src/simulator/Simulator.js.map +1 -1
  83. package/out-tsc/src/store/AppState.js +89 -3
  84. package/out-tsc/src/store/AppState.js.map +1 -1
  85. package/out-tsc/test/actions/play_audio.test.js +118 -0
  86. package/out-tsc/test/actions/play_audio.test.js.map +1 -0
  87. package/out-tsc/test/actions/say_msg.test.js +158 -0
  88. package/out-tsc/test/actions/say_msg.test.js.map +1 -0
  89. package/out-tsc/test/nodes/wait_for_audio.test.js +156 -0
  90. package/out-tsc/test/nodes/wait_for_audio.test.js.map +1 -0
  91. package/out-tsc/test/nodes/wait_for_dial.test.js +336 -0
  92. package/out-tsc/test/nodes/wait_for_dial.test.js.map +1 -0
  93. package/out-tsc/test/nodes/wait_for_digits.test.js +198 -84
  94. package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -1
  95. package/out-tsc/test/nodes/wait_for_menu.test.js +340 -0
  96. package/out-tsc/test/nodes/wait_for_menu.test.js.map +1 -0
  97. package/out-tsc/test/temba-floating-tab.test.js +4 -6
  98. package/out-tsc/test/temba-floating-tab.test.js.map +1 -1
  99. package/out-tsc/test/temba-flow-collision.test.js +473 -220
  100. package/out-tsc/test/temba-flow-collision.test.js.map +1 -1
  101. package/out-tsc/test/temba-flow-editor.test.js +0 -2
  102. package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
  103. package/out-tsc/test/temba-flow-plumber-connections.test.js +83 -84
  104. package/out-tsc/test/temba-flow-plumber-connections.test.js.map +1 -1
  105. package/out-tsc/test/temba-flow-plumber.test.js +102 -93
  106. package/out-tsc/test/temba-flow-plumber.test.js.map +1 -1
  107. package/out-tsc/test/temba-node-type-selector.test.js +6 -6
  108. package/out-tsc/test/temba-node-type-selector.test.js.map +1 -1
  109. package/package.json +1 -1
  110. package/screenshots/truth/actions/play_audio/editor/expression-url.png +0 -0
  111. package/screenshots/truth/actions/play_audio/editor/static-url.png +0 -0
  112. package/screenshots/truth/actions/play_audio/render/expression-url.png +0 -0
  113. package/screenshots/truth/actions/play_audio/render/static-url.png +0 -0
  114. package/screenshots/truth/actions/say_msg/editor/multiline-text.png +0 -0
  115. package/screenshots/truth/actions/say_msg/editor/simple-text.png +0 -0
  116. package/screenshots/truth/actions/say_msg/editor/text-with-audio-url.png +0 -0
  117. package/screenshots/truth/actions/say_msg/render/multiline-text.png +0 -0
  118. package/screenshots/truth/actions/say_msg/render/simple-text.png +0 -0
  119. package/screenshots/truth/actions/say_msg/render/text-with-audio-url.png +0 -0
  120. package/screenshots/truth/editor/router.png +0 -0
  121. package/screenshots/truth/editor/wait.png +0 -0
  122. package/screenshots/truth/nodes/wait_for_audio/editor/basic-audio-wait.png +0 -0
  123. package/screenshots/truth/nodes/wait_for_audio/render/basic-audio-wait.png +0 -0
  124. package/screenshots/truth/nodes/wait_for_dial/editor/basic-dial.png +0 -0
  125. package/screenshots/truth/nodes/wait_for_dial/editor/dial-with-limits.png +0 -0
  126. package/screenshots/truth/nodes/wait_for_dial/render/basic-dial.png +0 -0
  127. package/screenshots/truth/nodes/wait_for_dial/render/dial-with-limits.png +0 -0
  128. package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
  129. package/screenshots/truth/nodes/wait_for_digits/editor/digits-with-rules.png +0 -0
  130. package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
  131. package/screenshots/truth/nodes/wait_for_digits/render/digits-with-rules.png +0 -0
  132. package/screenshots/truth/nodes/wait_for_menu/editor/menu-with-digits.png +0 -0
  133. package/screenshots/truth/nodes/wait_for_menu/render/menu-with-digits.png +0 -0
  134. package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
  135. package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
  136. package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
  137. package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
  138. package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
  139. package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
  140. package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
  141. package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
  142. package/src/display/Chat.ts +13 -7
  143. package/src/display/Dropdown.ts +3 -1
  144. package/src/display/FloatingTab.ts +24 -33
  145. package/src/display/Thumbnail.ts +162 -2
  146. package/src/flow/CanvasMenu.ts +8 -3
  147. package/src/flow/CanvasNode.ts +75 -30
  148. package/src/flow/Editor.ts +336 -288
  149. package/src/flow/NodeEditor.ts +137 -9
  150. package/src/flow/Plumber.ts +1011 -457
  151. package/src/flow/StickyNote.ts +14 -4
  152. package/src/flow/actions/audio-player.ts +127 -0
  153. package/src/flow/actions/enter_flow.ts +44 -0
  154. package/src/flow/actions/play_audio.ts +64 -5
  155. package/src/flow/actions/say_msg.ts +94 -4
  156. package/src/flow/config.ts +11 -3
  157. package/src/flow/nodes/shared-rules.ts +1 -1
  158. package/src/flow/nodes/terminal.ts +9 -0
  159. package/src/flow/nodes/wait_for_audio.ts +88 -0
  160. package/src/flow/nodes/wait_for_dial.ts +176 -0
  161. package/src/flow/nodes/wait_for_digits.ts +86 -2
  162. package/src/flow/nodes/wait_for_menu.ts +209 -3
  163. package/src/flow/operators.ts +23 -5
  164. package/src/flow/types.ts +23 -1
  165. package/src/flow/utils.ts +238 -81
  166. package/src/form/ArrayEditor.ts +4 -2
  167. package/src/form/FieldRenderer.ts +64 -1
  168. package/src/interfaces.ts +3 -1
  169. package/src/layout/Dialog.ts +53 -7
  170. package/src/list/TicketList.ts +4 -1
  171. package/src/live/TembaChart.ts +1 -1
  172. package/src/locales/es.ts +13 -18
  173. package/src/locales/fr.ts +13 -18
  174. package/src/locales/locale-codes.ts +2 -11
  175. package/src/locales/pt.ts +13 -18
  176. package/src/simulator/Simulator.ts +13 -3
  177. package/src/store/AppState.ts +105 -1
  178. package/src/store/flow-definition.d.ts +2 -0
  179. package/test/actions/play_audio.test.ts +155 -0
  180. package/test/actions/say_msg.test.ts +196 -0
  181. package/test/nodes/wait_for_audio.test.ts +182 -0
  182. package/test/nodes/wait_for_dial.test.ts +382 -0
  183. package/test/nodes/wait_for_digits.test.ts +233 -109
  184. package/test/nodes/wait_for_menu.test.ts +383 -0
  185. package/test/temba-floating-tab.test.ts +4 -6
  186. package/test/temba-flow-collision.test.ts +495 -293
  187. package/test/temba-flow-editor.test.ts +0 -2
  188. package/test/temba-flow-plumber-connections.test.ts +97 -97
  189. package/test/temba-flow-plumber.test.ts +116 -103
  190. package/test/temba-node-type-selector.test.ts +6 -6
  191. package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
  192. package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
  193. package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
  194. package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
  195. package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
  196. package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
@@ -1,4 +1,16 @@
1
1
  import { html } from 'lit-html';
2
+ export function formatIssueMessage(issue) {
3
+ if (issue.dependency) {
4
+ const name = issue.dependency.name || issue.dependency.key;
5
+ return `Cannot find a ${issue.dependency.type} for ${name}`;
6
+ }
7
+ return issue.description;
8
+ }
9
+ const GRID_SIZE = 20;
10
+ export function snapToGrid(value) {
11
+ const snapped = Math.round(value / GRID_SIZE) * GRID_SIZE;
12
+ return Math.max(snapped, 0);
13
+ }
2
14
  /**
3
15
  * Renders a single line item with optional icon
4
16
  */
@@ -206,85 +218,221 @@ export const nodesOverlap = (bounds1, bounds2) => {
206
218
  export const detectCollisions = (targetBounds, allBounds) => {
207
219
  return allBounds.filter((bounds) => bounds.uuid !== targetBounds.uuid && nodesOverlap(targetBounds, bounds));
208
220
  };
221
+ const DIRECTIONS = ['down', 'up', 'right', 'left'];
222
+ /**
223
+ * Creates a new NodeBounds at a different position
224
+ */
225
+ const makeBoundsAt = (original, left, top) => ({
226
+ ...original,
227
+ left,
228
+ top,
229
+ right: left + original.width,
230
+ bottom: top + original.height
231
+ });
209
232
  /**
210
- * Calculates the new positions needed to resolve all collisions
211
- * Nodes are only moved downward, never up, left, or right
212
- * Returns a map of node UUIDs to their new positions
233
+ * Computes the minimum position needed to clear all fixed nodes in a given direction.
234
+ * Returns null if the direction is not viable (e.g., would require negative coordinates
235
+ * and still overlap).
213
236
  */
214
- export const calculateReflowPositions = (movedNodeUuid, movedNodeBounds, allBounds, droppedBelowMidpoint = false) => {
237
+ const computeDirectionalClearance = (collider, fixedNodes, direction) => {
238
+ switch (direction) {
239
+ case 'down': {
240
+ const maxBottom = Math.max(...fixedNodes.map((f) => f.bottom));
241
+ const newTop = snapToGrid(maxBottom + MIN_NODE_SPACING);
242
+ return { left: collider.left, top: newTop };
243
+ }
244
+ case 'up': {
245
+ const minTop = Math.min(...fixedNodes.map((f) => f.top));
246
+ const newTop = snapToGrid(minTop - collider.height - MIN_NODE_SPACING);
247
+ if (newTop < 0)
248
+ return { left: collider.left, top: 0 };
249
+ return { left: collider.left, top: newTop };
250
+ }
251
+ case 'right': {
252
+ const maxRight = Math.max(...fixedNodes.map((f) => f.right));
253
+ const newLeft = snapToGrid(maxRight + MIN_NODE_SPACING);
254
+ return { left: newLeft, top: collider.top };
255
+ }
256
+ case 'left': {
257
+ const minLeft = Math.min(...fixedNodes.map((f) => f.left));
258
+ const newLeft = snapToGrid(minLeft - collider.width - MIN_NODE_SPACING);
259
+ if (newLeft < 0)
260
+ return { left: 0, top: collider.top };
261
+ return { left: newLeft, top: collider.top };
262
+ }
263
+ }
264
+ };
265
+ /**
266
+ * Calculates new positions to resolve all collisions using multi-directional reflow.
267
+ *
268
+ * Sacred nodes (the ones just dropped/created) keep their positions. All other
269
+ * colliding nodes are moved in whichever direction requires the least displacement
270
+ * and causes the fewest cascading collisions.
271
+ */
272
+ export const calculateReflowPositions = (sacredNodeUuids, allBounds) => {
215
273
  const newPositions = new Map();
216
- // If dropped below midpoint, the moved node should move down instead
217
- if (droppedBelowMidpoint) {
218
- // Find all nodes that collide with the moved node
219
- const collisions = detectCollisions(movedNodeBounds, allBounds);
220
- if (collisions.length > 0) {
221
- // Find the highest bottom position of all colliding nodes
222
- const maxBottom = Math.max(...collisions.map((b) => b.bottom));
223
- // Move the dropped node below all colliding nodes
224
- const newTop = maxBottom + MIN_NODE_SPACING;
225
- newPositions.set(movedNodeUuid, {
226
- left: movedNodeBounds.left,
227
- top: newTop
228
- });
229
- // Update the moved node bounds for further collision checks
230
- movedNodeBounds = {
231
- ...movedNodeBounds,
232
- top: newTop,
233
- bottom: newTop + movedNodeBounds.height
234
- };
274
+ const sacredSet = new Set(sacredNodeUuids);
275
+ // Mutable map of current bounds, updated as collisions are resolved
276
+ const currentBounds = new Map();
277
+ for (const b of allBounds) {
278
+ currentBounds.set(b.uuid, { ...b });
279
+ }
280
+ // A sacred node yields to an existing node at the top of the canvas when
281
+ // the sacred wasn't dropped above it. The existing node keeps its top
282
+ // position and the sacred node moves below instead.
283
+ for (const sacredUuid of [...sacredSet]) {
284
+ const sacred = currentBounds.get(sacredUuid);
285
+ if (!sacred)
286
+ continue;
287
+ for (const [uuid, bounds] of currentBounds) {
288
+ if (uuid === sacredUuid || sacredSet.has(uuid))
289
+ continue;
290
+ if (!nodesOverlap(sacred, bounds))
291
+ continue;
292
+ if (sacred.top > bounds.top && bounds.top < MIN_NODE_SPACING) {
293
+ sacredSet.delete(sacredUuid);
294
+ sacredSet.add(uuid);
295
+ break;
296
+ }
297
+ }
298
+ }
299
+ // Seed the queue with non-sacred nodes that overlap any sacred node
300
+ const queue = [];
301
+ const inQueue = new Set();
302
+ for (const sacredUuid of sacredSet) {
303
+ const sacred = currentBounds.get(sacredUuid);
304
+ if (!sacred)
305
+ continue;
306
+ for (const [uuid, bounds] of currentBounds) {
307
+ if (sacredSet.has(uuid) || inQueue.has(uuid))
308
+ continue;
309
+ if (nodesOverlap(sacred, bounds)) {
310
+ queue.push(uuid);
311
+ inQueue.add(uuid);
312
+ }
235
313
  }
236
314
  }
237
- // Now check for any remaining collisions and move other nodes down
238
- const processedNodes = new Set();
239
- processedNodes.add(movedNodeUuid);
240
- // Keep checking for collisions until none remain
241
- let hasCollisions = true;
315
+ const resolved = new Set();
242
316
  let iterations = 0;
243
- const maxIterations = 100; // Prevent infinite loops
244
- while (hasCollisions && iterations < maxIterations) {
245
- hasCollisions = false;
317
+ const maxIterations = 200;
318
+ while (queue.length > 0 && iterations < maxIterations) {
246
319
  iterations++;
247
- // Check all nodes for collisions
248
- for (const bounds of allBounds) {
249
- if (processedNodes.has(bounds.uuid)) {
320
+ const uuid = queue.shift();
321
+ if (resolved.has(uuid))
322
+ continue;
323
+ const collider = currentBounds.get(uuid);
324
+ // Find all fixed nodes (sacred + already-resolved) that overlap this node
325
+ const fixedOverlaps = [];
326
+ for (const [otherUuid, otherBounds] of currentBounds) {
327
+ if (otherUuid === uuid)
250
328
  continue;
329
+ if (sacredSet.has(otherUuid) || resolved.has(otherUuid)) {
330
+ if (nodesOverlap(collider, otherBounds)) {
331
+ fixedOverlaps.push(otherBounds);
332
+ }
333
+ }
334
+ }
335
+ if (fixedOverlaps.length === 0)
336
+ continue;
337
+ // Determine direction constraints and axis bias from sacred node overlaps
338
+ const sacredOverlaps = fixedOverlaps.filter((f) => sacredSet.has(f.uuid));
339
+ const allowedDirections = [...DIRECTIONS];
340
+ let axisBias = null;
341
+ if (sacredOverlaps.length > 0) {
342
+ // Rule 1: don't move a lower node above the sacred node
343
+ // Rule 2: don't move a right-of node to the left of the sacred node
344
+ for (const sacred of sacredOverlaps) {
345
+ if (collider.top > sacred.top) {
346
+ const idx = allowedDirections.indexOf('up');
347
+ if (idx !== -1)
348
+ allowedDirections.splice(idx, 1);
349
+ }
350
+ if (collider.left > sacred.left) {
351
+ const idx = allowedDirections.indexOf('left');
352
+ if (idx !== -1)
353
+ allowedDirections.splice(idx, 1);
354
+ }
355
+ }
356
+ // Rule 3: bias direction based on overlap shape
357
+ let totalOverlapWidth = 0;
358
+ let totalOverlapHeight = 0;
359
+ for (const sacred of sacredOverlaps) {
360
+ totalOverlapWidth +=
361
+ Math.min(collider.right, sacred.right) -
362
+ Math.max(collider.left, sacred.left);
363
+ totalOverlapHeight +=
364
+ Math.min(collider.bottom, sacred.bottom) -
365
+ Math.max(collider.top, sacred.top);
366
+ }
367
+ if (totalOverlapWidth > totalOverlapHeight) {
368
+ axisBias = 'vertical'; // wide overlap = nodes stacked = prefer up/down
251
369
  }
252
- // Use original bounds since we skip already processed nodes
253
- const currentBounds = bounds;
254
- // Check if this node collides with the moved node or any already repositioned nodes
255
- let collisionFound = false;
256
- let maxCollisionBottom = 0;
257
- // Check against moved node
258
- if (nodesOverlap(currentBounds, movedNodeBounds)) {
259
- collisionFound = true;
260
- maxCollisionBottom = Math.max(maxCollisionBottom, movedNodeBounds.bottom);
370
+ else if (totalOverlapHeight > totalOverlapWidth) {
371
+ axisBias = 'horizontal'; // tall overlap = nodes side-by-side = prefer left/right
261
372
  }
262
- // Check against other repositioned nodes
263
- for (const [otherUuid, otherPosition] of newPositions.entries()) {
264
- if (otherUuid === bounds.uuid)
373
+ }
374
+ // Try each allowed direction, pick the one with least disruption
375
+ let bestPos = null;
376
+ let bestScore = Infinity;
377
+ for (const dir of allowedDirections) {
378
+ const candidate = computeDirectionalClearance(collider, fixedOverlaps, dir);
379
+ if (!candidate)
380
+ continue;
381
+ const candidateBounds = makeBoundsAt(collider, candidate.left, candidate.top);
382
+ // Verify no overlap with any sacred or resolved node
383
+ let stillOverlaps = false;
384
+ let cascadeCount = 0;
385
+ for (const [otherUuid, otherBounds] of currentBounds) {
386
+ if (otherUuid === uuid)
265
387
  continue;
266
- const otherBounds = allBounds.find((b) => b.uuid === otherUuid);
267
- if (!otherBounds)
388
+ if (!nodesOverlap(candidateBounds, otherBounds))
268
389
  continue;
269
- const otherUpdatedBounds = {
270
- ...otherBounds,
271
- top: otherPosition.top,
272
- bottom: otherPosition.top + otherBounds.height
273
- };
274
- if (nodesOverlap(currentBounds, otherUpdatedBounds)) {
275
- collisionFound = true;
276
- maxCollisionBottom = Math.max(maxCollisionBottom, otherUpdatedBounds.bottom);
390
+ if (sacredSet.has(otherUuid) || resolved.has(otherUuid)) {
391
+ stillOverlaps = true;
392
+ break;
277
393
  }
394
+ cascadeCount++;
278
395
  }
279
- if (collisionFound) {
280
- // Move this node down below the collision
281
- const newTop = maxCollisionBottom + MIN_NODE_SPACING;
282
- newPositions.set(bounds.uuid, {
283
- left: bounds.left,
284
- top: newTop
285
- });
286
- hasCollisions = true;
287
- processedNodes.add(bounds.uuid);
396
+ if (stillOverlaps)
397
+ continue;
398
+ const distance = Math.abs(candidate.left - collider.left) +
399
+ Math.abs(candidate.top - collider.top);
400
+ // When colliding with sacred nodes, use axis bias scoring;
401
+ // for cascading collisions (no sacred overlap), use original scoring
402
+ let score;
403
+ if (sacredOverlaps.length > 0) {
404
+ const isVerticalDir = dir === 'up' || dir === 'down';
405
+ const axisMatch = axisBias === null ||
406
+ (axisBias === 'vertical' && isVerticalDir) ||
407
+ (axisBias === 'horizontal' && !isVerticalDir);
408
+ const axisPenalty = axisMatch ? 0 : 5000;
409
+ score = cascadeCount * 2000 + axisPenalty + distance;
410
+ }
411
+ else {
412
+ score = cascadeCount * 10000 + distance;
413
+ }
414
+ if (score < bestScore) {
415
+ bestScore = score;
416
+ bestPos = candidate;
417
+ }
418
+ }
419
+ if (bestPos) {
420
+ newPositions.set(uuid, { left: bestPos.left, top: bestPos.top });
421
+ const newBounds = makeBoundsAt(collider, bestPos.left, bestPos.top);
422
+ currentBounds.set(uuid, newBounds);
423
+ resolved.add(uuid);
424
+ // Enqueue any new cascading collisions
425
+ for (const [otherUuid, otherBounds] of currentBounds) {
426
+ if (otherUuid === uuid)
427
+ continue;
428
+ if (sacredSet.has(otherUuid) || resolved.has(otherUuid))
429
+ continue;
430
+ if (inQueue.has(otherUuid))
431
+ continue;
432
+ if (nodesOverlap(newBounds, otherBounds)) {
433
+ queue.push(otherUuid);
434
+ inQueue.add(otherUuid);
435
+ }
288
436
  }
289
437
  }
290
438
  }
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../src/flow/utils.ts"],"names":[],"mappings":"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","sourcesContent":["import { html } from 'lit-html';\nimport { NamedObject, FlowPosition } from '../store/flow-definition';\n\n/**\n * Renders a single line item with optional icon\n */\nexport const renderLineItem = (name: string, icon?: string) => {\n return html`<div style=\"display:flex;items-align:center;\">\n ${icon\n ? html`<temba-icon name=${icon} style=\"margin-right:0.5em\"></temba-icon>`\n : null}\n <div\n style=\"white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 250px;\"\n >\n ${name}\n </div>\n </div>`;\n};\n\n/**\n * Renders a list of named objects with optional icon, showing up to 3 items\n * with a \"+X more\" indicator if there are more items\n */\nexport const renderNamedObjects = (assets: NamedObject[], icon?: string) => {\n return renderStringList(\n assets.map((asset) => asset.name),\n icon\n );\n};\n\n/**\n * Renders a list of strings with optional icon, showing up to 3 items\n * with a \"+X more\" indicator if there are more items\n */\nexport const renderStringList = (items: string[], icon?: string) => {\n const itemElements = [];\n const maxDisplay = 3;\n\n // Show up to 3 items, or all 4 if exactly 4 items\n const displayCount =\n items.length === 4 ? 4 : Math.min(maxDisplay, items.length);\n\n for (let i = 0; i < displayCount; i++) {\n const item = items[i];\n itemElements.push(renderLineItem(item, icon));\n }\n\n // Add \"+X more\" if there are more than 3 items (and not exactly 4)\n if (items.length > maxDisplay && items.length !== 4) {\n const remainingCount = items.length - maxDisplay;\n itemElements.push(html`<div\n style=\"display:flex;items-align:center;margin-top:0.2em;\"\n >\n ${icon\n ? html`<div style=\"margin-right:0.4em; width: 1em;\"></div>` // spacing placeholder\n : null}\n <div style=\"font-size:0.8em\">+${remainingCount} more</div>\n </div>`);\n }\n return itemElements;\n};\n\nexport interface Scheme {\n scheme: string;\n name: string;\n path: string;\n excludeFromSplit?: boolean;\n}\n\nexport const SCHEMES: Scheme[] = [\n {\n scheme: 'tel',\n name: 'SMS',\n path: 'Phone Number'\n },\n {\n scheme: 'whatsapp',\n name: 'WhatsApp',\n path: 'WhatsApp Number'\n },\n {\n scheme: 'facebook',\n name: 'Facebook',\n path: 'Facebook ID'\n },\n {\n scheme: 'instagram',\n name: 'Instagram',\n path: 'Instagram ID'\n },\n {\n scheme: 'twitterid',\n name: 'Twitter',\n path: 'Twitter ID',\n excludeFromSplit: true\n },\n {\n scheme: 'telegram',\n name: 'Telegram',\n path: 'Telegram ID'\n },\n {\n scheme: 'viber',\n name: 'Viber',\n path: 'Viber ID'\n },\n {\n scheme: 'line',\n name: 'Line',\n path: 'Line ID'\n },\n {\n scheme: 'wechat',\n name: 'WeChat',\n path: 'WeChat ID'\n },\n {\n scheme: 'fcm',\n name: 'Firebase',\n path: 'Firebase ID'\n },\n {\n scheme: 'jiochat',\n name: 'JioChat',\n path: 'JioChat ID'\n },\n {\n scheme: 'freshchat',\n name: 'Freshchat',\n path: 'Freshchat ID'\n },\n {\n scheme: 'mailto',\n name: 'Email',\n path: 'Email Address',\n excludeFromSplit: true\n },\n {\n scheme: 'twitter',\n name: 'Twitter',\n path: 'Twitter Handle',\n excludeFromSplit: true\n },\n {\n scheme: 'vk',\n name: 'VK',\n path: 'VK ID'\n },\n {\n scheme: 'discord',\n name: 'Discord',\n path: 'Discord ID'\n },\n {\n scheme: 'webchat',\n name: 'Webchat',\n path: 'Webchat ID',\n excludeFromSplit: true\n },\n {\n scheme: 'rocketchat',\n name: 'RocketChat',\n path: 'RocketChat ID'\n },\n {\n scheme: 'ext',\n name: 'External',\n path: 'External ID'\n }\n];\n\n/**\n * Represents the bounding box of a node on the canvas\n */\nexport interface NodeBounds {\n uuid: string;\n left: number;\n top: number;\n right: number;\n bottom: number;\n width: number;\n height: number;\n}\n\n/**\n * Minimum vertical spacing between nodes (in pixels)\n */\nconst MIN_NODE_SPACING = 30;\n\n/**\n * Small buffer to avoid floating point precision issues in overlap detection (in pixels)\n * This prevents false positives when nodes are exactly adjacent (e.g., bottom of one node\n * at exactly the same position as top of another)\n */\nconst OVERLAP_BUFFER = 10;\n\n/**\n * Gets the bounding box for a node from the DOM\n *\n * @param nodeUuid - The UUID of the node\n * @param position - The current position of the node\n * @param element - Optional pre-fetched DOM element (recommended for performance when checking multiple nodes)\n * @returns NodeBounds object or null if element not found\n *\n * Note: When element is not provided, performs a DOM query which may impact performance\n * during bulk collision detection. Consider fetching elements beforehand when possible.\n */\nexport const getNodeBounds = (\n nodeUuid: string,\n position: FlowPosition,\n element?: HTMLElement\n): NodeBounds | null => {\n // If element is provided, use it; otherwise try to find it in DOM\n const nodeElement =\n element || (document.querySelector(`[id=\"${nodeUuid}\"]`) as HTMLElement);\n\n if (!nodeElement) {\n return null;\n }\n\n const rect = nodeElement.getBoundingClientRect();\n const width = rect.width;\n const height = rect.height;\n\n return {\n uuid: nodeUuid,\n left: position.left,\n top: position.top,\n right: position.left + width,\n bottom: position.top + height,\n width,\n height\n };\n};\n\n/**\n * Checks if two node bounding boxes overlap\n */\nexport const nodesOverlap = (\n bounds1: NodeBounds,\n bounds2: NodeBounds\n): boolean => {\n // Use a small buffer to avoid floating point precision issues\n const buffer = OVERLAP_BUFFER;\n\n return !(\n bounds1.right <= bounds2.left - buffer ||\n bounds1.left >= bounds2.right + buffer ||\n bounds1.bottom <= bounds2.top - buffer ||\n bounds1.top >= bounds2.bottom + buffer\n );\n};\n\n/**\n * Detects all collisions between a node and other nodes\n */\nexport const detectCollisions = (\n targetBounds: NodeBounds,\n allBounds: NodeBounds[]\n): NodeBounds[] => {\n return allBounds.filter(\n (bounds) =>\n bounds.uuid !== targetBounds.uuid && nodesOverlap(targetBounds, bounds)\n );\n};\n\n/**\n * Calculates the new positions needed to resolve all collisions\n * Nodes are only moved downward, never up, left, or right\n * Returns a map of node UUIDs to their new positions\n */\nexport const calculateReflowPositions = (\n movedNodeUuid: string,\n movedNodeBounds: NodeBounds,\n allBounds: NodeBounds[],\n droppedBelowMidpoint: boolean = false\n): Map<string, FlowPosition> => {\n const newPositions = new Map<string, FlowPosition>();\n\n // If dropped below midpoint, the moved node should move down instead\n if (droppedBelowMidpoint) {\n // Find all nodes that collide with the moved node\n const collisions = detectCollisions(movedNodeBounds, allBounds);\n\n if (collisions.length > 0) {\n // Find the highest bottom position of all colliding nodes\n const maxBottom = Math.max(...collisions.map((b) => b.bottom));\n\n // Move the dropped node below all colliding nodes\n const newTop = maxBottom + MIN_NODE_SPACING;\n newPositions.set(movedNodeUuid, {\n left: movedNodeBounds.left,\n top: newTop\n });\n\n // Update the moved node bounds for further collision checks\n movedNodeBounds = {\n ...movedNodeBounds,\n top: newTop,\n bottom: newTop + movedNodeBounds.height\n };\n }\n }\n\n // Now check for any remaining collisions and move other nodes down\n const processedNodes = new Set<string>();\n processedNodes.add(movedNodeUuid);\n\n // Keep checking for collisions until none remain\n let hasCollisions = true;\n let iterations = 0;\n const maxIterations = 100; // Prevent infinite loops\n\n while (hasCollisions && iterations < maxIterations) {\n hasCollisions = false;\n iterations++;\n\n // Check all nodes for collisions\n for (const bounds of allBounds) {\n if (processedNodes.has(bounds.uuid)) {\n continue;\n }\n\n // Use original bounds since we skip already processed nodes\n const currentBounds = bounds;\n\n // Check if this node collides with the moved node or any already repositioned nodes\n let collisionFound = false;\n let maxCollisionBottom = 0;\n\n // Check against moved node\n if (nodesOverlap(currentBounds, movedNodeBounds)) {\n collisionFound = true;\n maxCollisionBottom = Math.max(\n maxCollisionBottom,\n movedNodeBounds.bottom\n );\n }\n\n // Check against other repositioned nodes\n for (const [otherUuid, otherPosition] of newPositions.entries()) {\n if (otherUuid === bounds.uuid) continue;\n\n const otherBounds = allBounds.find((b) => b.uuid === otherUuid);\n if (!otherBounds) continue;\n\n const otherUpdatedBounds = {\n ...otherBounds,\n top: otherPosition.top,\n bottom: otherPosition.top + otherBounds.height\n };\n\n if (nodesOverlap(currentBounds, otherUpdatedBounds)) {\n collisionFound = true;\n maxCollisionBottom = Math.max(\n maxCollisionBottom,\n otherUpdatedBounds.bottom\n );\n }\n }\n\n if (collisionFound) {\n // Move this node down below the collision\n const newTop = maxCollisionBottom + MIN_NODE_SPACING;\n newPositions.set(bounds.uuid, {\n left: bounds.left,\n top: newTop\n });\n hasCollisions = true;\n processedNodes.add(bounds.uuid);\n }\n }\n }\n\n return newPositions;\n};\n"]}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../src/flow/utils.ts"],"names":[],"mappings":"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","sourcesContent":["import { html } from 'lit-html';\nimport { NamedObject, FlowPosition } from '../store/flow-definition';\nimport { FlowIssue } from '../store/AppState';\n\nexport function formatIssueMessage(issue: FlowIssue): string {\n if (issue.dependency) {\n const name = issue.dependency.name || issue.dependency.key;\n return `Cannot find a ${issue.dependency.type} for ${name}`;\n }\n return issue.description;\n}\n\nconst GRID_SIZE = 20;\n\nexport function snapToGrid(value: number): number {\n const snapped = Math.round(value / GRID_SIZE) * GRID_SIZE;\n return Math.max(snapped, 0);\n}\n\n/**\n * Renders a single line item with optional icon\n */\nexport const renderLineItem = (name: string, icon?: string) => {\n return html`<div style=\"display:flex;items-align:center;\">\n ${icon\n ? html`<temba-icon name=${icon} style=\"margin-right:0.5em\"></temba-icon>`\n : null}\n <div\n style=\"white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 250px;\"\n >\n ${name}\n </div>\n </div>`;\n};\n\n/**\n * Renders a list of named objects with optional icon, showing up to 3 items\n * with a \"+X more\" indicator if there are more items\n */\nexport const renderNamedObjects = (assets: NamedObject[], icon?: string) => {\n return renderStringList(\n assets.map((asset) => asset.name),\n icon\n );\n};\n\n/**\n * Renders a list of strings with optional icon, showing up to 3 items\n * with a \"+X more\" indicator if there are more items\n */\nexport const renderStringList = (items: string[], icon?: string) => {\n const itemElements = [];\n const maxDisplay = 3;\n\n // Show up to 3 items, or all 4 if exactly 4 items\n const displayCount =\n items.length === 4 ? 4 : Math.min(maxDisplay, items.length);\n\n for (let i = 0; i < displayCount; i++) {\n const item = items[i];\n itemElements.push(renderLineItem(item, icon));\n }\n\n // Add \"+X more\" if there are more than 3 items (and not exactly 4)\n if (items.length > maxDisplay && items.length !== 4) {\n const remainingCount = items.length - maxDisplay;\n itemElements.push(html`<div\n style=\"display:flex;items-align:center;margin-top:0.2em;\"\n >\n ${icon\n ? html`<div style=\"margin-right:0.4em; width: 1em;\"></div>` // spacing placeholder\n : null}\n <div style=\"font-size:0.8em\">+${remainingCount} more</div>\n </div>`);\n }\n return itemElements;\n};\n\nexport interface Scheme {\n scheme: string;\n name: string;\n path: string;\n excludeFromSplit?: boolean;\n}\n\nexport const SCHEMES: Scheme[] = [\n {\n scheme: 'tel',\n name: 'SMS',\n path: 'Phone Number'\n },\n {\n scheme: 'whatsapp',\n name: 'WhatsApp',\n path: 'WhatsApp Number'\n },\n {\n scheme: 'facebook',\n name: 'Facebook',\n path: 'Facebook ID'\n },\n {\n scheme: 'instagram',\n name: 'Instagram',\n path: 'Instagram ID'\n },\n {\n scheme: 'twitterid',\n name: 'Twitter',\n path: 'Twitter ID',\n excludeFromSplit: true\n },\n {\n scheme: 'telegram',\n name: 'Telegram',\n path: 'Telegram ID'\n },\n {\n scheme: 'viber',\n name: 'Viber',\n path: 'Viber ID'\n },\n {\n scheme: 'line',\n name: 'Line',\n path: 'Line ID'\n },\n {\n scheme: 'wechat',\n name: 'WeChat',\n path: 'WeChat ID'\n },\n {\n scheme: 'fcm',\n name: 'Firebase',\n path: 'Firebase ID'\n },\n {\n scheme: 'jiochat',\n name: 'JioChat',\n path: 'JioChat ID'\n },\n {\n scheme: 'freshchat',\n name: 'Freshchat',\n path: 'Freshchat ID'\n },\n {\n scheme: 'mailto',\n name: 'Email',\n path: 'Email Address',\n excludeFromSplit: true\n },\n {\n scheme: 'twitter',\n name: 'Twitter',\n path: 'Twitter Handle',\n excludeFromSplit: true\n },\n {\n scheme: 'vk',\n name: 'VK',\n path: 'VK ID'\n },\n {\n scheme: 'discord',\n name: 'Discord',\n path: 'Discord ID'\n },\n {\n scheme: 'webchat',\n name: 'Webchat',\n path: 'Webchat ID',\n excludeFromSplit: true\n },\n {\n scheme: 'rocketchat',\n name: 'RocketChat',\n path: 'RocketChat ID'\n },\n {\n scheme: 'ext',\n name: 'External',\n path: 'External ID'\n }\n];\n\n/**\n * Represents the bounding box of a node on the canvas\n */\nexport interface NodeBounds {\n uuid: string;\n left: number;\n top: number;\n right: number;\n bottom: number;\n width: number;\n height: number;\n}\n\n/**\n * Minimum vertical spacing between nodes (in pixels)\n */\nconst MIN_NODE_SPACING = 30;\n\n/**\n * Small buffer to avoid floating point precision issues in overlap detection (in pixels)\n * This prevents false positives when nodes are exactly adjacent (e.g., bottom of one node\n * at exactly the same position as top of another)\n */\nconst OVERLAP_BUFFER = 10;\n\n/**\n * Gets the bounding box for a node from the DOM\n *\n * @param nodeUuid - The UUID of the node\n * @param position - The current position of the node\n * @param element - Optional pre-fetched DOM element (recommended for performance when checking multiple nodes)\n * @returns NodeBounds object or null if element not found\n *\n * Note: When element is not provided, performs a DOM query which may impact performance\n * during bulk collision detection. Consider fetching elements beforehand when possible.\n */\nexport const getNodeBounds = (\n nodeUuid: string,\n position: FlowPosition,\n element?: HTMLElement\n): NodeBounds | null => {\n // If element is provided, use it; otherwise try to find it in DOM\n const nodeElement =\n element || (document.querySelector(`[id=\"${nodeUuid}\"]`) as HTMLElement);\n\n if (!nodeElement) {\n return null;\n }\n\n const rect = nodeElement.getBoundingClientRect();\n const width = rect.width;\n const height = rect.height;\n\n return {\n uuid: nodeUuid,\n left: position.left,\n top: position.top,\n right: position.left + width,\n bottom: position.top + height,\n width,\n height\n };\n};\n\n/**\n * Checks if two node bounding boxes overlap\n */\nexport const nodesOverlap = (\n bounds1: NodeBounds,\n bounds2: NodeBounds\n): boolean => {\n // Use a small buffer to avoid floating point precision issues\n const buffer = OVERLAP_BUFFER;\n\n return !(\n bounds1.right <= bounds2.left - buffer ||\n bounds1.left >= bounds2.right + buffer ||\n bounds1.bottom <= bounds2.top - buffer ||\n bounds1.top >= bounds2.bottom + buffer\n );\n};\n\n/**\n * Detects all collisions between a node and other nodes\n */\nexport const detectCollisions = (\n targetBounds: NodeBounds,\n allBounds: NodeBounds[]\n): NodeBounds[] => {\n return allBounds.filter(\n (bounds) =>\n bounds.uuid !== targetBounds.uuid && nodesOverlap(targetBounds, bounds)\n );\n};\n\ntype Direction = 'down' | 'up' | 'right' | 'left';\n\nconst DIRECTIONS: Direction[] = ['down', 'up', 'right', 'left'];\n\n/**\n * Creates a new NodeBounds at a different position\n */\nconst makeBoundsAt = (\n original: NodeBounds,\n left: number,\n top: number\n): NodeBounds => ({\n ...original,\n left,\n top,\n right: left + original.width,\n bottom: top + original.height\n});\n\n/**\n * Computes the minimum position needed to clear all fixed nodes in a given direction.\n * Returns null if the direction is not viable (e.g., would require negative coordinates\n * and still overlap).\n */\nconst computeDirectionalClearance = (\n collider: NodeBounds,\n fixedNodes: NodeBounds[],\n direction: Direction\n): { left: number; top: number } | null => {\n switch (direction) {\n case 'down': {\n const maxBottom = Math.max(...fixedNodes.map((f) => f.bottom));\n const newTop = snapToGrid(maxBottom + MIN_NODE_SPACING);\n return { left: collider.left, top: newTop };\n }\n case 'up': {\n const minTop = Math.min(...fixedNodes.map((f) => f.top));\n const newTop = snapToGrid(minTop - collider.height - MIN_NODE_SPACING);\n if (newTop < 0) return { left: collider.left, top: 0 };\n return { left: collider.left, top: newTop };\n }\n case 'right': {\n const maxRight = Math.max(...fixedNodes.map((f) => f.right));\n const newLeft = snapToGrid(maxRight + MIN_NODE_SPACING);\n return { left: newLeft, top: collider.top };\n }\n case 'left': {\n const minLeft = Math.min(...fixedNodes.map((f) => f.left));\n const newLeft = snapToGrid(minLeft - collider.width - MIN_NODE_SPACING);\n if (newLeft < 0) return { left: 0, top: collider.top };\n return { left: newLeft, top: collider.top };\n }\n }\n};\n\n/**\n * Calculates new positions to resolve all collisions using multi-directional reflow.\n *\n * Sacred nodes (the ones just dropped/created) keep their positions. All other\n * colliding nodes are moved in whichever direction requires the least displacement\n * and causes the fewest cascading collisions.\n */\nexport const calculateReflowPositions = (\n sacredNodeUuids: string[],\n allBounds: NodeBounds[]\n): Map<string, FlowPosition> => {\n const newPositions = new Map<string, FlowPosition>();\n const sacredSet = new Set(sacredNodeUuids);\n\n // Mutable map of current bounds, updated as collisions are resolved\n const currentBounds = new Map<string, NodeBounds>();\n for (const b of allBounds) {\n currentBounds.set(b.uuid, { ...b });\n }\n\n // A sacred node yields to an existing node at the top of the canvas when\n // the sacred wasn't dropped above it. The existing node keeps its top\n // position and the sacred node moves below instead.\n for (const sacredUuid of [...sacredSet]) {\n const sacred = currentBounds.get(sacredUuid);\n if (!sacred) continue;\n\n for (const [uuid, bounds] of currentBounds) {\n if (uuid === sacredUuid || sacredSet.has(uuid)) continue;\n if (!nodesOverlap(sacred, bounds)) continue;\n\n if (sacred.top > bounds.top && bounds.top < MIN_NODE_SPACING) {\n sacredSet.delete(sacredUuid);\n sacredSet.add(uuid);\n break;\n }\n }\n }\n\n // Seed the queue with non-sacred nodes that overlap any sacred node\n const queue: string[] = [];\n const inQueue = new Set<string>();\n\n for (const sacredUuid of sacredSet) {\n const sacred = currentBounds.get(sacredUuid);\n if (!sacred) continue;\n for (const [uuid, bounds] of currentBounds) {\n if (sacredSet.has(uuid) || inQueue.has(uuid)) continue;\n if (nodesOverlap(sacred, bounds)) {\n queue.push(uuid);\n inQueue.add(uuid);\n }\n }\n }\n\n const resolved = new Set<string>();\n let iterations = 0;\n const maxIterations = 200;\n\n while (queue.length > 0 && iterations < maxIterations) {\n iterations++;\n const uuid = queue.shift()!;\n\n if (resolved.has(uuid)) continue;\n\n const collider = currentBounds.get(uuid)!;\n\n // Find all fixed nodes (sacred + already-resolved) that overlap this node\n const fixedOverlaps: NodeBounds[] = [];\n for (const [otherUuid, otherBounds] of currentBounds) {\n if (otherUuid === uuid) continue;\n if (sacredSet.has(otherUuid) || resolved.has(otherUuid)) {\n if (nodesOverlap(collider, otherBounds)) {\n fixedOverlaps.push(otherBounds);\n }\n }\n }\n\n if (fixedOverlaps.length === 0) continue;\n\n // Determine direction constraints and axis bias from sacred node overlaps\n const sacredOverlaps = fixedOverlaps.filter((f) => sacredSet.has(f.uuid));\n const allowedDirections: Direction[] = [...DIRECTIONS];\n let axisBias: 'vertical' | 'horizontal' | null = null;\n\n if (sacredOverlaps.length > 0) {\n // Rule 1: don't move a lower node above the sacred node\n // Rule 2: don't move a right-of node to the left of the sacred node\n for (const sacred of sacredOverlaps) {\n if (collider.top > sacred.top) {\n const idx = allowedDirections.indexOf('up');\n if (idx !== -1) allowedDirections.splice(idx, 1);\n }\n if (collider.left > sacred.left) {\n const idx = allowedDirections.indexOf('left');\n if (idx !== -1) allowedDirections.splice(idx, 1);\n }\n }\n\n // Rule 3: bias direction based on overlap shape\n let totalOverlapWidth = 0;\n let totalOverlapHeight = 0;\n for (const sacred of sacredOverlaps) {\n totalOverlapWidth +=\n Math.min(collider.right, sacred.right) -\n Math.max(collider.left, sacred.left);\n totalOverlapHeight +=\n Math.min(collider.bottom, sacred.bottom) -\n Math.max(collider.top, sacred.top);\n }\n if (totalOverlapWidth > totalOverlapHeight) {\n axisBias = 'vertical'; // wide overlap = nodes stacked = prefer up/down\n } else if (totalOverlapHeight > totalOverlapWidth) {\n axisBias = 'horizontal'; // tall overlap = nodes side-by-side = prefer left/right\n }\n }\n\n // Try each allowed direction, pick the one with least disruption\n let bestPos: { left: number; top: number } | null = null;\n let bestScore = Infinity;\n\n for (const dir of allowedDirections) {\n const candidate = computeDirectionalClearance(\n collider,\n fixedOverlaps,\n dir\n );\n if (!candidate) continue;\n\n const candidateBounds = makeBoundsAt(\n collider,\n candidate.left,\n candidate.top\n );\n\n // Verify no overlap with any sacred or resolved node\n let stillOverlaps = false;\n let cascadeCount = 0;\n for (const [otherUuid, otherBounds] of currentBounds) {\n if (otherUuid === uuid) continue;\n if (!nodesOverlap(candidateBounds, otherBounds)) continue;\n\n if (sacredSet.has(otherUuid) || resolved.has(otherUuid)) {\n stillOverlaps = true;\n break;\n }\n cascadeCount++;\n }\n if (stillOverlaps) continue;\n\n const distance =\n Math.abs(candidate.left - collider.left) +\n Math.abs(candidate.top - collider.top);\n\n // When colliding with sacred nodes, use axis bias scoring;\n // for cascading collisions (no sacred overlap), use original scoring\n let score: number;\n if (sacredOverlaps.length > 0) {\n const isVerticalDir = dir === 'up' || dir === 'down';\n const axisMatch =\n axisBias === null ||\n (axisBias === 'vertical' && isVerticalDir) ||\n (axisBias === 'horizontal' && !isVerticalDir);\n const axisPenalty = axisMatch ? 0 : 5000;\n score = cascadeCount * 2000 + axisPenalty + distance;\n } else {\n score = cascadeCount * 10000 + distance;\n }\n\n if (score < bestScore) {\n bestScore = score;\n bestPos = candidate;\n }\n }\n\n if (bestPos) {\n newPositions.set(uuid, { left: bestPos.left, top: bestPos.top });\n const newBounds = makeBoundsAt(collider, bestPos.left, bestPos.top);\n currentBounds.set(uuid, newBounds);\n resolved.add(uuid);\n\n // Enqueue any new cascading collisions\n for (const [otherUuid, otherBounds] of currentBounds) {\n if (otherUuid === uuid) continue;\n if (sacredSet.has(otherUuid) || resolved.has(otherUuid)) continue;\n if (inQueue.has(otherUuid)) continue;\n if (nodesOverlap(newBounds, otherBounds)) {\n queue.push(otherUuid);\n inQueue.add(otherUuid);\n }\n }\n }\n }\n\n return newPositions;\n};\n"]}
@@ -494,13 +494,15 @@ let TembaArrayEditor = class TembaArrayEditor extends BaseListEditor {
494
494
  }
495
495
 
496
496
  .removable .remove-btn {
497
- visibility: hidden;
497
+ opacity: 0.3;
498
498
  cursor: default;
499
+ pointer-events: none;
499
500
  }
500
501
 
501
502
  .removable .drag-handle {
502
- visibility: hidden;
503
+ opacity: 0.3;
503
504
  cursor: default;
505
+ pointer-events: none;
504
506
  }
505
507
 
506
508
  .drag-handle {
@@ -1 +1 @@
1
- {"version":3,"file":"ArrayEditor.js","sourceRoot":"","sources":["../../../src/form/ArrayEditor.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, css, TemplateResult } from 'lit';\nimport { customElement, property } from 'lit/decorators.js';\nimport { FieldConfig } from '../flow/types';\nimport { BaseListEditor, ListItem } from './BaseListEditor';\nimport { FieldRenderer } from './FieldRenderer';\nimport '../list/SortableList';\nimport { Icon } from '../Icons';\n\n@customElement('temba-array-editor')\nexport class TembaArrayEditor extends BaseListEditor<ListItem> {\n @property({ type: Object })\n itemConfig: Record<string, FieldConfig> = {};\n\n @property({ type: String })\n itemLabel = 'Item';\n\n @property({ type: Function })\n onItemChange?: (\n itemIndex: number,\n field: string,\n value: any,\n allItems: any[]\n ) => any[];\n\n @property({ type: Function })\n isEmptyItemFn?: (item: any) => boolean;\n\n @property({ type: Boolean })\n sortable = false;\n\n @property({ type: Boolean })\n maintainEmptyItem = true; // Enable by default for better UX\n\n // Focus preservation properties\n private focusInfo: {\n itemIndex: number;\n fieldName: string;\n selectionStart?: number;\n selectionEnd?: number;\n } | null = null;\n\n constructor() {\n super();\n this._items = [];\n }\n\n // External API\n @property({ type: Array })\n get value(): any[] {\n return [...this._items];\n }\n\n set value(newValue: any[]) {\n this._items = newValue || [];\n this.requestUpdate();\n }\n\n // Implement abstract methods\n isEmptyItem(item: ListItem): boolean {\n // Use configurable function if provided\n if (this.isEmptyItemFn) {\n return this.isEmptyItemFn(item);\n }\n\n // Default behavior: check if all values are empty\n const values = Object.values(item);\n if (values.length === 0) {\n return true;\n }\n\n return values.every(\n (value) => value === undefined || value === null || value === ''\n );\n }\n\n // Override cleanItems to be more permissive for form data\n protected cleanItems(items: ListItem[]): any {\n // For runtime attachments, keep items that have at least one non-empty field\n return items.filter((item) => {\n const values = Object.values(item);\n return (\n values.length > 0 &&\n values.some(\n (value) => value !== undefined && value !== null && value !== ''\n )\n );\n });\n }\n\n // Capture focus information before update\n private captureFocus(): void {\n const activeElement = this.shadowRoot?.activeElement as HTMLElement;\n\n // Also try document.activeElement as a fallback\n const globalActive = document.activeElement as HTMLElement;\n let targetElement = activeElement || globalActive;\n\n // If active element is within this component's shadow root, use it\n if (globalActive && this.shadowRoot?.contains(globalActive)) {\n targetElement = globalActive;\n }\n\n if (!targetElement) {\n this.focusInfo = null;\n return;\n }\n\n // Find the array item container by traversing up the DOM\n let currentElement = targetElement;\n let arrayItemElement: HTMLElement | null = null;\n\n // Traverse up through shadow DOM boundaries\n while (currentElement) {\n if (currentElement.classList?.contains('array-item')) {\n arrayItemElement = currentElement;\n break;\n }\n\n // Move up to parent, or cross shadow boundaries\n if (currentElement.parentElement) {\n currentElement = currentElement.parentElement;\n } else if (\n currentElement.parentNode &&\n (currentElement.parentNode as any).host\n ) {\n // Cross shadow boundary\n currentElement = (currentElement.parentNode as any).host;\n } else {\n break;\n }\n }\n\n if (!arrayItemElement) {\n this.focusInfo = null;\n return;\n }\n\n // Find the item index by looking at the item ID\n const itemIdMatch = arrayItemElement.id?.match(/array-item-(\\d+)/);\n if (!itemIdMatch) {\n this.focusInfo = null;\n return;\n }\n\n const itemIndex = parseInt(itemIdMatch[1], 10);\n\n // Determine the field name by examining the input element and its containers\n let fieldName = '';\n\n // First, check if it's a temba component with a name attribute\n if (targetElement.tagName?.toLowerCase().startsWith('temba-')) {\n fieldName =\n (targetElement as any).name || targetElement.getAttribute('name') || '';\n }\n\n // If not found, check regular HTML elements\n if (\n !fieldName &&\n targetElement.hasAttribute &&\n targetElement.hasAttribute('name')\n ) {\n fieldName = targetElement.getAttribute('name') || '';\n }\n\n // If still not found, look for data-field-name in parent containers\n if (!fieldName) {\n let searchElement = targetElement;\n while (searchElement && searchElement !== arrayItemElement) {\n if (\n searchElement.hasAttribute &&\n searchElement.hasAttribute('data-field-name')\n ) {\n fieldName = searchElement.getAttribute('data-field-name') || '';\n break;\n }\n searchElement = searchElement.parentElement;\n }\n }\n\n if (!fieldName) {\n this.focusInfo = null;\n return;\n }\n\n // Capture selection for text inputs (try the actual input element inside temba components)\n let inputForSelection = targetElement;\n if (targetElement.tagName?.toLowerCase().startsWith('temba-')) {\n // Look for the actual input element inside the temba component\n const innerInput =\n targetElement.shadowRoot?.querySelector('input, textarea') ||\n targetElement.querySelector('input, textarea');\n if (innerInput) {\n inputForSelection = innerInput as HTMLElement;\n }\n }\n\n const selectionStart = (inputForSelection as any).selectionStart;\n const selectionEnd = (inputForSelection as any).selectionEnd;\n\n this.focusInfo = {\n itemIndex,\n fieldName,\n selectionStart,\n selectionEnd\n };\n }\n\n // Restore focus after update\n private restoreFocus(): void {\n if (!this.focusInfo) {\n return;\n }\n\n const { itemIndex, fieldName, selectionStart, selectionEnd } =\n this.focusInfo;\n\n // Find the target element by array item index\n const arrayItemId = `array-item-${itemIndex}`;\n const arrayItemElement = this.shadowRoot?.getElementById(arrayItemId);\n\n if (!arrayItemElement) {\n // If the exact item doesn't exist (e.g., due to reordering), try to find by field name\n const allItems = this.shadowRoot?.querySelectorAll('.array-item');\n if (allItems && allItems.length > itemIndex) {\n const fallbackItem = allItems[itemIndex];\n if (fallbackItem) {\n this.attemptFocusRestore(\n fallbackItem as HTMLElement,\n fieldName,\n selectionStart,\n selectionEnd\n );\n }\n }\n this.focusInfo = null;\n return;\n }\n\n this.attemptFocusRestore(\n arrayItemElement,\n fieldName,\n selectionStart,\n selectionEnd\n );\n this.focusInfo = null;\n }\n\n private attemptFocusRestore(\n container: HTMLElement,\n fieldName: string,\n selectionStart?: number,\n selectionEnd?: number\n ): void {\n // Look for the field container first\n const fieldContainer = container.querySelector(\n `[data-field-name=\"${fieldName}\"]`\n );\n\n let targetElement: HTMLElement | null = null;\n\n if (fieldContainer) {\n // Look for temba components or input elements within the field container\n targetElement = fieldContainer.querySelector(\n 'temba-textinput, temba-completion, input, textarea'\n ) as HTMLElement;\n }\n\n // Fallback: search entire container\n if (!targetElement) {\n const selectors = [\n `temba-textinput[name=\"${fieldName}\"]`,\n `temba-completion[name=\"${fieldName}\"]`,\n `input[name=\"${fieldName}\"]`,\n `textarea[name=\"${fieldName}\"]`,\n `[name=\"${fieldName}\"]`\n ];\n\n for (const selector of selectors) {\n targetElement = container.querySelector(selector) as HTMLElement;\n if (targetElement) break;\n }\n }\n\n if (targetElement) {\n // Use multiple animation frames to ensure DOM is fully settled\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n try {\n targetElement.focus();\n\n // Restore selection if it's a text input\n if (selectionStart !== undefined && selectionEnd !== undefined) {\n // For temba components, we need to focus the inner input\n let inputForSelection = targetElement;\n if (targetElement.tagName?.toLowerCase().startsWith('temba-')) {\n const innerInput =\n targetElement.shadowRoot?.querySelector('input, textarea') ||\n targetElement.querySelector('input, textarea');\n if (innerInput && 'setSelectionRange' in innerInput) {\n inputForSelection = innerInput as any;\n }\n }\n\n if ('setSelectionRange' in inputForSelection) {\n (inputForSelection as any).setSelectionRange(\n selectionStart,\n selectionEnd\n );\n }\n }\n } catch (error) {\n // Ignore focus errors - element might not be focusable\n // Focus restoration failed, silently continue\n }\n });\n });\n }\n }\n\n createEmptyItem(): ListItem {\n return {};\n }\n\n protected handleFieldChange(\n itemIndex: number,\n fieldName: string,\n newValue: any\n ) {\n let updatedItems: any[];\n\n if (this.onItemChange) {\n updatedItems = this.onItemChange(\n itemIndex,\n fieldName,\n newValue,\n this._items\n );\n } else {\n updatedItems = [...this._items];\n updatedItems[itemIndex] = {\n ...updatedItems[itemIndex],\n [fieldName]: newValue\n };\n }\n\n this.updateValue(updatedItems);\n }\n\n // Override Lit's update lifecycle methods for focus preservation\n protected willUpdate(changedProperties: Map<string, any>): void {\n super.willUpdate(changedProperties);\n\n // Capture focus before update if items are changing\n if (changedProperties.has('_items') || changedProperties.has('value')) {\n this.captureFocus();\n }\n }\n\n updated(changedProperties: Map<string, any>): void {\n super.updated(changedProperties);\n\n // Restore focus after update if items changed\n if (changedProperties.has('_items') || changedProperties.has('value')) {\n this.restoreFocus();\n }\n }\n\n private handleOrderChanged(event: CustomEvent): void {\n const detail = event.detail;\n\n // Handle swap-based logic from SortableList\n if (detail.swap && Array.isArray(detail.swap) && detail.swap.length === 2) {\n const [fromIdx, toIdx] = detail.swap;\n\n // Only reorder if the indexes are different and valid\n if (\n fromIdx !== toIdx &&\n fromIdx >= 0 &&\n toIdx >= 0 &&\n fromIdx < this._items.length &&\n toIdx < this._items.length\n ) {\n const updatedItems = [...this._items];\n // Move the item using splice operations\n const movedItem = updatedItems.splice(fromIdx, 1)[0];\n updatedItems.splice(toIdx, 0, movedItem);\n this.updateValue(updatedItems);\n }\n }\n }\n\n renderWidget(): TemplateResult {\n const items = this.displayItems;\n\n const itemsContent = items.map((item, index) => {\n const renderedItem = this.renderItem(item, index);\n\n if (this.sortable && !this.isEmptyItem(item)) {\n // Wrap non-empty items with sortable class and unique ID for drag-and-drop\n return html`\n <div class=\"sortable\" id=\"array-item-${index}\">${renderedItem}</div>\n `;\n } else {\n // Non-sortable items or empty items don't get the sortable wrapper\n return renderedItem;\n }\n });\n\n if (this.sortable) {\n return html`\n <div class=${this.getContainerClass()}>\n <temba-sortable-list\n dragHandle=\"drag-handle\"\n gap=\"0.4em\"\n @temba-order-changed=${this.handleOrderChanged}\n style=\"display: grid; grid-template-columns: 1fr; gap: 8px;\"\n >\n ${itemsContent}\n </temba-sortable-list>\n ${this.shouldShowAddButton() ? this.renderAddButton() : ''}\n </div>\n `;\n } else {\n // Non-sortable rendering (original behavior)\n return html`\n <div class=${this.getContainerClass()}>\n <div\n class=\"list-items\"\n style=\"display: grid; grid-template-columns: 1fr; gap: 8px;\"\n >\n ${itemsContent}\n </div>\n ${this.shouldShowAddButton() ? this.renderAddButton() : ''}\n </div>\n `;\n }\n }\n\n private computeFieldValue(\n itemIndex: number,\n fieldName: string,\n config: FieldConfig\n ): any {\n const item = this._items[itemIndex] || {};\n const currentValue = item[fieldName];\n\n if (config.computeValue) {\n return config.computeValue(item, currentValue);\n }\n\n // For select fields, ensure we return the right type\n /*if (config.type === 'select') {\n console.log('computeFieldValue select', currentValue, config);\n const selectConfig = config as SelectFieldConfig;\n if (currentValue === undefined || currentValue === null) {\n return selectConfig.multi ? [] : '';\n }\n }*/\n\n return currentValue;\n }\n\n private renderArrayField(\n itemIndex: number,\n fieldName: string,\n config: FieldConfig\n ): TemplateResult {\n const computedValue = this.computeFieldValue(itemIndex, fieldName, config);\n\n // Extract flavor from select config if available\n const flavor =\n config.type === 'select' ? (config as any).flavor || 'small' : 'small';\n\n // Build container style with width/maxWidth if specified\n let containerStyle = '';\n if (config.width) {\n containerStyle = `width: ${config.width};`;\n } else if (config.maxWidth) {\n containerStyle = `max-width: ${config.maxWidth};`;\n }\n\n // Use FieldRenderer for consistent field rendering\n const fieldContent = FieldRenderer.renderField(\n fieldName,\n config,\n computedValue,\n {\n showLabel: false, // ArrayEditor doesn't show labels for individual fields\n flavor: flavor,\n extraClasses: 'form-control',\n onChange: (e: Event) => {\n let value: any;\n const target = e.target as any;\n\n // Handle different field types and their change events\n if (config.type === 'select') {\n // Use consistent temba-select value normalization\n value = target.values;\n } else {\n // For other field types, use the target value directly\n value = target.value;\n }\n\n this.handleFieldChange(itemIndex, fieldName, value);\n }\n }\n );\n\n // Wrap in container with style if maxWidth is specified\n if (containerStyle) {\n return html`<div style=\"${containerStyle}\">${fieldContent}</div>`;\n }\n\n return fieldContent;\n }\n\n renderItem(item: ListItem, index: number): TemplateResult {\n const canRemove = this.canRemoveItem(index);\n\n // Render fields and track if any value fields are visible\n const fieldElements: TemplateResult[] = [];\n let hasVisibleValueField = false;\n\n Object.entries(this.itemConfig).forEach(([fieldName, config]) => {\n // Check visibility condition\n let isVisible = true;\n if (config.conditions?.visible) {\n try {\n const currentItem = this._items[index] || {};\n isVisible = config.conditions.visible(currentItem);\n } catch (error) {\n console.error(`Error checking visibility for ${fieldName}:`, error);\n }\n }\n\n if (isVisible) {\n // Check if this is a value field (text input without fixed sizing)\n const isValueField =\n !config.width && !config.maxWidth && config.type === 'text';\n if (isValueField) {\n hasVisibleValueField = true;\n }\n\n fieldElements.push(html`\n <div\n data-field-name=\"${fieldName}\"\n style=\"${config.width || config.maxWidth || config.type === 'select'\n ? 'flex:none'\n : 'flex:1'}\"\n >\n ${this.renderArrayField(index, fieldName, config)}\n </div>\n `);\n }\n });\n\n // If no value fields are visible, add a spacer to maintain alignment\n if (!hasVisibleValueField) {\n // Insert spacer after operator (first field) and before category (last field)\n fieldElements.splice(\n -1,\n 0,\n html`<div class=\"field field-flex spacer\" style=\"flex-grow:1\"></div>`\n );\n }\n\n return html`\n <div class=\"array-item\" id=\"array-item-${index}\">\n <div\n class=\"item-fields ${canRemove ? '' : 'removable'}\"\n style=\"display: flex; gap: 12px; align-items: center\"\n >\n ${this.sortable\n ? html`<temba-icon\n name=${Icon.sort}\n style=\"margin-right: -6px;\"\n class=\"drag-handle\"\n ></temba-icon>`\n : null}\n ${fieldElements}\n <button\n @click=${canRemove ? () => this.removeItem(index) : undefined}\n class=\"remove-btn\"\n style=\"\n padding: 4px;\n border: 1px solid #ccc;\n border-radius: 4px;\n background: white;\n cursor: pointer;\n background: #fefefe;\n color: #999;\n font-size: 14px;\n \"\n ?disabled=${!canRemove}\n >\n <temba-icon name=\"x\"></temba-icon>\n </button>\n </div>\n </div>\n `;\n }\n\n protected getContainerClass(): string {\n return 'array-editor';\n }\n\n static get styles() {\n return css`\n ${super.styles}\n\n .item-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n\n .item-title {\n font-weight: 600;\n color: #333;\n }\n\n .field {\n /* Base field styles */\n }\n\n .spacer {\n /* Empty spacer to maintain layout alignment */\n }\n\n .add-btn,\n .remove-btn {\n padding: 4px;\n border: 1px solid #ccc;\n border-radius: 4px;\n background: white;\n cursor: pointer;\n font-size: 14px;\n }\n\n .add-btn:hover,\n .remove-btn:hover {\n background: #f8f8f8;\n }\n\n .remove-btn {\n background: #fefefe;\n color: #999;\n }\n\n .removable .remove-btn {\n visibility: hidden;\n cursor: default;\n }\n\n .removable .drag-handle {\n visibility: hidden;\n cursor: default;\n }\n\n .drag-handle {\n cursor: grab;\n color: #ccc;\n }\n `;\n }\n}\n"]}
1
+ {"version":3,"file":"ArrayEditor.js","sourceRoot":"","sources":["../../../src/form/ArrayEditor.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, css, TemplateResult } from 'lit';\nimport { customElement, property } from 'lit/decorators.js';\nimport { FieldConfig } from '../flow/types';\nimport { BaseListEditor, ListItem } from './BaseListEditor';\nimport { FieldRenderer } from './FieldRenderer';\nimport '../list/SortableList';\nimport { Icon } from '../Icons';\n\n@customElement('temba-array-editor')\nexport class TembaArrayEditor extends BaseListEditor<ListItem> {\n @property({ type: Object })\n itemConfig: Record<string, FieldConfig> = {};\n\n @property({ type: String })\n itemLabel = 'Item';\n\n @property({ type: Function })\n onItemChange?: (\n itemIndex: number,\n field: string,\n value: any,\n allItems: any[]\n ) => any[];\n\n @property({ type: Function })\n isEmptyItemFn?: (item: any) => boolean;\n\n @property({ type: Boolean })\n sortable = false;\n\n @property({ type: Boolean })\n maintainEmptyItem = true; // Enable by default for better UX\n\n // Focus preservation properties\n private focusInfo: {\n itemIndex: number;\n fieldName: string;\n selectionStart?: number;\n selectionEnd?: number;\n } | null = null;\n\n constructor() {\n super();\n this._items = [];\n }\n\n // External API\n @property({ type: Array })\n get value(): any[] {\n return [...this._items];\n }\n\n set value(newValue: any[]) {\n this._items = newValue || [];\n this.requestUpdate();\n }\n\n // Implement abstract methods\n isEmptyItem(item: ListItem): boolean {\n // Use configurable function if provided\n if (this.isEmptyItemFn) {\n return this.isEmptyItemFn(item);\n }\n\n // Default behavior: check if all values are empty\n const values = Object.values(item);\n if (values.length === 0) {\n return true;\n }\n\n return values.every(\n (value) => value === undefined || value === null || value === ''\n );\n }\n\n // Override cleanItems to be more permissive for form data\n protected cleanItems(items: ListItem[]): any {\n // For runtime attachments, keep items that have at least one non-empty field\n return items.filter((item) => {\n const values = Object.values(item);\n return (\n values.length > 0 &&\n values.some(\n (value) => value !== undefined && value !== null && value !== ''\n )\n );\n });\n }\n\n // Capture focus information before update\n private captureFocus(): void {\n const activeElement = this.shadowRoot?.activeElement as HTMLElement;\n\n // Also try document.activeElement as a fallback\n const globalActive = document.activeElement as HTMLElement;\n let targetElement = activeElement || globalActive;\n\n // If active element is within this component's shadow root, use it\n if (globalActive && this.shadowRoot?.contains(globalActive)) {\n targetElement = globalActive;\n }\n\n if (!targetElement) {\n this.focusInfo = null;\n return;\n }\n\n // Find the array item container by traversing up the DOM\n let currentElement = targetElement;\n let arrayItemElement: HTMLElement | null = null;\n\n // Traverse up through shadow DOM boundaries\n while (currentElement) {\n if (currentElement.classList?.contains('array-item')) {\n arrayItemElement = currentElement;\n break;\n }\n\n // Move up to parent, or cross shadow boundaries\n if (currentElement.parentElement) {\n currentElement = currentElement.parentElement;\n } else if (\n currentElement.parentNode &&\n (currentElement.parentNode as any).host\n ) {\n // Cross shadow boundary\n currentElement = (currentElement.parentNode as any).host;\n } else {\n break;\n }\n }\n\n if (!arrayItemElement) {\n this.focusInfo = null;\n return;\n }\n\n // Find the item index by looking at the item ID\n const itemIdMatch = arrayItemElement.id?.match(/array-item-(\\d+)/);\n if (!itemIdMatch) {\n this.focusInfo = null;\n return;\n }\n\n const itemIndex = parseInt(itemIdMatch[1], 10);\n\n // Determine the field name by examining the input element and its containers\n let fieldName = '';\n\n // First, check if it's a temba component with a name attribute\n if (targetElement.tagName?.toLowerCase().startsWith('temba-')) {\n fieldName =\n (targetElement as any).name || targetElement.getAttribute('name') || '';\n }\n\n // If not found, check regular HTML elements\n if (\n !fieldName &&\n targetElement.hasAttribute &&\n targetElement.hasAttribute('name')\n ) {\n fieldName = targetElement.getAttribute('name') || '';\n }\n\n // If still not found, look for data-field-name in parent containers\n if (!fieldName) {\n let searchElement = targetElement;\n while (searchElement && searchElement !== arrayItemElement) {\n if (\n searchElement.hasAttribute &&\n searchElement.hasAttribute('data-field-name')\n ) {\n fieldName = searchElement.getAttribute('data-field-name') || '';\n break;\n }\n searchElement = searchElement.parentElement;\n }\n }\n\n if (!fieldName) {\n this.focusInfo = null;\n return;\n }\n\n // Capture selection for text inputs (try the actual input element inside temba components)\n let inputForSelection = targetElement;\n if (targetElement.tagName?.toLowerCase().startsWith('temba-')) {\n // Look for the actual input element inside the temba component\n const innerInput =\n targetElement.shadowRoot?.querySelector('input, textarea') ||\n targetElement.querySelector('input, textarea');\n if (innerInput) {\n inputForSelection = innerInput as HTMLElement;\n }\n }\n\n const selectionStart = (inputForSelection as any).selectionStart;\n const selectionEnd = (inputForSelection as any).selectionEnd;\n\n this.focusInfo = {\n itemIndex,\n fieldName,\n selectionStart,\n selectionEnd\n };\n }\n\n // Restore focus after update\n private restoreFocus(): void {\n if (!this.focusInfo) {\n return;\n }\n\n const { itemIndex, fieldName, selectionStart, selectionEnd } =\n this.focusInfo;\n\n // Find the target element by array item index\n const arrayItemId = `array-item-${itemIndex}`;\n const arrayItemElement = this.shadowRoot?.getElementById(arrayItemId);\n\n if (!arrayItemElement) {\n // If the exact item doesn't exist (e.g., due to reordering), try to find by field name\n const allItems = this.shadowRoot?.querySelectorAll('.array-item');\n if (allItems && allItems.length > itemIndex) {\n const fallbackItem = allItems[itemIndex];\n if (fallbackItem) {\n this.attemptFocusRestore(\n fallbackItem as HTMLElement,\n fieldName,\n selectionStart,\n selectionEnd\n );\n }\n }\n this.focusInfo = null;\n return;\n }\n\n this.attemptFocusRestore(\n arrayItemElement,\n fieldName,\n selectionStart,\n selectionEnd\n );\n this.focusInfo = null;\n }\n\n private attemptFocusRestore(\n container: HTMLElement,\n fieldName: string,\n selectionStart?: number,\n selectionEnd?: number\n ): void {\n // Look for the field container first\n const fieldContainer = container.querySelector(\n `[data-field-name=\"${fieldName}\"]`\n );\n\n let targetElement: HTMLElement | null = null;\n\n if (fieldContainer) {\n // Look for temba components or input elements within the field container\n targetElement = fieldContainer.querySelector(\n 'temba-textinput, temba-completion, input, textarea'\n ) as HTMLElement;\n }\n\n // Fallback: search entire container\n if (!targetElement) {\n const selectors = [\n `temba-textinput[name=\"${fieldName}\"]`,\n `temba-completion[name=\"${fieldName}\"]`,\n `input[name=\"${fieldName}\"]`,\n `textarea[name=\"${fieldName}\"]`,\n `[name=\"${fieldName}\"]`\n ];\n\n for (const selector of selectors) {\n targetElement = container.querySelector(selector) as HTMLElement;\n if (targetElement) break;\n }\n }\n\n if (targetElement) {\n // Use multiple animation frames to ensure DOM is fully settled\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n try {\n targetElement.focus();\n\n // Restore selection if it's a text input\n if (selectionStart !== undefined && selectionEnd !== undefined) {\n // For temba components, we need to focus the inner input\n let inputForSelection = targetElement;\n if (targetElement.tagName?.toLowerCase().startsWith('temba-')) {\n const innerInput =\n targetElement.shadowRoot?.querySelector('input, textarea') ||\n targetElement.querySelector('input, textarea');\n if (innerInput && 'setSelectionRange' in innerInput) {\n inputForSelection = innerInput as any;\n }\n }\n\n if ('setSelectionRange' in inputForSelection) {\n (inputForSelection as any).setSelectionRange(\n selectionStart,\n selectionEnd\n );\n }\n }\n } catch (error) {\n // Ignore focus errors - element might not be focusable\n // Focus restoration failed, silently continue\n }\n });\n });\n }\n }\n\n createEmptyItem(): ListItem {\n return {};\n }\n\n protected handleFieldChange(\n itemIndex: number,\n fieldName: string,\n newValue: any\n ) {\n let updatedItems: any[];\n\n if (this.onItemChange) {\n updatedItems = this.onItemChange(\n itemIndex,\n fieldName,\n newValue,\n this._items\n );\n } else {\n updatedItems = [...this._items];\n updatedItems[itemIndex] = {\n ...updatedItems[itemIndex],\n [fieldName]: newValue\n };\n }\n\n this.updateValue(updatedItems);\n }\n\n // Override Lit's update lifecycle methods for focus preservation\n protected willUpdate(changedProperties: Map<string, any>): void {\n super.willUpdate(changedProperties);\n\n // Capture focus before update if items are changing\n if (changedProperties.has('_items') || changedProperties.has('value')) {\n this.captureFocus();\n }\n }\n\n updated(changedProperties: Map<string, any>): void {\n super.updated(changedProperties);\n\n // Restore focus after update if items changed\n if (changedProperties.has('_items') || changedProperties.has('value')) {\n this.restoreFocus();\n }\n }\n\n private handleOrderChanged(event: CustomEvent): void {\n const detail = event.detail;\n\n // Handle swap-based logic from SortableList\n if (detail.swap && Array.isArray(detail.swap) && detail.swap.length === 2) {\n const [fromIdx, toIdx] = detail.swap;\n\n // Only reorder if the indexes are different and valid\n if (\n fromIdx !== toIdx &&\n fromIdx >= 0 &&\n toIdx >= 0 &&\n fromIdx < this._items.length &&\n toIdx < this._items.length\n ) {\n const updatedItems = [...this._items];\n // Move the item using splice operations\n const movedItem = updatedItems.splice(fromIdx, 1)[0];\n updatedItems.splice(toIdx, 0, movedItem);\n this.updateValue(updatedItems);\n }\n }\n }\n\n renderWidget(): TemplateResult {\n const items = this.displayItems;\n\n const itemsContent = items.map((item, index) => {\n const renderedItem = this.renderItem(item, index);\n\n if (this.sortable && !this.isEmptyItem(item)) {\n // Wrap non-empty items with sortable class and unique ID for drag-and-drop\n return html`\n <div class=\"sortable\" id=\"array-item-${index}\">${renderedItem}</div>\n `;\n } else {\n // Non-sortable items or empty items don't get the sortable wrapper\n return renderedItem;\n }\n });\n\n if (this.sortable) {\n return html`\n <div class=${this.getContainerClass()}>\n <temba-sortable-list\n dragHandle=\"drag-handle\"\n gap=\"0.4em\"\n @temba-order-changed=${this.handleOrderChanged}\n style=\"display: grid; grid-template-columns: 1fr; gap: 8px;\"\n >\n ${itemsContent}\n </temba-sortable-list>\n ${this.shouldShowAddButton() ? this.renderAddButton() : ''}\n </div>\n `;\n } else {\n // Non-sortable rendering (original behavior)\n return html`\n <div class=${this.getContainerClass()}>\n <div\n class=\"list-items\"\n style=\"display: grid; grid-template-columns: 1fr; gap: 8px;\"\n >\n ${itemsContent}\n </div>\n ${this.shouldShowAddButton() ? this.renderAddButton() : ''}\n </div>\n `;\n }\n }\n\n private computeFieldValue(\n itemIndex: number,\n fieldName: string,\n config: FieldConfig\n ): any {\n const item = this._items[itemIndex] || {};\n const currentValue = item[fieldName];\n\n if (config.computeValue) {\n return config.computeValue(item, currentValue);\n }\n\n // For select fields, ensure we return the right type\n /*if (config.type === 'select') {\n console.log('computeFieldValue select', currentValue, config);\n const selectConfig = config as SelectFieldConfig;\n if (currentValue === undefined || currentValue === null) {\n return selectConfig.multi ? [] : '';\n }\n }*/\n\n return currentValue;\n }\n\n private renderArrayField(\n itemIndex: number,\n fieldName: string,\n config: FieldConfig\n ): TemplateResult {\n const computedValue = this.computeFieldValue(itemIndex, fieldName, config);\n\n // Extract flavor from select config if available\n const flavor =\n config.type === 'select' ? (config as any).flavor || 'small' : 'small';\n\n // Build container style with width/maxWidth if specified\n let containerStyle = '';\n if (config.width) {\n containerStyle = `width: ${config.width};`;\n } else if (config.maxWidth) {\n containerStyle = `max-width: ${config.maxWidth};`;\n }\n\n // Use FieldRenderer for consistent field rendering\n const fieldContent = FieldRenderer.renderField(\n fieldName,\n config,\n computedValue,\n {\n showLabel: false, // ArrayEditor doesn't show labels for individual fields\n flavor: flavor,\n extraClasses: 'form-control',\n onChange: (e: Event) => {\n let value: any;\n const target = e.target as any;\n\n // Handle different field types and their change events\n if (config.type === 'select') {\n // Use consistent temba-select value normalization\n value = target.values;\n } else {\n // For other field types, use the target value directly\n value = target.value;\n }\n\n this.handleFieldChange(itemIndex, fieldName, value);\n }\n }\n );\n\n // Wrap in container with style if maxWidth is specified\n if (containerStyle) {\n return html`<div style=\"${containerStyle}\">${fieldContent}</div>`;\n }\n\n return fieldContent;\n }\n\n renderItem(item: ListItem, index: number): TemplateResult {\n const canRemove = this.canRemoveItem(index);\n\n // Render fields and track if any value fields are visible\n const fieldElements: TemplateResult[] = [];\n let hasVisibleValueField = false;\n\n Object.entries(this.itemConfig).forEach(([fieldName, config]) => {\n // Check visibility condition\n let isVisible = true;\n if (config.conditions?.visible) {\n try {\n const currentItem = this._items[index] || {};\n isVisible = config.conditions.visible(currentItem);\n } catch (error) {\n console.error(`Error checking visibility for ${fieldName}:`, error);\n }\n }\n\n if (isVisible) {\n // Check if this is a value field (text input without fixed sizing)\n const isValueField =\n !config.width && !config.maxWidth && config.type === 'text';\n if (isValueField) {\n hasVisibleValueField = true;\n }\n\n fieldElements.push(html`\n <div\n data-field-name=\"${fieldName}\"\n style=\"${config.width || config.maxWidth || config.type === 'select'\n ? 'flex:none'\n : 'flex:1'}\"\n >\n ${this.renderArrayField(index, fieldName, config)}\n </div>\n `);\n }\n });\n\n // If no value fields are visible, add a spacer to maintain alignment\n if (!hasVisibleValueField) {\n // Insert spacer after operator (first field) and before category (last field)\n fieldElements.splice(\n -1,\n 0,\n html`<div class=\"field field-flex spacer\" style=\"flex-grow:1\"></div>`\n );\n }\n\n return html`\n <div class=\"array-item\" id=\"array-item-${index}\">\n <div\n class=\"item-fields ${canRemove ? '' : 'removable'}\"\n style=\"display: flex; gap: 12px; align-items: center\"\n >\n ${this.sortable\n ? html`<temba-icon\n name=${Icon.sort}\n style=\"margin-right: -6px;\"\n class=\"drag-handle\"\n ></temba-icon>`\n : null}\n ${fieldElements}\n <button\n @click=${canRemove ? () => this.removeItem(index) : undefined}\n class=\"remove-btn\"\n style=\"\n padding: 4px;\n border: 1px solid #ccc;\n border-radius: 4px;\n background: white;\n cursor: pointer;\n background: #fefefe;\n color: #999;\n font-size: 14px;\n \"\n ?disabled=${!canRemove}\n >\n <temba-icon name=\"x\"></temba-icon>\n </button>\n </div>\n </div>\n `;\n }\n\n protected getContainerClass(): string {\n return 'array-editor';\n }\n\n static get styles() {\n return css`\n ${super.styles}\n\n .item-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n\n .item-title {\n font-weight: 600;\n color: #333;\n }\n\n .field {\n /* Base field styles */\n }\n\n .spacer {\n /* Empty spacer to maintain layout alignment */\n }\n\n .add-btn,\n .remove-btn {\n padding: 4px;\n border: 1px solid #ccc;\n border-radius: 4px;\n background: white;\n cursor: pointer;\n font-size: 14px;\n }\n\n .add-btn:hover,\n .remove-btn:hover {\n background: #f8f8f8;\n }\n\n .remove-btn {\n background: #fefefe;\n color: #999;\n }\n\n .removable .remove-btn {\n opacity: 0.3;\n cursor: default;\n pointer-events: none;\n }\n\n .removable .drag-handle {\n opacity: 0.3;\n cursor: default;\n pointer-events: none;\n }\n\n .drag-handle {\n cursor: grab;\n color: #ccc;\n }\n `;\n }\n}\n"]}