@nyaruka/temba-components 0.129.2 → 0.129.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (524) hide show
  1. package/.github/workflows/build.yml +6 -5
  2. package/.github/workflows/coverage.yml +80 -0
  3. package/CHANGELOG.md +22 -0
  4. package/README.md +6 -0
  5. package/check-coverage.js +133 -0
  6. package/demo/data/flows/sample-flow.json +107 -100
  7. package/dist/temba-components.js +893 -476
  8. package/dist/temba-components.js.map +1 -1
  9. package/generate-coverage-badge.sh +69 -0
  10. package/out-tsc/src/{vectoricon/index.js → Icons.js} +1 -1
  11. package/out-tsc/src/Icons.js.map +1 -0
  12. package/out-tsc/src/display/Alert.js.map +1 -0
  13. package/out-tsc/src/display/Anchor.js.map +1 -0
  14. package/out-tsc/src/display/Button.js.map +1 -0
  15. package/out-tsc/src/{charcount → display}/CharCount.js +159 -2
  16. package/out-tsc/src/display/CharCount.js.map +1 -0
  17. package/out-tsc/src/display/Chat.js.map +1 -0
  18. package/out-tsc/src/display/ContactName.js.map +1 -0
  19. package/out-tsc/src/display/ContactUrn.js.map +1 -0
  20. package/out-tsc/src/display/Dropdown.js.map +1 -0
  21. package/out-tsc/src/{vectoricon/VectorIcon.js → display/Icon.js} +2 -2
  22. package/out-tsc/src/display/Icon.js.map +1 -0
  23. package/out-tsc/src/display/Label.js.map +1 -0
  24. package/out-tsc/src/{leafletmap → display}/LeafletMap.js +16 -1
  25. package/out-tsc/src/display/LeafletMap.js.map +1 -0
  26. package/out-tsc/src/display/Lightbox.js.map +1 -0
  27. package/out-tsc/src/{loading → display}/Loading.js.map +1 -1
  28. package/out-tsc/src/{options → display}/Options.js.map +1 -1
  29. package/out-tsc/src/display/ProgressBar.js.map +1 -0
  30. package/out-tsc/src/display/TembaDate.js.map +1 -0
  31. package/out-tsc/src/display/TembaUser.js.map +1 -0
  32. package/out-tsc/src/display/Thumbnail.js.map +1 -0
  33. package/out-tsc/src/{tip → display}/Tip.js +1 -2
  34. package/out-tsc/src/display/Tip.js.map +1 -0
  35. package/out-tsc/src/display/Toast.js.map +1 -0
  36. package/out-tsc/src/display/sms/gsmsplitter.js.map +1 -0
  37. package/out-tsc/src/display/sms/gsmvalidator.js.map +1 -0
  38. package/out-tsc/src/display/sms/index.js.map +1 -0
  39. package/out-tsc/src/display/sms/unicodesplitter.js.map +1 -0
  40. package/out-tsc/src/events.js +2 -0
  41. package/out-tsc/src/events.js.map +1 -0
  42. package/out-tsc/src/excellent/ExcellentParser.js.map +1 -0
  43. package/out-tsc/src/excellent/helpers.js.map +1 -0
  44. package/out-tsc/src/flow/Editor.js +533 -140
  45. package/out-tsc/src/flow/Editor.js.map +1 -1
  46. package/out-tsc/src/flow/EditorNode.js +287 -20
  47. package/out-tsc/src/flow/EditorNode.js.map +1 -1
  48. package/out-tsc/src/flow/Plumber.js +154 -74
  49. package/out-tsc/src/flow/Plumber.js.map +1 -1
  50. package/out-tsc/src/flow/StickyNote.js +153 -9
  51. package/out-tsc/src/flow/StickyNote.js.map +1 -1
  52. package/out-tsc/src/flow/config.js +88 -18
  53. package/out-tsc/src/flow/config.js.map +1 -1
  54. package/out-tsc/src/flow/render.js +327 -10
  55. package/out-tsc/src/flow/render.js.map +1 -1
  56. package/out-tsc/src/{checkbox → form}/Checkbox.js +2 -2
  57. package/out-tsc/src/form/Checkbox.js.map +1 -0
  58. package/out-tsc/src/{colorpicker → form}/ColorPicker.js +1 -1
  59. package/out-tsc/src/form/ColorPicker.js.map +1 -0
  60. package/out-tsc/src/{completion → form}/Completion.js +2 -2
  61. package/out-tsc/src/form/Completion.js.map +1 -0
  62. package/out-tsc/src/{compose → form}/Compose.js +1 -1
  63. package/out-tsc/src/form/Compose.js.map +1 -0
  64. package/out-tsc/src/{contactsearch → form}/ContactSearch.js +2 -2
  65. package/out-tsc/src/form/ContactSearch.js.map +1 -0
  66. package/out-tsc/src/form/CroppieCSS.js.map +1 -0
  67. package/out-tsc/src/{datepicker → form}/DatePicker.js +1 -1
  68. package/out-tsc/src/form/DatePicker.js.map +1 -0
  69. package/out-tsc/src/{FormElement.js → form/FormElement.js} +1 -1
  70. package/out-tsc/src/form/FormElement.js.map +1 -0
  71. package/out-tsc/src/form/FormField.js.map +1 -0
  72. package/out-tsc/src/{imagepicker → form}/ImagePicker.js +2 -2
  73. package/out-tsc/src/form/ImagePicker.js.map +1 -0
  74. package/out-tsc/src/{mediapicker → form}/MediaPicker.js +1 -1
  75. package/out-tsc/src/form/MediaPicker.js.map +1 -0
  76. package/out-tsc/src/form/RangePicker.js.map +1 -0
  77. package/out-tsc/src/{slider → form}/TembaSlider.js +1 -1
  78. package/out-tsc/src/form/TembaSlider.js.map +1 -0
  79. package/out-tsc/src/{templates → form}/TemplateEditor.js +1 -1
  80. package/out-tsc/src/form/TemplateEditor.js.map +1 -0
  81. package/out-tsc/src/{textinput → form}/TextInput.js +3 -3
  82. package/out-tsc/src/form/TextInput.js.map +1 -0
  83. package/out-tsc/src/{omnibox → form/select}/Omnibox.js +2 -2
  84. package/out-tsc/src/form/select/Omnibox.js.map +1 -0
  85. package/out-tsc/src/{select → form/select}/PopupSelect.js +1 -1
  86. package/out-tsc/src/form/select/PopupSelect.js.map +1 -0
  87. package/out-tsc/src/{select → form/select}/Select.js +86 -87
  88. package/out-tsc/src/form/select/Select.js.map +1 -0
  89. package/out-tsc/src/{select → form/select}/UserSelect.js +1 -1
  90. package/out-tsc/src/form/select/UserSelect.js.map +1 -0
  91. package/out-tsc/src/{select → form/select}/WorkspaceSelect.js +1 -1
  92. package/out-tsc/src/form/select/WorkspaceSelect.js.map +1 -0
  93. package/out-tsc/src/interfaces.js +1 -0
  94. package/out-tsc/src/interfaces.js.map +1 -1
  95. package/out-tsc/src/layout/Dialog.js.map +1 -0
  96. package/out-tsc/src/layout/Mask.js.map +1 -0
  97. package/out-tsc/src/{dialog → layout}/Modax.js.map +1 -1
  98. package/out-tsc/src/layout/Resizer.js.map +1 -0
  99. package/out-tsc/src/layout/Tab.js.map +1 -0
  100. package/out-tsc/src/layout/TabPane.js.map +1 -0
  101. package/out-tsc/src/list/NotificationList.js +1 -1
  102. package/out-tsc/src/list/NotificationList.js.map +1 -1
  103. package/out-tsc/src/list/RunList.js +1 -1
  104. package/out-tsc/src/list/RunList.js.map +1 -1
  105. package/out-tsc/src/list/ShortcutList.js.map +1 -1
  106. package/out-tsc/src/list/TembaMenu.js +1 -1
  107. package/out-tsc/src/list/TembaMenu.js.map +1 -1
  108. package/out-tsc/src/list/TicketList.js +1 -1
  109. package/out-tsc/src/list/TicketList.js.map +1 -1
  110. package/out-tsc/src/{aliaseditor → live}/AliasEditor.js +1 -1
  111. package/out-tsc/src/live/AliasEditor.js.map +1 -0
  112. package/out-tsc/src/{contacts → live}/ContactBadges.js +1 -1
  113. package/out-tsc/src/live/ContactBadges.js.map +1 -0
  114. package/out-tsc/src/{contacts → live}/ContactChat.js +79 -3
  115. package/out-tsc/src/live/ContactChat.js.map +1 -0
  116. package/out-tsc/src/{contacts → live}/ContactDetails.js +1 -1
  117. package/out-tsc/src/live/ContactDetails.js.map +1 -0
  118. package/out-tsc/src/{contacts → live}/ContactFieldEditor.js +2 -2
  119. package/out-tsc/src/live/ContactFieldEditor.js.map +1 -0
  120. package/out-tsc/src/live/ContactFields.js.map +1 -0
  121. package/out-tsc/src/live/ContactNameFetch.js.map +1 -0
  122. package/out-tsc/src/{contacts → live}/ContactNotepad.js +1 -1
  123. package/out-tsc/src/{contacts → live}/ContactNotepad.js.map +1 -1
  124. package/out-tsc/src/{contacts → live}/ContactPending.js +1 -1
  125. package/out-tsc/src/live/ContactPending.js.map +1 -0
  126. package/out-tsc/src/live/ContactStoreElement.js.map +1 -0
  127. package/out-tsc/src/live/FieldManager.js.map +1 -0
  128. package/out-tsc/src/live/StartProgress.js.map +1 -0
  129. package/out-tsc/src/live/TembaChart.js.map +1 -0
  130. package/out-tsc/src/store/AppState.js +54 -24
  131. package/out-tsc/src/store/AppState.js.map +1 -1
  132. package/out-tsc/src/store/Store.js +1 -1
  133. package/out-tsc/src/store/Store.js.map +1 -1
  134. package/out-tsc/src/{utils/index.js → utils.js} +22 -1
  135. package/out-tsc/src/utils.js.map +1 -0
  136. package/out-tsc/src/webchat/WebChat.js +1 -1
  137. package/out-tsc/src/webchat/WebChat.js.map +1 -1
  138. package/out-tsc/temba-components.js +2 -2
  139. package/out-tsc/temba-components.js.map +1 -1
  140. package/out-tsc/temba-modules.js +54 -54
  141. package/out-tsc/temba-modules.js.map +1 -1
  142. package/out-tsc/temba-webchat.js +2 -2
  143. package/out-tsc/temba-webchat.js.map +1 -1
  144. package/out-tsc/test/temba-alert.test.js +1 -1
  145. package/out-tsc/test/temba-alert.test.js.map +1 -1
  146. package/out-tsc/test/temba-appstate-language.test.js +90 -0
  147. package/out-tsc/test/temba-appstate-language.test.js.map +1 -1
  148. package/out-tsc/test/temba-charcount.test.js.map +1 -1
  149. package/out-tsc/test/temba-chart.test.js +1 -1
  150. package/out-tsc/test/temba-chart.test.js.map +1 -1
  151. package/out-tsc/test/temba-checkbox.test.js.map +1 -1
  152. package/out-tsc/test/temba-color-picker.test.js +1 -1
  153. package/out-tsc/test/temba-color-picker.test.js.map +1 -1
  154. package/out-tsc/test/temba-completion.test.js +1 -1
  155. package/out-tsc/test/temba-completion.test.js.map +1 -1
  156. package/out-tsc/test/temba-compose.test.js +1 -1
  157. package/out-tsc/test/temba-compose.test.js.map +1 -1
  158. package/out-tsc/test/temba-contact-badges.test.js +1 -1
  159. package/out-tsc/test/temba-contact-badges.test.js.map +1 -1
  160. package/out-tsc/test/temba-contact-chat.test.js +3 -1
  161. package/out-tsc/test/temba-contact-chat.test.js.map +1 -1
  162. package/out-tsc/test/temba-contact-details.test.js +1 -1
  163. package/out-tsc/test/temba-contact-details.test.js.map +1 -1
  164. package/out-tsc/test/temba-contact-fields.test.js +1 -1
  165. package/out-tsc/test/temba-contact-fields.test.js.map +1 -1
  166. package/out-tsc/test/temba-contact-search.test.js +1 -1
  167. package/out-tsc/test/temba-contact-search.test.js.map +1 -1
  168. package/out-tsc/test/temba-date.test.js +5 -1
  169. package/out-tsc/test/temba-date.test.js.map +1 -1
  170. package/out-tsc/test/temba-datepicker.test.js +1 -1
  171. package/out-tsc/test/temba-datepicker.test.js.map +1 -1
  172. package/out-tsc/test/temba-dialog.test.js +1 -1
  173. package/out-tsc/test/temba-dialog.test.js.map +1 -1
  174. package/out-tsc/test/temba-dropdown.test.js +1 -1
  175. package/out-tsc/test/temba-dropdown.test.js.map +1 -1
  176. package/out-tsc/test/temba-excellent-helpers.test.js +316 -0
  177. package/out-tsc/test/temba-excellent-helpers.test.js.map +1 -0
  178. package/out-tsc/test/temba-field-manager.test.js.map +1 -1
  179. package/out-tsc/test/temba-flow-editor-node.test.js +414 -1
  180. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
  181. package/out-tsc/test/temba-flow-editor.test.js +185 -0
  182. package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
  183. package/out-tsc/test/temba-flow-plumber-connections.test.js +113 -0
  184. package/out-tsc/test/temba-flow-plumber-connections.test.js.map +1 -0
  185. package/out-tsc/test/temba-flow-plumber.test.js +73 -93
  186. package/out-tsc/test/temba-flow-plumber.test.js.map +1 -1
  187. package/out-tsc/test/temba-flow-render.test.js +624 -1
  188. package/out-tsc/test/temba-flow-render.test.js.map +1 -1
  189. package/out-tsc/test/temba-flow-self-routing.test.js +172 -0
  190. package/out-tsc/test/temba-flow-self-routing.test.js.map +1 -0
  191. package/out-tsc/test/temba-formfield.test.js.map +1 -1
  192. package/out-tsc/test/temba-icon.test.js +1 -1
  193. package/out-tsc/test/temba-icon.test.js.map +1 -1
  194. package/out-tsc/test/temba-integration-markdown.test.js.map +1 -1
  195. package/out-tsc/test/temba-label.test.js +1 -1
  196. package/out-tsc/test/temba-label.test.js.map +1 -1
  197. package/out-tsc/test/temba-lightbox.test.js +1 -1
  198. package/out-tsc/test/temba-lightbox.test.js.map +1 -1
  199. package/out-tsc/test/temba-markdown.test.js +127 -0
  200. package/out-tsc/test/temba-markdown.test.js.map +1 -0
  201. package/out-tsc/test/temba-menu.test.js +1 -1
  202. package/out-tsc/test/temba-menu.test.js.map +1 -1
  203. package/out-tsc/test/temba-modax.test.js +1 -1
  204. package/out-tsc/test/temba-modax.test.js.map +1 -1
  205. package/out-tsc/test/temba-modules.test.js +47 -0
  206. package/out-tsc/test/temba-modules.test.js.map +1 -0
  207. package/out-tsc/test/temba-omnibox.test.js +1 -1
  208. package/out-tsc/test/temba-omnibox.test.js.map +1 -1
  209. package/out-tsc/test/temba-options.test.js.map +1 -1
  210. package/out-tsc/test/temba-range-picker.test.js +9 -2
  211. package/out-tsc/test/temba-range-picker.test.js.map +1 -1
  212. package/out-tsc/test/temba-rapid-element.test.js +273 -0
  213. package/out-tsc/test/temba-rapid-element.test.js.map +1 -0
  214. package/out-tsc/test/temba-resize-element.test.js +85 -0
  215. package/out-tsc/test/temba-resize-element.test.js.map +1 -0
  216. package/out-tsc/test/temba-select.test.js +2 -2
  217. package/out-tsc/test/temba-select.test.js.map +1 -1
  218. package/out-tsc/test/temba-slider.test.js.map +1 -1
  219. package/out-tsc/test/temba-sticky-note.test.js +194 -0
  220. package/out-tsc/test/temba-sticky-note.test.js.map +1 -0
  221. package/out-tsc/test/temba-template-editor.test.js.map +1 -1
  222. package/out-tsc/test/temba-textinput.test.js +1 -1
  223. package/out-tsc/test/temba-textinput.test.js.map +1 -1
  224. package/out-tsc/test/temba-tip.test.js +1 -1
  225. package/out-tsc/test/temba-tip.test.js.map +1 -1
  226. package/out-tsc/test/temba-toast.test.js +1 -1
  227. package/out-tsc/test/temba-toast.test.js.map +1 -1
  228. package/out-tsc/test/temba-utils-index.test.js +1 -1
  229. package/out-tsc/test/temba-utils-index.test.js.map +1 -1
  230. package/out-tsc/test/temba-utils-uuid.test.js +38 -0
  231. package/out-tsc/test/temba-utils-uuid.test.js.map +1 -0
  232. package/out-tsc/test/temba-webchat.test.js +28 -12
  233. package/out-tsc/test/temba-webchat.test.js.map +1 -1
  234. package/out-tsc/test/utils.test.js +2 -6
  235. package/out-tsc/test/utils.test.js.map +1 -1
  236. package/package.json +18 -9
  237. package/rollup.components.mjs +1 -1
  238. package/screenshots/truth/datepicker/range-picker-all.png +0 -0
  239. package/screenshots/truth/datepicker/range-picker-button-states.png +0 -0
  240. package/screenshots/truth/datepicker/range-picker-default.png +0 -0
  241. package/screenshots/truth/datepicker/range-picker-editing-start.png +0 -0
  242. package/screenshots/truth/datepicker/range-picker-initial-values.png +0 -0
  243. package/screenshots/truth/datepicker/range-picker-week.png +0 -0
  244. package/screenshots/truth/datepicker/range-picker-year.png +0 -0
  245. package/screenshots/truth/sticky-note/blue-color.png +0 -0
  246. package/screenshots/truth/sticky-note/blue.png +0 -0
  247. package/screenshots/truth/sticky-note/color-picker-expanded.png +0 -0
  248. package/screenshots/truth/sticky-note/default.png +0 -0
  249. package/screenshots/truth/sticky-note/gray-color.png +0 -0
  250. package/screenshots/truth/sticky-note/gray.png +0 -0
  251. package/screenshots/truth/sticky-note/green-color.png +0 -0
  252. package/screenshots/truth/sticky-note/green.png +0 -0
  253. package/screenshots/truth/sticky-note/pink-color.png +0 -0
  254. package/screenshots/truth/sticky-note/pink.png +0 -0
  255. package/screenshots/truth/sticky-note/yellow-color.png +0 -0
  256. package/screenshots/truth/sticky-note/yellow.png +0 -0
  257. package/src/{charcount → display}/CharCount.ts +164 -2
  258. package/src/{vectoricon/VectorIcon.ts → display/Icon.ts} +1 -1
  259. package/src/{leafletmap → display}/LeafletMap.ts +19 -1
  260. package/src/{thumbnail → display}/Thumbnail.ts +1 -1
  261. package/src/{tip → display}/Tip.ts +1 -2
  262. package/src/{contacts/events.ts → events.ts} +1 -64
  263. package/src/flow/Editor.ts +655 -165
  264. package/src/flow/EditorNode.ts +337 -22
  265. package/src/flow/Plumber.ts +186 -79
  266. package/src/flow/StickyNote.ts +165 -9
  267. package/src/flow/config.ts +114 -18
  268. package/src/flow/render.ts +398 -11
  269. package/src/{checkbox → form}/Checkbox.ts +2 -2
  270. package/src/{colorpicker → form}/ColorPicker.ts +2 -2
  271. package/src/{completion → form}/Completion.ts +3 -3
  272. package/src/{compose → form}/Compose.ts +7 -7
  273. package/src/{contactsearch → form}/ContactSearch.ts +6 -6
  274. package/src/{datepicker → form}/DatePicker.ts +1 -1
  275. package/src/{FormElement.ts → form/FormElement.ts} +1 -1
  276. package/src/{imagepicker → form}/ImagePicker.ts +2 -2
  277. package/src/{mediapicker → form}/MediaPicker.ts +1 -1
  278. package/src/{slider → form}/TembaSlider.ts +1 -1
  279. package/src/{templates → form}/TemplateEditor.ts +2 -2
  280. package/src/{textinput → form}/TextInput.ts +5 -5
  281. package/src/{omnibox → form/select}/Omnibox.ts +2 -2
  282. package/src/{select → form/select}/PopupSelect.ts +1 -1
  283. package/src/{select → form/select}/Select.ts +124 -126
  284. package/src/{select → form/select}/UserSelect.ts +1 -1
  285. package/src/{select → form/select}/WorkspaceSelect.ts +1 -1
  286. package/src/interfaces.ts +2 -1
  287. package/src/{dialog → layout}/Dialog.ts +1 -1
  288. package/src/list/NotificationList.ts +2 -2
  289. package/src/list/RunList.ts +3 -3
  290. package/src/list/ShortcutList.ts +1 -1
  291. package/src/list/TembaMenu.ts +2 -2
  292. package/src/list/TicketList.ts +1 -1
  293. package/src/{aliaseditor → live}/AliasEditor.ts +3 -3
  294. package/src/{contacts → live}/ContactBadges.ts +1 -1
  295. package/src/{contacts → live}/ContactChat.ts +118 -8
  296. package/src/{contacts → live}/ContactDetails.ts +1 -1
  297. package/src/{contacts → live}/ContactFieldEditor.ts +4 -4
  298. package/src/{contacts → live}/ContactFields.ts +1 -1
  299. package/src/{contacts → live}/ContactNotepad.ts +1 -1
  300. package/src/{contacts → live}/ContactPending.ts +1 -1
  301. package/src/{chart → live}/TembaChart.ts +1 -1
  302. package/src/store/AppState.ts +75 -29
  303. package/src/store/Store.ts +1 -1
  304. package/src/store/flow-definition.d.ts +125 -0
  305. package/src/{utils/index.ts → utils.ts} +26 -10
  306. package/src/webchat/WebChat.ts +1 -1
  307. package/static/css/temba-components.css +1 -0
  308. package/svg.js +1 -4
  309. package/temba-components.ts +2 -2
  310. package/temba-modules.ts +54 -54
  311. package/temba-webchat.ts +2 -2
  312. package/test/temba-alert.test.ts +1 -1
  313. package/test/temba-appstate-language.test.ts +108 -0
  314. package/test/temba-charcount.test.ts +1 -1
  315. package/test/temba-chart.test.ts +1 -1
  316. package/test/temba-checkbox.test.ts +1 -1
  317. package/test/temba-color-picker.test.ts +1 -1
  318. package/test/temba-completion.test.ts +1 -1
  319. package/test/temba-compose.test.ts +1 -1
  320. package/test/temba-contact-badges.test.ts +1 -1
  321. package/test/temba-contact-chat.test.ts +6 -4
  322. package/test/temba-contact-details.test.ts +1 -1
  323. package/test/temba-contact-fields.test.ts +1 -1
  324. package/test/temba-contact-search.test.ts +2 -2
  325. package/test/temba-date.test.ts +8 -3
  326. package/test/temba-datepicker.test.ts +1 -1
  327. package/test/temba-dialog.test.ts +1 -1
  328. package/test/temba-dropdown.test.ts +1 -1
  329. package/test/temba-excellent-helpers.test.ts +417 -0
  330. package/test/temba-field-manager.test.ts +2 -2
  331. package/test/temba-flow-editor-node.test.ts +536 -1
  332. package/test/temba-flow-editor.test.ts +224 -0
  333. package/test/temba-flow-editor.test.ts.backup +563 -0
  334. package/test/temba-flow-plumber-connections.test.ts +142 -0
  335. package/test/temba-flow-plumber.test.ts +83 -120
  336. package/test/temba-flow-render.test.ts +787 -4
  337. package/test/temba-flow-self-routing.test.ts +215 -0
  338. package/test/temba-formfield.test.ts +1 -1
  339. package/test/temba-icon.test.ts +1 -1
  340. package/test/temba-integration-markdown.test.ts +1 -1
  341. package/test/temba-label.test.ts +1 -1
  342. package/test/temba-lightbox.test.ts +1 -1
  343. package/test/temba-markdown.test.ts +162 -0
  344. package/test/temba-menu.test.ts +1 -1
  345. package/test/temba-modax.test.ts +2 -2
  346. package/test/temba-modules.test.ts +56 -0
  347. package/test/temba-omnibox.test.ts +1 -1
  348. package/test/temba-options.test.ts +1 -1
  349. package/test/temba-range-picker.test.ts +17 -2
  350. package/test/temba-rapid-element.test.ts +341 -0
  351. package/test/temba-resize-element.test.ts +104 -0
  352. package/test/temba-select.test.ts +2 -2
  353. package/test/temba-slider.test.ts +1 -1
  354. package/test/temba-sticky-note.test.ts +281 -0
  355. package/test/temba-template-editor.test.ts +1 -1
  356. package/test/temba-textinput.test.ts +1 -1
  357. package/test/temba-tip.test.ts +1 -1
  358. package/test/temba-toast.test.ts +1 -1
  359. package/test/temba-utils-index.test.ts +1 -1
  360. package/test/temba-utils-index.test.ts.backup +1737 -0
  361. package/test/temba-utils-uuid.test.ts +48 -0
  362. package/test/temba-webchat.test.ts +30 -12
  363. package/test/utils.test.ts +5 -9
  364. package/web-dev-server.config.mjs +1 -1
  365. package/out-tsc/src/FormElement.js.map +0 -1
  366. package/out-tsc/src/alert/Alert.js.map +0 -1
  367. package/out-tsc/src/aliaseditor/AliasEditor.js.map +0 -1
  368. package/out-tsc/src/anchor/Anchor.js.map +0 -1
  369. package/out-tsc/src/button/Button.js.map +0 -1
  370. package/out-tsc/src/charcount/CharCount.js.map +0 -1
  371. package/out-tsc/src/charcount/helpers.js +0 -159
  372. package/out-tsc/src/charcount/helpers.js.map +0 -1
  373. package/out-tsc/src/chart/TembaChart.js.map +0 -1
  374. package/out-tsc/src/chat/Chat.js.map +0 -1
  375. package/out-tsc/src/checkbox/Checkbox.js.map +0 -1
  376. package/out-tsc/src/colorpicker/ColorPicker.js.map +0 -1
  377. package/out-tsc/src/completion/Completion.js.map +0 -1
  378. package/out-tsc/src/completion/ExcellentParser.js.map +0 -1
  379. package/out-tsc/src/completion/helpers.js.map +0 -1
  380. package/out-tsc/src/compose/Compose.js.map +0 -1
  381. package/out-tsc/src/contacts/ContactBadges.js.map +0 -1
  382. package/out-tsc/src/contacts/ContactChat.js.map +0 -1
  383. package/out-tsc/src/contacts/ContactDetails.js.map +0 -1
  384. package/out-tsc/src/contacts/ContactFieldEditor.js.map +0 -1
  385. package/out-tsc/src/contacts/ContactFields.js.map +0 -1
  386. package/out-tsc/src/contacts/ContactName.js.map +0 -1
  387. package/out-tsc/src/contacts/ContactNameFetch.js.map +0 -1
  388. package/out-tsc/src/contacts/ContactPending.js.map +0 -1
  389. package/out-tsc/src/contacts/ContactStoreElement.js.map +0 -1
  390. package/out-tsc/src/contacts/ContactUrn.js.map +0 -1
  391. package/out-tsc/src/contacts/events.js +0 -65
  392. package/out-tsc/src/contacts/events.js.map +0 -1
  393. package/out-tsc/src/contacts/helpers.js +0 -77
  394. package/out-tsc/src/contacts/helpers.js.map +0 -1
  395. package/out-tsc/src/contactsearch/ContactSearch.js.map +0 -1
  396. package/out-tsc/src/date/TembaDate.js.map +0 -1
  397. package/out-tsc/src/datepicker/DatePicker.js.map +0 -1
  398. package/out-tsc/src/datepicker/RangePicker.js.map +0 -1
  399. package/out-tsc/src/dialog/Dialog.js.map +0 -1
  400. package/out-tsc/src/dropdown/Dropdown.js.map +0 -1
  401. package/out-tsc/src/fields/FieldManager.js.map +0 -1
  402. package/out-tsc/src/formfield/FormField.js.map +0 -1
  403. package/out-tsc/src/imagepicker/CroppieCSS.js.map +0 -1
  404. package/out-tsc/src/imagepicker/ImagePicker.js.map +0 -1
  405. package/out-tsc/src/label/Label.js.map +0 -1
  406. package/out-tsc/src/leafletmap/LeafletMap.js.map +0 -1
  407. package/out-tsc/src/leafletmap/helpers.js +0 -17
  408. package/out-tsc/src/leafletmap/helpers.js.map +0 -1
  409. package/out-tsc/src/lightbox/Lightbox.js.map +0 -1
  410. package/out-tsc/src/mask/Mask.js.map +0 -1
  411. package/out-tsc/src/mediapicker/MediaPicker.js.map +0 -1
  412. package/out-tsc/src/omnibox/Omnibox.js.map +0 -1
  413. package/out-tsc/src/options/helpers.js +0 -28
  414. package/out-tsc/src/options/helpers.js.map +0 -1
  415. package/out-tsc/src/progress/ProgressBar.js.map +0 -1
  416. package/out-tsc/src/progress/StartProgress.js.map +0 -1
  417. package/out-tsc/src/resizer/Resizer.js.map +0 -1
  418. package/out-tsc/src/select/PopupSelect.js.map +0 -1
  419. package/out-tsc/src/select/Select.js.map +0 -1
  420. package/out-tsc/src/select/UserSelect.js.map +0 -1
  421. package/out-tsc/src/select/WorkspaceSelect.js.map +0 -1
  422. package/out-tsc/src/select/helpers.js +0 -1
  423. package/out-tsc/src/select/helpers.js.map +0 -1
  424. package/out-tsc/src/shadowless/Shadowless.js +0 -33
  425. package/out-tsc/src/shadowless/Shadowless.js.map +0 -1
  426. package/out-tsc/src/slider/TembaSlider.js.map +0 -1
  427. package/out-tsc/src/sms/gsmsplitter.js.map +0 -1
  428. package/out-tsc/src/sms/gsmvalidator.js.map +0 -1
  429. package/out-tsc/src/sms/index.js.map +0 -1
  430. package/out-tsc/src/sms/unicodesplitter.js.map +0 -1
  431. package/out-tsc/src/tabpane/Tab.js.map +0 -1
  432. package/out-tsc/src/tabpane/TabPane.js.map +0 -1
  433. package/out-tsc/src/templates/TemplateEditor.js.map +0 -1
  434. package/out-tsc/src/textinput/TextInput.js.map +0 -1
  435. package/out-tsc/src/textinput/helpers.js +0 -12
  436. package/out-tsc/src/textinput/helpers.js.map +0 -1
  437. package/out-tsc/src/thumbnail/Thumbnail.js.map +0 -1
  438. package/out-tsc/src/tip/Tip.js.map +0 -1
  439. package/out-tsc/src/tip/helpers.js +0 -7
  440. package/out-tsc/src/tip/helpers.js.map +0 -1
  441. package/out-tsc/src/toast/Toast.js.map +0 -1
  442. package/out-tsc/src/user/TembaUser.js.map +0 -1
  443. package/out-tsc/src/utils/index.js.map +0 -1
  444. package/out-tsc/src/vectoricon/VectorIcon.js.map +0 -1
  445. package/out-tsc/src/vectoricon/index.js.map +0 -1
  446. package/src/charcount/helpers.ts +0 -162
  447. package/src/contacts/helpers.ts +0 -103
  448. package/src/leafletmap/helpers.ts +0 -18
  449. package/src/options/helpers.ts +0 -37
  450. package/src/select/helpers.ts +0 -0
  451. package/src/shadowless/Shadowless.ts +0 -32
  452. package/src/textinput/helpers.ts +0 -11
  453. package/src/tip/helpers.ts +0 -7
  454. /package/out-tsc/src/{alert → display}/Alert.js +0 -0
  455. /package/out-tsc/src/{anchor → display}/Anchor.js +0 -0
  456. /package/out-tsc/src/{button → display}/Button.js +0 -0
  457. /package/out-tsc/src/{chat → display}/Chat.js +0 -0
  458. /package/out-tsc/src/{contacts → display}/ContactName.js +0 -0
  459. /package/out-tsc/src/{contacts → display}/ContactUrn.js +0 -0
  460. /package/out-tsc/src/{dropdown → display}/Dropdown.js +0 -0
  461. /package/out-tsc/src/{label → display}/Label.js +0 -0
  462. /package/out-tsc/src/{lightbox → display}/Lightbox.js +0 -0
  463. /package/out-tsc/src/{loading → display}/Loading.js +0 -0
  464. /package/out-tsc/src/{options → display}/Options.js +0 -0
  465. /package/out-tsc/src/{progress → display}/ProgressBar.js +0 -0
  466. /package/out-tsc/src/{date → display}/TembaDate.js +0 -0
  467. /package/out-tsc/src/{user → display}/TembaUser.js +0 -0
  468. /package/out-tsc/src/{thumbnail → display}/Thumbnail.js +0 -0
  469. /package/out-tsc/src/{toast → display}/Toast.js +0 -0
  470. /package/out-tsc/src/{sms → display/sms}/gsmsplitter.js +0 -0
  471. /package/out-tsc/src/{sms → display/sms}/gsmvalidator.js +0 -0
  472. /package/out-tsc/src/{sms → display/sms}/index.js +0 -0
  473. /package/out-tsc/src/{sms → display/sms}/unicodesplitter.js +0 -0
  474. /package/out-tsc/src/{completion → excellent}/ExcellentParser.js +0 -0
  475. /package/out-tsc/src/{completion → excellent}/helpers.js +0 -0
  476. /package/out-tsc/src/{imagepicker → form}/CroppieCSS.js +0 -0
  477. /package/out-tsc/src/{formfield → form}/FormField.js +0 -0
  478. /package/out-tsc/src/{datepicker → form}/RangePicker.js +0 -0
  479. /package/out-tsc/src/{dialog → layout}/Dialog.js +0 -0
  480. /package/out-tsc/src/{mask → layout}/Mask.js +0 -0
  481. /package/out-tsc/src/{dialog → layout}/Modax.js +0 -0
  482. /package/out-tsc/src/{resizer → layout}/Resizer.js +0 -0
  483. /package/out-tsc/src/{tabpane → layout}/Tab.js +0 -0
  484. /package/out-tsc/src/{tabpane → layout}/TabPane.js +0 -0
  485. /package/out-tsc/src/{contacts → live}/ContactFields.js +0 -0
  486. /package/out-tsc/src/{contacts → live}/ContactNameFetch.js +0 -0
  487. /package/out-tsc/src/{contacts → live}/ContactStoreElement.js +0 -0
  488. /package/out-tsc/src/{fields → live}/FieldManager.js +0 -0
  489. /package/out-tsc/src/{progress → live}/StartProgress.js +0 -0
  490. /package/out-tsc/src/{chart → live}/TembaChart.js +0 -0
  491. /package/src/{vectoricon/index.ts → Icons.ts} +0 -0
  492. /package/src/{alert → display}/Alert.ts +0 -0
  493. /package/src/{anchor → display}/Anchor.ts +0 -0
  494. /package/src/{button → display}/Button.ts +0 -0
  495. /package/src/{chat → display}/Chat.ts +0 -0
  496. /package/src/{contacts → display}/ContactName.ts +0 -0
  497. /package/src/{contacts → display}/ContactUrn.ts +0 -0
  498. /package/src/{dropdown → display}/Dropdown.ts +0 -0
  499. /package/src/{label → display}/Label.ts +0 -0
  500. /package/src/{lightbox → display}/Lightbox.ts +0 -0
  501. /package/src/{loading → display}/Loading.ts +0 -0
  502. /package/src/{options → display}/Options.ts +0 -0
  503. /package/src/{progress → display}/ProgressBar.ts +0 -0
  504. /package/src/{date → display}/TembaDate.ts +0 -0
  505. /package/src/{user → display}/TembaUser.ts +0 -0
  506. /package/src/{toast → display}/Toast.ts +0 -0
  507. /package/src/{sms → display/sms}/gsmsplitter.ts +0 -0
  508. /package/src/{sms → display/sms}/gsmvalidator.ts +0 -0
  509. /package/src/{sms → display/sms}/index.ts +0 -0
  510. /package/src/{sms → display/sms}/unicodesplitter.ts +0 -0
  511. /package/src/{completion → excellent}/ExcellentParser.ts +0 -0
  512. /package/src/{completion → excellent}/helpers.ts +0 -0
  513. /package/src/{imagepicker → form}/CroppieCSS.ts +0 -0
  514. /package/src/{formfield → form}/FormField.ts +0 -0
  515. /package/src/{datepicker → form}/RangePicker.ts +0 -0
  516. /package/src/{mask → layout}/Mask.ts +0 -0
  517. /package/src/{dialog → layout}/Modax.ts +0 -0
  518. /package/src/{resizer → layout}/Resizer.ts +0 -0
  519. /package/src/{tabpane → layout}/Tab.ts +0 -0
  520. /package/src/{tabpane → layout}/TabPane.ts +0 -0
  521. /package/src/{contacts → live}/ContactNameFetch.ts +0 -0
  522. /package/src/{contacts → live}/ContactStoreElement.ts +0 -0
  523. /package/src/{fields → live}/FieldManager.ts +0 -0
  524. /package/src/{progress → live}/StartProgress.ts +0 -0
@@ -5,12 +5,29 @@ import { FlowDefinition, FlowPosition } from '../store/flow-definition';
5
5
  import { getStore } from '../store/Store';
6
6
  import { AppState, fromStore, zustand } from '../store/AppState';
7
7
  import { RapidElement } from '../RapidElement';
8
+ import { repeat } from 'lit-html/directives/repeat.js';
8
9
 
9
10
  import { Plumber } from './Plumber';
10
11
  import { EditorNode } from './EditorNode';
12
+ import { Dialog } from '../layout/Dialog';
13
+ import { Connection } from '@jsplumb/browser-ui';
11
14
 
12
15
  export function snapToGrid(value: number): number {
13
- return Math.round(value / 20) * 20;
16
+ const snapped = Math.round(value / 20) * 20;
17
+ return Math.max(snapped, 0);
18
+ }
19
+
20
+ export function findNodeForExit(
21
+ definition: FlowDefinition,
22
+ exitUuid: string
23
+ ): string | null {
24
+ for (const node of definition.nodes) {
25
+ const exit = node.exits.find((e) => e.uuid === exitUuid);
26
+ if (exit) {
27
+ return node.uuid;
28
+ }
29
+ }
30
+ return null;
14
31
  }
15
32
 
16
33
  const SAVE_QUIET_TIME = 500;
@@ -22,7 +39,14 @@ export interface DraggableItem {
22
39
  type: 'node' | 'sticky';
23
40
  }
24
41
 
25
- const DRAG_THRESHOLD = 10;
42
+ export interface SelectionBox {
43
+ startX: number;
44
+ startY: number;
45
+ endX: number;
46
+ endY: number;
47
+ }
48
+
49
+ const DRAG_THRESHOLD = 5;
26
50
 
27
51
  export class Editor extends RapidElement {
28
52
  // unfortunately, jsplumb requires that we be in light DOM
@@ -61,9 +85,36 @@ export class Editor extends RapidElement {
61
85
  private currentDragItem: DraggableItem | null = null;
62
86
  private startPos = { left: 0, top: 0 };
63
87
 
88
+ // Selection state
89
+ @state()
90
+ private selectedItems: Set<string> = new Set();
91
+
92
+ @state()
93
+ private isSelecting = false;
94
+
95
+ @state()
96
+ private selectionBox: SelectionBox | null = null;
97
+
98
+ @state()
99
+ private targetId: string | null = null;
100
+
101
+ @state()
102
+ private sourceId: string | null = null;
103
+
104
+ @state()
105
+ private dragFromNodeId: string | null = null;
106
+
107
+ @state()
108
+ private isValidTarget = true;
109
+
110
+ private canvasMouseDown = false;
111
+
64
112
  // Bound event handlers to maintain proper 'this' context
65
113
  private boundMouseMove = this.handleMouseMove.bind(this);
66
114
  private boundMouseUp = this.handleMouseUp.bind(this);
115
+ private boundGlobalMouseDown = this.handleGlobalMouseDown.bind(this);
116
+ private boundKeyDown = this.handleKeyDown.bind(this);
117
+ private boundCanvasDoubleClick = this.handleCanvasDoubleClick.bind(this);
67
118
 
68
119
  static get styles() {
69
120
  return css`
@@ -75,41 +126,23 @@ export class Editor extends RapidElement {
75
126
  #grid {
76
127
  position: relative;
77
128
  background-color: #f9f9f9;
129
+ background-image: radial-gradient(
130
+ circle,
131
+ rgba(61, 177, 255, 0.3) 1px,
132
+ transparent 1px
133
+ );
134
+ background-size: 20px 20px;
78
135
  background-position: 10px 10px;
79
- background-image: linear-gradient(
80
- 0deg,
81
- transparent 24%,
82
- rgba(61, 177, 255, 0.15) 25%,
83
- rgba(61, 177, 255, 0.15) 26%,
84
- transparent 27%,
85
- transparent 74%,
86
- rgba(61, 177, 255, 0.15) 75%,
87
- rgba(61, 177, 255, 0.15) 76%,
88
- transparent 77%,
89
- transparent
90
- ),
91
- linear-gradient(
92
- 90deg,
93
- transparent 24%,
94
- rgba(61, 177, 255, 0.15) 25%,
95
- rgba(61, 177, 255, 0.15) 26%,
96
- transparent 27%,
97
- transparent 74%,
98
- rgba(61, 177, 255, 0.15) 75%,
99
- rgba(61, 177, 255, 0.15) 76%,
100
- transparent 77%,
101
- transparent
102
- );
103
- background-size: 40px 40px;
104
136
  box-shadow: inset -5px 0 10px rgba(0, 0, 0, 0.05);
105
137
  border-top: 1px solid #e0e0e0;
106
- display: inline-block;
107
138
  width: 100%;
139
+ display: flex;
108
140
  }
109
141
 
110
142
  #canvas {
111
143
  position: relative;
112
- padding: 20px;
144
+ padding: 0px;
145
+ flex-grow: 1;
113
146
  margin: 20px;
114
147
  }
115
148
 
@@ -119,7 +152,7 @@ export class Editor extends RapidElement {
119
152
  }
120
153
 
121
154
  #canvas > .dragging {
122
- z-index: 10000 !important;
155
+ z-index: 99999 !important;
123
156
  }
124
157
 
125
158
  body .jtk-endpoint {
@@ -129,70 +162,44 @@ export class Editor extends RapidElement {
129
162
 
130
163
  .jtk-endpoint {
131
164
  z-index: 600;
165
+ opacity: 0;
132
166
  }
133
167
 
134
168
  .plumb-source {
135
169
  z-index: 600;
136
- border: 0px solid var(--color-connectors);
170
+ cursor: pointer;
171
+ opacity: 0;
137
172
  }
138
173
 
139
174
  .plumb-source.connected {
140
- box-shadow: 0 3px 3px 0px rgba(0, 0, 0, 0.1);
141
175
  border-radius: 50%;
176
+ pointer-events: none;
142
177
  }
143
178
 
144
179
  .plumb-source circle {
145
- fill: tomato;
146
- }
147
-
148
- .plumb-source.connected circle {
149
- fill: #fff;
150
- }
151
-
152
- .plumb-source svg {
153
- fill: var(--color-connectors) !important;
154
- stroke: var(--color-connectors);
180
+ fill: purple;
155
181
  }
156
182
 
157
183
  .plumb-target {
158
- margin-top: -6px;
159
184
  z-index: 600;
160
185
  opacity: 0;
161
186
  cursor: pointer;
187
+ fill: transparent;
162
188
  }
163
189
 
164
- body .plumb-connector path {
190
+ body svg.jtk-connector.plumb-connector path {
165
191
  stroke: var(--color-connectors) !important;
166
192
  stroke-width: 3px;
167
- z-index: 10;
168
193
  }
169
194
 
170
195
  body .plumb-connector {
171
- z-index: 10;
172
- }
173
-
174
- body .plumb-connector.elevated {
175
- z-index: 550;
176
- }
177
-
178
- body .plumb-connector.elevated path {
179
- stroke: var(--color-connectors) !important;
180
- stroke-width: 3px;
181
- z-index: 550;
182
- }
183
-
184
- body .plumb-connector.elevated .plumb-arrow {
185
- fill: var(--color-connectors);
186
- stroke: var(--color-connectors);
187
- stroke-width: 0px;
188
- margin-top: 6px;
189
- z-index: 550;
196
+ z-index: 10 !important;
190
197
  }
191
198
 
192
199
  body .plumb-connector .plumb-arrow {
193
200
  fill: var(--color-connectors);
194
201
  stroke: var(--color-connectors);
195
- stroke-width: 0px;
202
+ stroke-width: 0px !important;
196
203
  margin-top: 6px;
197
204
  z-index: 10;
198
205
  }
@@ -207,6 +214,50 @@ export class Editor extends RapidElement {
207
214
  stroke-width: 0px;
208
215
  z-index: 10;
209
216
  }
217
+
218
+ /* Connection dragging feedback */
219
+ body svg.jtk-connector.jtk-dragging {
220
+ z-index: 99999 !important;
221
+ }
222
+
223
+ .katavorio-drag-no-select svg.jtk-connector path,
224
+ .katavorio-drag-no-select svg.jtk-endpoint path {
225
+ pointer-events: none !important;
226
+ border: 1px solid purple;
227
+ }
228
+
229
+ /* Connection target feedback */
230
+ temba-flow-node.connection-target-valid {
231
+ outline: 3px solid var(--color-success, #22c55e) !important;
232
+ outline-offset: 2px;
233
+ border-radius: var(--curvature);
234
+ }
235
+
236
+ temba-flow-node.connection-target-invalid {
237
+ outline: 3px solid var(--color-error, #ef4444) !important;
238
+ outline-offset: 2px;
239
+ border-radius: var(--curvature);
240
+ }
241
+
242
+ /* Selection box styles */
243
+ .selection-box {
244
+ position: absolute;
245
+ border: 2px dashed #6298f0ff;
246
+ background-color: rgba(59, 130, 246, 0.1);
247
+ z-index: 9999;
248
+ pointer-events: none;
249
+ }
250
+
251
+ /* Selected item styles */
252
+ .draggable.selected {
253
+ outline: 3px solid #6298f0ff;
254
+ outline-offset: 0px;
255
+ border-radius: var(--curvature);
256
+ }
257
+
258
+ .jtk-floating-endpoint {
259
+ pointer-events: none;
260
+ }
210
261
  `;
211
262
  }
212
263
 
@@ -223,6 +274,51 @@ export class Editor extends RapidElement {
223
274
  if (changes.has('flow')) {
224
275
  getStore().getState().fetchRevision(`/flow/revisions/${this.flow}`);
225
276
  }
277
+
278
+ this.plumber.on('connection:drag', (info: Connection) => {
279
+ this.dragFromNodeId = document
280
+ .getElementById(info.sourceId)
281
+ .closest('.node').id;
282
+ this.sourceId = info.sourceId;
283
+ });
284
+
285
+ this.plumber.on('connection:abort', () => {
286
+ this.makeConnection();
287
+ });
288
+
289
+ this.plumber.on('connection:detach', () => {
290
+ this.makeConnection();
291
+ });
292
+ }
293
+
294
+ private makeConnection() {
295
+ if (this.sourceId && this.targetId && this.isValidTarget) {
296
+ this.plumber.connectIds(
297
+ this.dragFromNodeId,
298
+ this.sourceId,
299
+ this.targetId
300
+ );
301
+ getStore()
302
+ .getState()
303
+ .updateConnection(this.dragFromNodeId, this.sourceId, this.targetId);
304
+
305
+ setTimeout(() => {
306
+ this.plumber.repaintEverything();
307
+ }, 100);
308
+ }
309
+
310
+ // Clean up visual feedback
311
+ document.querySelectorAll('temba-flow-node').forEach((node) => {
312
+ node.classList.remove(
313
+ 'connection-target-valid',
314
+ 'connection-target-invalid'
315
+ );
316
+ });
317
+
318
+ this.sourceId = null;
319
+ this.targetId = null;
320
+ this.dragFromNodeId = null;
321
+ this.isValidTarget = true;
226
322
  }
227
323
 
228
324
  protected updated(
@@ -277,11 +373,25 @@ export class Editor extends RapidElement {
277
373
  }
278
374
  document.removeEventListener('mousemove', this.boundMouseMove);
279
375
  document.removeEventListener('mouseup', this.boundMouseUp);
376
+ document.removeEventListener('mousedown', this.boundGlobalMouseDown);
377
+ document.removeEventListener('keydown', this.boundKeyDown);
378
+
379
+ const canvas = this.querySelector('#canvas');
380
+ if (canvas) {
381
+ canvas.removeEventListener('dblclick', this.boundCanvasDoubleClick);
382
+ }
280
383
  }
281
384
 
282
385
  private setupGlobalEventListeners(): void {
283
386
  document.addEventListener('mousemove', this.boundMouseMove);
284
387
  document.addEventListener('mouseup', this.boundMouseUp);
388
+ document.addEventListener('mousedown', this.boundGlobalMouseDown);
389
+ document.addEventListener('keydown', this.boundKeyDown);
390
+
391
+ const canvas = this.querySelector('#canvas');
392
+ if (canvas) {
393
+ canvas.addEventListener('dblclick', this.boundCanvasDoubleClick);
394
+ }
285
395
  }
286
396
 
287
397
  private getPosition(uuid: string, type: 'node' | 'sticky'): FlowPosition {
@@ -292,27 +402,10 @@ export class Editor extends RapidElement {
292
402
  }
293
403
  }
294
404
 
295
- private updatePosition(
296
- uuid: string,
297
- type: 'node' | 'sticky',
298
- position: FlowPosition
299
- ): void {
300
- if (type === 'node') {
301
- getStore().getState().updateNodePosition(uuid, position);
302
- } else {
303
- const currentSticky = this.definition._ui.stickies?.[uuid];
304
- if (currentSticky) {
305
- getStore()
306
- .getState()
307
- .updateStickyNote(uuid, {
308
- ...currentSticky,
309
- position
310
- });
311
- }
312
- }
313
- }
314
-
315
405
  private handleMouseDown(event: MouseEvent): void {
406
+ // ignore right clicks
407
+ if (event.button !== 0) return;
408
+
316
409
  const element = event.currentTarget as HTMLElement;
317
410
  // Only start dragging if clicking on the element itself, not on exits or other interactive elements
318
411
  const target = event.target as HTMLElement;
@@ -326,7 +419,17 @@ export class Editor extends RapidElement {
326
419
  const position = this.getPosition(uuid, type);
327
420
  if (!position) return;
328
421
 
329
- // Set up potential drag state, but don't start dragging yet
422
+ // If clicking on a non-selected item, clear selection unless Ctrl/Cmd is held
423
+ if (!this.selectedItems.has(uuid) && !event.ctrlKey && !event.metaKey) {
424
+ this.selectedItems.clear();
425
+ // Don't add single items to selection - single clicks just clear existing selection
426
+ } else if (!this.selectedItems.has(uuid)) {
427
+ // Add this item to selection only if Ctrl/Cmd is held
428
+ this.selectedItems.add(uuid);
429
+ }
430
+
431
+ // Always set up drag state regardless of selection status
432
+ // This allows single nodes to be dragged without being selected
330
433
  this.isMouseDown = true;
331
434
  this.dragStartPos = { x: event.clientX, y: event.clientY };
332
435
  this.startPos = { left: position.left, top: position.top };
@@ -341,7 +444,284 @@ export class Editor extends RapidElement {
341
444
  event.stopPropagation();
342
445
  }
343
446
 
447
+ private handleGlobalMouseDown(event: MouseEvent): void {
448
+ // ignore right clicks
449
+ if (event.button !== 0) return;
450
+
451
+ // Check if the click is within our canvas
452
+ const canvasRect = this.querySelector('#grid')?.getBoundingClientRect();
453
+
454
+ if (!canvasRect) return;
455
+
456
+ const isWithinCanvas =
457
+ event.clientX >= canvasRect.left &&
458
+ event.clientX <= canvasRect.right &&
459
+ event.clientY >= canvasRect.top &&
460
+ event.clientY <= canvasRect.bottom;
461
+
462
+ if (!isWithinCanvas) return;
463
+
464
+ // Check if we clicked on a draggable item (node or sticky)
465
+ const target = event.target as HTMLElement;
466
+ const clickedOnDraggable = target.closest('.draggable');
467
+
468
+ if (clickedOnDraggable) {
469
+ // This is handled by the individual item mousedown handlers
470
+ return;
471
+ }
472
+
473
+ // We clicked on empty canvas space, start selection
474
+ this.handleCanvasMouseDown(event);
475
+ }
476
+
477
+ private handleCanvasMouseDown(event: MouseEvent): void {
478
+ const target = event.target as HTMLElement;
479
+ if (target.id === 'canvas' || target.id === 'grid') {
480
+ // Ignore clicks on exits
481
+
482
+ // Start selection box
483
+ this.canvasMouseDown = true;
484
+ this.dragStartPos = { x: event.clientX, y: event.clientY };
485
+
486
+ const canvasRect = this.querySelector('#canvas')?.getBoundingClientRect();
487
+ if (canvasRect) {
488
+ // Clear current selection
489
+ this.selectedItems.clear();
490
+
491
+ const relativeX = event.clientX - canvasRect.left;
492
+ const relativeY = event.clientY - canvasRect.top;
493
+
494
+ this.selectionBox = {
495
+ startX: relativeX,
496
+ startY: relativeY,
497
+ endX: relativeX,
498
+ endY: relativeY
499
+ };
500
+ }
501
+
502
+ event.preventDefault();
503
+ }
504
+ }
505
+
506
+ private handleKeyDown(event: KeyboardEvent): void {
507
+ if (event.key === 'Delete' || event.key === 'Backspace') {
508
+ if (this.selectedItems.size > 0) {
509
+ this.showDeleteConfirmation();
510
+ }
511
+ }
512
+ if (event.key === 'Escape') {
513
+ this.selectedItems.clear();
514
+ this.requestUpdate();
515
+ }
516
+ }
517
+
518
+ private showDeleteConfirmation(): void {
519
+ const itemCount = this.selectedItems.size;
520
+ const itemType = itemCount === 1 ? 'item' : 'items';
521
+
522
+ // Create and show confirmation dialog
523
+ const dialog = document.createElement('temba-dialog') as Dialog;
524
+ dialog.header = 'Delete Items';
525
+ dialog.primaryButtonName = 'Delete';
526
+ dialog.cancelButtonName = 'Cancel';
527
+ dialog.destructive = true;
528
+ dialog.innerHTML = `<div style="padding: 20px;">Are you sure you want to delete ${itemCount} ${itemType}?</div>`;
529
+
530
+ dialog.addEventListener('temba-button-clicked', (event: any) => {
531
+ if (event.detail.button.name === 'Delete') {
532
+ this.deleteSelectedItems();
533
+ dialog.open = false;
534
+ }
535
+ });
536
+
537
+ // Add to document and show
538
+ document.body.appendChild(dialog);
539
+ dialog.open = true;
540
+
541
+ // Clean up dialog when closed
542
+ dialog.addEventListener('temba-dialog-hidden', () => {
543
+ document.body.removeChild(dialog);
544
+ });
545
+ }
546
+
547
+ private deleteNodes(uuids: string[]): void {
548
+ // Clean up jsPlumb connections for nodes before removing them
549
+ uuids.forEach((uuid) => {
550
+ this.plumber.removeNodeConnections(uuid);
551
+ });
552
+
553
+ // Now remove them from the definition
554
+ if (uuids.length > 0 && this.plumber) {
555
+ getStore().getState().removeNodes(uuids);
556
+ }
557
+ }
558
+
559
+ private deleteSelectedItems(): void {
560
+ const nodes = Array.from(this.selectedItems).filter((uuid) =>
561
+ this.definition.nodes.some((node) => node.uuid === uuid)
562
+ );
563
+ this.deleteNodes(Array.from(nodes));
564
+
565
+ const stickies = Array.from(this.selectedItems).filter(
566
+ (uuid) => this.definition._ui?.stickies?.[uuid]
567
+ );
568
+
569
+ getStore().getState().removeStickyNotes(stickies);
570
+
571
+ // Clear selection
572
+ this.selectedItems.clear();
573
+ }
574
+
575
+ private updateSelectionBox(event: MouseEvent): void {
576
+ if (!this.selectionBox || !this.canvasMouseDown) return;
577
+
578
+ const canvasRect = this.querySelector('#canvas')?.getBoundingClientRect();
579
+ if (!canvasRect) return;
580
+
581
+ const relativeX = event.clientX - canvasRect.left;
582
+ const relativeY = event.clientY - canvasRect.top;
583
+
584
+ this.selectionBox = {
585
+ ...this.selectionBox,
586
+ endX: relativeX,
587
+ endY: relativeY
588
+ };
589
+
590
+ // Update selected items based on selection box
591
+ this.updateSelectedItemsFromBox();
592
+ }
593
+
594
+ private updateSelectedItemsFromBox(): void {
595
+ if (!this.selectionBox) return;
596
+
597
+ const newSelection = new Set<string>();
598
+
599
+ const boxLeft = Math.min(this.selectionBox.startX, this.selectionBox.endX);
600
+ const boxTop = Math.min(this.selectionBox.startY, this.selectionBox.endY);
601
+ const boxRight = Math.max(this.selectionBox.startX, this.selectionBox.endX);
602
+ const boxBottom = Math.max(
603
+ this.selectionBox.startY,
604
+ this.selectionBox.endY
605
+ );
606
+
607
+ // Check nodes
608
+ this.definition?.nodes.forEach((node) => {
609
+ const nodeElement = this.querySelector(`[id="${node.uuid}"]`);
610
+ if (nodeElement) {
611
+ const position = this.definition._ui.nodes[node.uuid]?.position;
612
+ if (position) {
613
+ const rect = nodeElement.getBoundingClientRect();
614
+ const canvasRect =
615
+ this.querySelector('#canvas')?.getBoundingClientRect();
616
+
617
+ if (canvasRect) {
618
+ const nodeLeft = position.left;
619
+ const nodeTop = position.top;
620
+ const nodeRight = nodeLeft + rect.width;
621
+ const nodeBottom = nodeTop + rect.height;
622
+
623
+ // Check if selection box intersects with node
624
+ if (
625
+ boxLeft < nodeRight &&
626
+ boxRight > nodeLeft &&
627
+ boxTop < nodeBottom &&
628
+ boxBottom > nodeTop
629
+ ) {
630
+ newSelection.add(node.uuid);
631
+ }
632
+ }
633
+ }
634
+ }
635
+ });
636
+
637
+ // Check sticky notes
638
+ const stickies = this.definition?._ui?.stickies || {};
639
+ Object.entries(stickies).forEach(([uuid, sticky]) => {
640
+ if (sticky.position) {
641
+ const stickyElement = this.querySelector(
642
+ `temba-sticky-note[uuid="${uuid}"]`
643
+ ) as HTMLElement;
644
+
645
+ if (stickyElement) {
646
+ // Use clientWidth/clientHeight instead of getBoundingClientRect() to get element dimensions
647
+ // This avoids the coordinate system mismatch between viewport and canvas coordinates
648
+ const width = stickyElement.clientWidth;
649
+ const height = stickyElement.clientHeight;
650
+
651
+ // Use the canvas coordinates from the sticky's position
652
+ const stickyLeft = sticky.position.left;
653
+ const stickyTop = sticky.position.top;
654
+ const stickyRight = stickyLeft + width;
655
+ const stickyBottom = stickyTop + height;
656
+
657
+ // Check if selection box intersects with sticky
658
+ if (
659
+ boxLeft < stickyRight &&
660
+ boxRight > stickyLeft &&
661
+ boxTop < stickyBottom &&
662
+ boxBottom > stickyTop
663
+ ) {
664
+ newSelection.add(uuid);
665
+ }
666
+ }
667
+ }
668
+ });
669
+
670
+ this.selectedItems = newSelection;
671
+ }
672
+
673
+ private renderSelectionBox(): TemplateResult | string {
674
+ if (!this.selectionBox || !this.isSelecting) return '';
675
+
676
+ const left = Math.min(this.selectionBox.startX, this.selectionBox.endX);
677
+ const top = Math.min(this.selectionBox.startY, this.selectionBox.endY);
678
+ const width = Math.abs(this.selectionBox.endX - this.selectionBox.startX);
679
+ const height = Math.abs(this.selectionBox.endY - this.selectionBox.startY);
680
+
681
+ return html`<div
682
+ class="selection-box"
683
+ style="left: ${left}px; top: ${top}px; width: ${width}px; height: ${height}px;"
684
+ ></div>`;
685
+ }
686
+
344
687
  private handleMouseMove(event: MouseEvent): void {
688
+ // Handle selection box drawing
689
+ if (this.canvasMouseDown && !this.isMouseDown) {
690
+ this.isSelecting = true;
691
+ this.updateSelectionBox(event);
692
+ this.requestUpdate(); // Force re-render
693
+ return;
694
+ }
695
+
696
+ if (this.plumber.connectionDragging) {
697
+ const targetNode = document.querySelector('temba-flow-node:hover');
698
+
699
+ // Clear previous target styles
700
+ document.querySelectorAll('temba-flow-node').forEach((node) => {
701
+ node.classList.remove(
702
+ 'connection-target-valid',
703
+ 'connection-target-invalid'
704
+ );
705
+ });
706
+
707
+ if (targetNode) {
708
+ this.targetId = targetNode.getAttribute('uuid');
709
+ // Check if target is different from source node (prevent self-targeting)
710
+ this.isValidTarget = this.targetId !== this.dragFromNodeId;
711
+
712
+ // Apply visual feedback based on validity
713
+ if (this.isValidTarget) {
714
+ targetNode.classList.add('connection-target-valid');
715
+ } else {
716
+ targetNode.classList.add('connection-target-invalid');
717
+ }
718
+ } else {
719
+ this.targetId = null;
720
+ this.isValidTarget = true;
721
+ }
722
+ }
723
+
724
+ // Handle item dragging
345
725
  if (!this.isMouseDown || !this.currentDragItem) return;
346
726
 
347
727
  const deltaX = event.clientX - this.dragStartPos.x;
@@ -351,77 +731,119 @@ export class Editor extends RapidElement {
351
731
  // Only start dragging if we've moved beyond the threshold
352
732
  if (!this.isDragging && distance > DRAG_THRESHOLD) {
353
733
  this.isDragging = true;
354
-
355
- // If this is a node, elevate connections
356
- if (this.currentDragItem.type === 'node' && this.plumber) {
357
- this.plumber.elevateNodeConnections(this.currentDragItem.uuid);
358
- }
359
734
  }
360
735
 
361
736
  // If we're actually dragging, update positions
362
737
  if (this.isDragging) {
363
- const newLeft = this.startPos.left + deltaX;
364
- const newTop = this.startPos.top + deltaY;
365
-
366
- // Update the visual position during drag
367
- this.currentDragItem.element.style.left = `${newLeft}px`;
368
- this.currentDragItem.element.style.top = `${newTop}px`;
738
+ // Determine what items to move
739
+ const itemsToMove =
740
+ this.selectedItems.has(this.currentDragItem.uuid) &&
741
+ this.selectedItems.size > 1
742
+ ? Array.from(this.selectedItems)
743
+ : [this.currentDragItem.uuid];
744
+
745
+ itemsToMove.forEach((uuid) => {
746
+ const element = this.querySelector(`[uuid="${uuid}"]`) as HTMLElement;
747
+ if (element) {
748
+ const type =
749
+ element.tagName === 'TEMBA-FLOW-NODE' ? 'node' : 'sticky';
750
+ const position = this.getPosition(uuid, type);
751
+
752
+ if (position) {
753
+ const newLeft = position.left + deltaX;
754
+ const newTop = position.top + deltaY;
755
+
756
+ // Update the visual position during drag
757
+ element.style.left = `${newLeft}px`;
758
+ element.style.top = `${newTop}px`;
759
+
760
+ // Add dragging class to ensure highest z-index
761
+ element.classList.add('dragging');
762
+ }
763
+ }
764
+ });
369
765
 
370
- // Repaint connections if this is a node
371
- if (this.currentDragItem.type === 'node' && this.plumber) {
372
- this.plumber.repaintEverything();
373
- }
766
+ this.plumber.revalidate(itemsToMove);
374
767
  }
375
768
  }
376
769
 
377
770
  private handleMouseUp(event: MouseEvent): void {
771
+ // Handle selection box completion
772
+ if (this.canvasMouseDown && this.isSelecting) {
773
+ this.isSelecting = false;
774
+ this.selectionBox = null;
775
+ this.canvasMouseDown = false;
776
+ this.requestUpdate();
777
+ return;
778
+ }
779
+
780
+ // Handle canvas click (clear selection)
781
+ if (this.canvasMouseDown && !this.isSelecting) {
782
+ this.canvasMouseDown = false;
783
+ return;
784
+ }
785
+
786
+ // Handle item drag completion
378
787
  if (!this.isMouseDown || !this.currentDragItem) return;
379
788
 
380
789
  // If we were actually dragging, handle the drag end
381
790
  if (this.isDragging) {
382
- // Restore normal z-index for node connections
383
- if (this.currentDragItem.type === 'node' && this.plumber) {
384
- this.plumber.restoreNodeConnections(this.currentDragItem.uuid);
385
- }
386
-
387
791
  const deltaX = event.clientX - this.dragStartPos.x;
388
792
  const deltaY = event.clientY - this.dragStartPos.y;
389
793
 
390
- const newLeft = this.startPos.left + deltaX;
391
- const newTop = this.startPos.top + deltaY;
392
-
393
- // Snap to 20px grid for final position
394
- const snappedLeft = snapToGrid(newLeft);
395
- const snappedTop = snapToGrid(newTop);
396
-
397
- const newPosition = { left: snappedLeft, top: snappedTop };
794
+ // Determine what items were moved
795
+ const itemsToMove =
796
+ this.selectedItems.has(this.currentDragItem.uuid) &&
797
+ this.selectedItems.size > 1
798
+ ? Array.from(this.selectedItems)
799
+ : [this.currentDragItem.uuid];
800
+
801
+ // Update positions for all moved items
802
+ const newPositions: { [uuid: string]: FlowPosition } = {};
803
+
804
+ itemsToMove.forEach((uuid) => {
805
+ const type = this.definition.nodes.find((node) => node.uuid === uuid)
806
+ ? 'node'
807
+ : 'sticky';
808
+ const position = this.getPosition(uuid, type);
809
+
810
+ if (position) {
811
+ const newLeft = position.left + deltaX;
812
+ const newTop = position.top + deltaY;
813
+
814
+ // Snap to 20px grid for final position
815
+ const snappedLeft = snapToGrid(newLeft);
816
+ const snappedTop = snapToGrid(newTop);
817
+
818
+ const newPosition = { left: snappedLeft, top: snappedTop };
819
+ newPositions[uuid] = newPosition;
820
+
821
+ // Remove dragging class
822
+ const element = this.querySelector(`[uuid="${uuid}"]`) as HTMLElement;
823
+ if (element) {
824
+ element.classList.remove('dragging');
825
+ element.style.left = `${snappedLeft}px`;
826
+ element.style.top = `${snappedTop}px`;
827
+ }
828
+ }
829
+ });
398
830
 
399
- // Update the store with the new snapped position
400
- this.updatePosition(
401
- this.currentDragItem.uuid,
402
- this.currentDragItem.type,
403
- newPosition
404
- );
831
+ if (Object.keys(newPositions).length > 0) {
832
+ getStore().getState().updateCanvasPositions(newPositions);
405
833
 
406
- // Update canvas positions for nodes
407
- if (this.currentDragItem.type === 'node') {
408
- getStore()
409
- .getState()
410
- .updateCanvasPositions({
411
- [this.currentDragItem.uuid]: newPosition
412
- });
834
+ setTimeout(() => {
835
+ this.plumber.repaintEverything();
836
+ }, 0);
413
837
  }
414
838
 
415
- // Repaint connections if this is a node
416
- if (this.currentDragItem.type === 'node' && this.plumber) {
417
- this.plumber.repaintEverything();
418
- }
839
+ this.selectedItems.clear();
419
840
  }
420
841
 
421
842
  // Reset all drag state
422
843
  this.isDragging = false;
423
844
  this.isMouseDown = false;
424
845
  this.currentDragItem = null;
846
+ this.canvasMouseDown = false;
425
847
  }
426
848
 
427
849
  private updateCanvasSize(): void {
@@ -449,10 +871,25 @@ export class Editor extends RapidElement {
449
871
 
450
872
  // Check sticky note positions
451
873
  const stickies = this.definition._ui?.stickies || {};
452
- Object.values(stickies).forEach((sticky) => {
874
+ Object.entries(stickies).forEach(([uuid, sticky]) => {
453
875
  if (sticky.position) {
454
- maxWidth = Math.max(maxWidth, sticky.position.left + 200); // Sticky note width
455
- maxHeight = Math.max(maxHeight, sticky.position.top + 100); // Sticky note height
876
+ const stickyElement = this.querySelector(
877
+ `temba-sticky-note[uuid="${uuid}"]`
878
+ ) as HTMLElement;
879
+ if (stickyElement) {
880
+ // Use clientWidth/clientHeight instead of getBoundingClientRect() to get element dimensions
881
+ // This avoids the coordinate system mismatch between viewport and canvas coordinates
882
+ const width = stickyElement.clientWidth;
883
+ const height = stickyElement.clientHeight;
884
+
885
+ // Both sticky.position and width/height are now in the same coordinate system
886
+ maxWidth = Math.max(maxWidth, sticky.position.left + width);
887
+ maxHeight = Math.max(maxHeight, sticky.position.top + height);
888
+ } else {
889
+ // Fallback to default sizes if element not found
890
+ maxWidth = Math.max(maxWidth, sticky.position.left + 200);
891
+ maxHeight = Math.max(maxHeight, sticky.position.top + 100);
892
+ }
456
893
  }
457
894
  });
458
895
 
@@ -460,6 +897,38 @@ export class Editor extends RapidElement {
460
897
  store.getState().expandCanvas(maxWidth, maxHeight);
461
898
  }
462
899
 
900
+ private handleCanvasDoubleClick(event: MouseEvent): void {
901
+ // Check if we double-clicked on empty canvas space
902
+ const target = event.target as HTMLElement;
903
+ if (target.id !== 'canvas') {
904
+ return;
905
+ }
906
+
907
+ // Get canvas position
908
+ const canvas = this.querySelector('#canvas');
909
+ if (!canvas) {
910
+ return;
911
+ }
912
+
913
+ const canvasRect = canvas.getBoundingClientRect();
914
+ const relativeX = event.clientX - canvasRect.left - 10;
915
+ const relativeY = event.clientY - canvasRect.top - 10;
916
+
917
+ // Snap position to grid
918
+ const snappedLeft = snapToGrid(relativeX);
919
+ const snappedTop = snapToGrid(relativeY);
920
+
921
+ // Create new sticky note
922
+ const store = getStore();
923
+ store.getState().createStickyNote({
924
+ left: snappedLeft,
925
+ top: snappedTop
926
+ });
927
+
928
+ event.preventDefault();
929
+ event.stopPropagation();
930
+ }
931
+
463
932
  public render(): TemplateResult {
464
933
  // we have to embed our own style since we are in light DOM
465
934
  const style = html`<style>
@@ -478,38 +947,59 @@ export class Editor extends RapidElement {
478
947
  >
479
948
  <div id="canvas">
480
949
  ${this.definition
481
- ? this.definition.nodes.map((node) => {
482
- const position =
483
- this.definition._ui.nodes[node.uuid].position;
484
-
485
- const dragging =
486
- this.isDragging && this.currentDragItem?.uuid === node.uuid;
487
-
488
- return html`<temba-flow-node
489
- class="draggable ${dragging ? 'dragging' : ''}"
490
- @mousedown=${this.handleMouseDown.bind(this)}
491
- uuid=${node.uuid}
492
- style="left:${position.left}px; top:${position.top}px"
493
- .plumber=${this.plumber}
494
- .node=${node}
495
- .ui=${this.definition._ui.nodes[node.uuid]}
496
- ></temba-flow-node>`;
497
- })
950
+ ? repeat(
951
+ this.definition.nodes,
952
+ (node) => node.uuid,
953
+ (node) => {
954
+ const position =
955
+ this.definition._ui.nodes[node.uuid].position;
956
+
957
+ const dragging =
958
+ this.isDragging &&
959
+ this.currentDragItem?.uuid === node.uuid;
960
+
961
+ const selected = this.selectedItems.has(node.uuid);
962
+
963
+ return html`<temba-flow-node
964
+ class="draggable ${dragging ? 'dragging' : ''} ${selected
965
+ ? 'selected'
966
+ : ''}"
967
+ @mousedown=${this.handleMouseDown.bind(this)}
968
+ uuid=${node.uuid}
969
+ style="left:${position.left}px; top:${position.top}px"
970
+ .plumber=${this.plumber}
971
+ .node=${node}
972
+ .ui=${this.definition._ui.nodes[node.uuid]}
973
+ @temba-node-deleted=${(event) => {
974
+ this.deleteNodes([event.detail.uuid]);
975
+ }}
976
+ ></temba-flow-node>`;
977
+ }
978
+ )
498
979
  : html`<temba-loading></temba-loading>`}
499
- ${Object.entries(stickies).map(([uuid, sticky]) => {
500
- const position = sticky.position || { left: 0, top: 0 };
501
- const dragging =
502
- this.isDragging && this.currentDragItem?.uuid === uuid;
503
- return html`<temba-sticky-note
504
- class="draggable ${dragging ? 'dragging' : ''}"
505
- @mousedown=${this.handleMouseDown.bind(this)}
506
- style="left:${position.left}px; top:${position.top}px; z-index: ${1000 +
507
- position.top}"
508
- uuid=${uuid}
509
- .data=${sticky}
510
- .dragging=${dragging}
511
- ></temba-sticky-note>`;
512
- })}
980
+ ${repeat(
981
+ Object.entries(stickies),
982
+ ([uuid]) => uuid,
983
+ ([uuid, sticky]) => {
984
+ const position = sticky.position || { left: 0, top: 0 };
985
+ const dragging =
986
+ this.isDragging && this.currentDragItem?.uuid === uuid;
987
+ const selected = this.selectedItems.has(uuid);
988
+ return html`<temba-sticky-note
989
+ class="draggable ${dragging ? 'dragging' : ''} ${selected
990
+ ? 'selected'
991
+ : ''}"
992
+ @mousedown=${this.handleMouseDown.bind(this)}
993
+ style="left:${position.left}px; top:${position.top}px; z-index: ${1000 +
994
+ position.top}"
995
+ uuid=${uuid}
996
+ .data=${sticky}
997
+ .dragging=${dragging}
998
+ .selected=${selected}
999
+ ></temba-sticky-note>`;
1000
+ }
1001
+ )}
1002
+ ${this.renderSelectionBox()}
513
1003
  </div>
514
1004
  </div>
515
1005
  </div>`;