@nyaruka/temba-components 0.129.1 → 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 (583) hide show
  1. package/.github/workflows/build.yml +6 -5
  2. package/.github/workflows/coverage.yml +80 -0
  3. package/CHANGELOG.md +32 -0
  4. package/README.md +6 -0
  5. package/check-coverage.js +133 -0
  6. package/demo/components/alert/example.html +71 -0
  7. package/demo/components/button/example.html +167 -0
  8. package/demo/{chart → components/chart}/example.html +3 -3
  9. package/demo/components/chart/horizontal-demo.html +160 -0
  10. package/demo/components/checkbox/example.html +68 -0
  11. package/demo/components/compose/example.html +69 -0
  12. package/demo/components/datepicker/example.html +3 -3
  13. package/demo/components/datepicker/range-picker-demo.html +2 -2
  14. package/demo/{dialog → components/dialog}/example.html +3 -3
  15. package/demo/components/dropdown/example.html +95 -0
  16. package/demo/{flow → components/flow}/example.html +1 -1
  17. package/demo/{misc → components/misc}/example.html +3 -3
  18. package/demo/components/progress/example.html +62 -0
  19. package/demo/components/select/drag-and-drop.html +162 -0
  20. package/demo/components/select/example.html +76 -0
  21. package/demo/components/select/multi.html +72 -0
  22. package/demo/components/slider/example.html +55 -0
  23. package/demo/{sortable-list → components/sortable-list}/example.html +3 -3
  24. package/demo/components/tabs/example.html +91 -0
  25. package/demo/{textinput → components/textinput}/completion.html +3 -3
  26. package/demo/components/textinput/example.html +141 -0
  27. package/demo/{webchat → components/webchat}/example.html +3 -3
  28. package/demo/data/flows/sample-flow.json +107 -100
  29. package/demo/index.html +21 -21
  30. package/demo/static/css/styles.css +253 -0
  31. package/demo/sticky-note-demo.html +88 -85
  32. package/demo/styles.css +24 -0
  33. package/dist/locales/es.js +5 -5
  34. package/dist/locales/es.js.map +1 -1
  35. package/dist/locales/fr.js +5 -5
  36. package/dist/locales/fr.js.map +1 -1
  37. package/dist/locales/locale-codes.js +2 -11
  38. package/dist/locales/locale-codes.js.map +1 -1
  39. package/dist/locales/pt.js +5 -5
  40. package/dist/locales/pt.js.map +1 -1
  41. package/dist/temba-components.js +893 -476
  42. package/dist/temba-components.js.map +1 -1
  43. package/generate-coverage-badge.sh +69 -0
  44. package/out-tsc/src/{vectoricon/index.js → Icons.js} +1 -1
  45. package/out-tsc/src/Icons.js.map +1 -0
  46. package/out-tsc/src/display/Alert.js.map +1 -0
  47. package/out-tsc/src/display/Anchor.js.map +1 -0
  48. package/out-tsc/src/display/Button.js.map +1 -0
  49. package/out-tsc/src/{charcount → display}/CharCount.js +159 -2
  50. package/out-tsc/src/display/CharCount.js.map +1 -0
  51. package/out-tsc/src/display/Chat.js.map +1 -0
  52. package/out-tsc/src/display/ContactName.js.map +1 -0
  53. package/out-tsc/src/display/ContactUrn.js.map +1 -0
  54. package/out-tsc/src/display/Dropdown.js.map +1 -0
  55. package/out-tsc/src/{vectoricon/VectorIcon.js → display/Icon.js} +2 -2
  56. package/out-tsc/src/display/Icon.js.map +1 -0
  57. package/out-tsc/src/display/Label.js.map +1 -0
  58. package/out-tsc/src/{leafletmap → display}/LeafletMap.js +16 -1
  59. package/out-tsc/src/display/LeafletMap.js.map +1 -0
  60. package/out-tsc/src/display/Lightbox.js.map +1 -0
  61. package/out-tsc/src/{loading → display}/Loading.js.map +1 -1
  62. package/out-tsc/src/{options → display}/Options.js.map +1 -1
  63. package/out-tsc/src/display/ProgressBar.js.map +1 -0
  64. package/out-tsc/src/display/TembaDate.js.map +1 -0
  65. package/out-tsc/src/display/TembaUser.js.map +1 -0
  66. package/out-tsc/src/display/Thumbnail.js.map +1 -0
  67. package/out-tsc/src/{tip → display}/Tip.js +1 -2
  68. package/out-tsc/src/display/Tip.js.map +1 -0
  69. package/out-tsc/src/display/Toast.js.map +1 -0
  70. package/out-tsc/src/display/sms/gsmsplitter.js.map +1 -0
  71. package/out-tsc/src/display/sms/gsmvalidator.js.map +1 -0
  72. package/out-tsc/src/display/sms/index.js.map +1 -0
  73. package/out-tsc/src/display/sms/unicodesplitter.js.map +1 -0
  74. package/out-tsc/src/events.js +2 -0
  75. package/out-tsc/src/events.js.map +1 -0
  76. package/out-tsc/src/excellent/ExcellentParser.js.map +1 -0
  77. package/out-tsc/src/excellent/helpers.js.map +1 -0
  78. package/out-tsc/src/flow/Editor.js +533 -140
  79. package/out-tsc/src/flow/Editor.js.map +1 -1
  80. package/out-tsc/src/flow/EditorNode.js +287 -20
  81. package/out-tsc/src/flow/EditorNode.js.map +1 -1
  82. package/out-tsc/src/flow/Plumber.js +154 -74
  83. package/out-tsc/src/flow/Plumber.js.map +1 -1
  84. package/out-tsc/src/flow/StickyNote.js +153 -9
  85. package/out-tsc/src/flow/StickyNote.js.map +1 -1
  86. package/out-tsc/src/flow/config.js +88 -18
  87. package/out-tsc/src/flow/config.js.map +1 -1
  88. package/out-tsc/src/flow/render.js +327 -10
  89. package/out-tsc/src/flow/render.js.map +1 -1
  90. package/out-tsc/src/{checkbox → form}/Checkbox.js +2 -2
  91. package/out-tsc/src/form/Checkbox.js.map +1 -0
  92. package/out-tsc/src/{colorpicker → form}/ColorPicker.js +1 -1
  93. package/out-tsc/src/form/ColorPicker.js.map +1 -0
  94. package/out-tsc/src/{completion → form}/Completion.js +2 -2
  95. package/out-tsc/src/form/Completion.js.map +1 -0
  96. package/out-tsc/src/{compose → form}/Compose.js +1 -1
  97. package/out-tsc/src/form/Compose.js.map +1 -0
  98. package/out-tsc/src/{contactsearch → form}/ContactSearch.js +2 -2
  99. package/out-tsc/src/form/ContactSearch.js.map +1 -0
  100. package/out-tsc/src/form/CroppieCSS.js.map +1 -0
  101. package/out-tsc/src/{datepicker → form}/DatePicker.js +1 -1
  102. package/out-tsc/src/form/DatePicker.js.map +1 -0
  103. package/out-tsc/src/{FormElement.js → form/FormElement.js} +1 -1
  104. package/out-tsc/src/form/FormElement.js.map +1 -0
  105. package/out-tsc/src/form/FormField.js.map +1 -0
  106. package/out-tsc/src/{imagepicker → form}/ImagePicker.js +2 -2
  107. package/out-tsc/src/form/ImagePicker.js.map +1 -0
  108. package/out-tsc/src/{mediapicker → form}/MediaPicker.js +1 -1
  109. package/out-tsc/src/form/MediaPicker.js.map +1 -0
  110. package/out-tsc/src/{datepicker → form}/RangePicker.js +3 -2
  111. package/out-tsc/src/form/RangePicker.js.map +1 -0
  112. package/out-tsc/src/{slider → form}/TembaSlider.js +1 -1
  113. package/out-tsc/src/form/TembaSlider.js.map +1 -0
  114. package/out-tsc/src/{templates → form}/TemplateEditor.js +1 -1
  115. package/out-tsc/src/form/TemplateEditor.js.map +1 -0
  116. package/out-tsc/src/{textinput → form}/TextInput.js +3 -3
  117. package/out-tsc/src/form/TextInput.js.map +1 -0
  118. package/out-tsc/src/{omnibox → form/select}/Omnibox.js +2 -2
  119. package/out-tsc/src/form/select/Omnibox.js.map +1 -0
  120. package/out-tsc/src/{select → form/select}/PopupSelect.js +1 -1
  121. package/out-tsc/src/form/select/PopupSelect.js.map +1 -0
  122. package/out-tsc/src/{select → form/select}/Select.js +86 -87
  123. package/out-tsc/src/form/select/Select.js.map +1 -0
  124. package/out-tsc/src/{select → form/select}/UserSelect.js +1 -1
  125. package/out-tsc/src/form/select/UserSelect.js.map +1 -0
  126. package/out-tsc/src/{select → form/select}/WorkspaceSelect.js +1 -1
  127. package/out-tsc/src/form/select/WorkspaceSelect.js.map +1 -0
  128. package/out-tsc/src/interfaces.js +1 -0
  129. package/out-tsc/src/interfaces.js.map +1 -1
  130. package/out-tsc/src/layout/Dialog.js.map +1 -0
  131. package/out-tsc/src/layout/Mask.js.map +1 -0
  132. package/out-tsc/src/{dialog → layout}/Modax.js.map +1 -1
  133. package/out-tsc/src/layout/Resizer.js.map +1 -0
  134. package/out-tsc/src/layout/Tab.js.map +1 -0
  135. package/out-tsc/src/layout/TabPane.js.map +1 -0
  136. package/out-tsc/src/list/NotificationList.js +1 -1
  137. package/out-tsc/src/list/NotificationList.js.map +1 -1
  138. package/out-tsc/src/list/RunList.js +1 -1
  139. package/out-tsc/src/list/RunList.js.map +1 -1
  140. package/out-tsc/src/list/ShortcutList.js.map +1 -1
  141. package/out-tsc/src/list/TembaMenu.js +1 -1
  142. package/out-tsc/src/list/TembaMenu.js.map +1 -1
  143. package/out-tsc/src/list/TicketList.js +1 -1
  144. package/out-tsc/src/list/TicketList.js.map +1 -1
  145. package/out-tsc/src/{aliaseditor → live}/AliasEditor.js +1 -1
  146. package/out-tsc/src/live/AliasEditor.js.map +1 -0
  147. package/out-tsc/src/{contacts → live}/ContactBadges.js +1 -1
  148. package/out-tsc/src/live/ContactBadges.js.map +1 -0
  149. package/out-tsc/src/{contacts → live}/ContactChat.js +79 -3
  150. package/out-tsc/src/live/ContactChat.js.map +1 -0
  151. package/out-tsc/src/{contacts → live}/ContactDetails.js +1 -1
  152. package/out-tsc/src/live/ContactDetails.js.map +1 -0
  153. package/out-tsc/src/{contacts → live}/ContactFieldEditor.js +2 -2
  154. package/out-tsc/src/live/ContactFieldEditor.js.map +1 -0
  155. package/out-tsc/src/live/ContactFields.js.map +1 -0
  156. package/out-tsc/src/live/ContactNameFetch.js.map +1 -0
  157. package/out-tsc/src/{contacts → live}/ContactNotepad.js +1 -1
  158. package/out-tsc/src/{contacts → live}/ContactNotepad.js.map +1 -1
  159. package/out-tsc/src/{contacts → live}/ContactPending.js +1 -1
  160. package/out-tsc/src/live/ContactPending.js.map +1 -0
  161. package/out-tsc/src/live/ContactStoreElement.js.map +1 -0
  162. package/out-tsc/src/live/FieldManager.js.map +1 -0
  163. package/out-tsc/src/live/StartProgress.js.map +1 -0
  164. package/out-tsc/src/{chart → live}/TembaChart.js +5 -1
  165. package/out-tsc/src/live/TembaChart.js.map +1 -0
  166. package/out-tsc/src/locales/es.js +5 -5
  167. package/out-tsc/src/locales/es.js.map +1 -1
  168. package/out-tsc/src/locales/fr.js +5 -5
  169. package/out-tsc/src/locales/fr.js.map +1 -1
  170. package/out-tsc/src/locales/locale-codes.js +2 -11
  171. package/out-tsc/src/locales/locale-codes.js.map +1 -1
  172. package/out-tsc/src/locales/pt.js +5 -5
  173. package/out-tsc/src/locales/pt.js.map +1 -1
  174. package/out-tsc/src/store/AppState.js +54 -24
  175. package/out-tsc/src/store/AppState.js.map +1 -1
  176. package/out-tsc/src/store/Store.js +1 -1
  177. package/out-tsc/src/store/Store.js.map +1 -1
  178. package/out-tsc/src/{utils/index.js → utils.js} +22 -1
  179. package/out-tsc/src/utils.js.map +1 -0
  180. package/out-tsc/src/webchat/WebChat.js +1 -1
  181. package/out-tsc/src/webchat/WebChat.js.map +1 -1
  182. package/out-tsc/temba-components.js +2 -2
  183. package/out-tsc/temba-components.js.map +1 -1
  184. package/out-tsc/temba-modules.js +54 -54
  185. package/out-tsc/temba-modules.js.map +1 -1
  186. package/out-tsc/temba-webchat.js +2 -2
  187. package/out-tsc/temba-webchat.js.map +1 -1
  188. package/out-tsc/test/temba-alert.test.js +1 -1
  189. package/out-tsc/test/temba-alert.test.js.map +1 -1
  190. package/out-tsc/test/temba-appstate-language.test.js +90 -0
  191. package/out-tsc/test/temba-appstate-language.test.js.map +1 -1
  192. package/out-tsc/test/temba-charcount.test.js.map +1 -1
  193. package/out-tsc/test/temba-chart.test.js +1 -1
  194. package/out-tsc/test/temba-chart.test.js.map +1 -1
  195. package/out-tsc/test/temba-checkbox.test.js.map +1 -1
  196. package/out-tsc/test/temba-color-picker.test.js +1 -1
  197. package/out-tsc/test/temba-color-picker.test.js.map +1 -1
  198. package/out-tsc/test/temba-completion.test.js +1 -1
  199. package/out-tsc/test/temba-completion.test.js.map +1 -1
  200. package/out-tsc/test/temba-compose.test.js +1 -1
  201. package/out-tsc/test/temba-compose.test.js.map +1 -1
  202. package/out-tsc/test/temba-contact-badges.test.js +1 -1
  203. package/out-tsc/test/temba-contact-badges.test.js.map +1 -1
  204. package/out-tsc/test/temba-contact-chat.test.js +3 -1
  205. package/out-tsc/test/temba-contact-chat.test.js.map +1 -1
  206. package/out-tsc/test/temba-contact-details.test.js +1 -1
  207. package/out-tsc/test/temba-contact-details.test.js.map +1 -1
  208. package/out-tsc/test/temba-contact-fields.test.js +1 -1
  209. package/out-tsc/test/temba-contact-fields.test.js.map +1 -1
  210. package/out-tsc/test/temba-contact-search.test.js +1 -1
  211. package/out-tsc/test/temba-contact-search.test.js.map +1 -1
  212. package/out-tsc/test/temba-date.test.js +5 -1
  213. package/out-tsc/test/temba-date.test.js.map +1 -1
  214. package/out-tsc/test/temba-datepicker.test.js +1 -1
  215. package/out-tsc/test/temba-datepicker.test.js.map +1 -1
  216. package/out-tsc/test/temba-dialog.test.js +1 -1
  217. package/out-tsc/test/temba-dialog.test.js.map +1 -1
  218. package/out-tsc/test/temba-dropdown.test.js +1 -1
  219. package/out-tsc/test/temba-dropdown.test.js.map +1 -1
  220. package/out-tsc/test/temba-excellent-helpers.test.js +316 -0
  221. package/out-tsc/test/temba-excellent-helpers.test.js.map +1 -0
  222. package/out-tsc/test/temba-field-manager.test.js.map +1 -1
  223. package/out-tsc/test/temba-flow-editor-node.test.js +414 -1
  224. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
  225. package/out-tsc/test/temba-flow-editor.test.js +185 -0
  226. package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
  227. package/out-tsc/test/temba-flow-plumber-connections.test.js +113 -0
  228. package/out-tsc/test/temba-flow-plumber-connections.test.js.map +1 -0
  229. package/out-tsc/test/temba-flow-plumber.test.js +73 -93
  230. package/out-tsc/test/temba-flow-plumber.test.js.map +1 -1
  231. package/out-tsc/test/temba-flow-render.test.js +624 -1
  232. package/out-tsc/test/temba-flow-render.test.js.map +1 -1
  233. package/out-tsc/test/temba-flow-self-routing.test.js +172 -0
  234. package/out-tsc/test/temba-flow-self-routing.test.js.map +1 -0
  235. package/out-tsc/test/temba-formfield.test.js.map +1 -1
  236. package/out-tsc/test/temba-icon.test.js +1 -1
  237. package/out-tsc/test/temba-icon.test.js.map +1 -1
  238. package/out-tsc/test/temba-integration-markdown.test.js.map +1 -1
  239. package/out-tsc/test/temba-label.test.js +1 -1
  240. package/out-tsc/test/temba-label.test.js.map +1 -1
  241. package/out-tsc/test/temba-lightbox.test.js +1 -1
  242. package/out-tsc/test/temba-lightbox.test.js.map +1 -1
  243. package/out-tsc/test/temba-markdown.test.js +127 -0
  244. package/out-tsc/test/temba-markdown.test.js.map +1 -0
  245. package/out-tsc/test/temba-menu.test.js +1 -1
  246. package/out-tsc/test/temba-menu.test.js.map +1 -1
  247. package/out-tsc/test/temba-modax.test.js +1 -1
  248. package/out-tsc/test/temba-modax.test.js.map +1 -1
  249. package/out-tsc/test/temba-modules.test.js +47 -0
  250. package/out-tsc/test/temba-modules.test.js.map +1 -0
  251. package/out-tsc/test/temba-omnibox.test.js +1 -1
  252. package/out-tsc/test/temba-omnibox.test.js.map +1 -1
  253. package/out-tsc/test/temba-options.test.js.map +1 -1
  254. package/out-tsc/test/temba-range-picker.test.js +9 -2
  255. package/out-tsc/test/temba-range-picker.test.js.map +1 -1
  256. package/out-tsc/test/temba-rapid-element.test.js +273 -0
  257. package/out-tsc/test/temba-rapid-element.test.js.map +1 -0
  258. package/out-tsc/test/temba-resize-element.test.js +85 -0
  259. package/out-tsc/test/temba-resize-element.test.js.map +1 -0
  260. package/out-tsc/test/temba-select.test.js +2 -2
  261. package/out-tsc/test/temba-select.test.js.map +1 -1
  262. package/out-tsc/test/temba-slider.test.js.map +1 -1
  263. package/out-tsc/test/temba-sticky-note.test.js +194 -0
  264. package/out-tsc/test/temba-sticky-note.test.js.map +1 -0
  265. package/out-tsc/test/temba-template-editor.test.js.map +1 -1
  266. package/out-tsc/test/temba-textinput.test.js +1 -1
  267. package/out-tsc/test/temba-textinput.test.js.map +1 -1
  268. package/out-tsc/test/temba-tip.test.js +1 -1
  269. package/out-tsc/test/temba-tip.test.js.map +1 -1
  270. package/out-tsc/test/temba-toast.test.js +1 -1
  271. package/out-tsc/test/temba-toast.test.js.map +1 -1
  272. package/out-tsc/test/temba-utils-index.test.js +1 -1
  273. package/out-tsc/test/temba-utils-index.test.js.map +1 -1
  274. package/out-tsc/test/temba-utils-uuid.test.js +38 -0
  275. package/out-tsc/test/temba-utils-uuid.test.js.map +1 -0
  276. package/out-tsc/test/temba-webchat.test.js +28 -12
  277. package/out-tsc/test/temba-webchat.test.js.map +1 -1
  278. package/out-tsc/test/utils.test.js +2 -6
  279. package/out-tsc/test/utils.test.js.map +1 -1
  280. package/package.json +18 -9
  281. package/rollup.components.mjs +1 -1
  282. package/screenshots/truth/datepicker/range-picker-all.png +0 -0
  283. package/screenshots/truth/datepicker/range-picker-button-states.png +0 -0
  284. package/screenshots/truth/datepicker/range-picker-default.png +0 -0
  285. package/screenshots/truth/datepicker/range-picker-editing-start.png +0 -0
  286. package/screenshots/truth/datepicker/range-picker-initial-values.png +0 -0
  287. package/screenshots/truth/datepicker/range-picker-week.png +0 -0
  288. package/screenshots/truth/datepicker/range-picker-year.png +0 -0
  289. package/screenshots/truth/sticky-note/blue-color.png +0 -0
  290. package/screenshots/truth/sticky-note/blue.png +0 -0
  291. package/screenshots/truth/sticky-note/color-picker-expanded.png +0 -0
  292. package/screenshots/truth/sticky-note/default.png +0 -0
  293. package/screenshots/truth/sticky-note/gray-color.png +0 -0
  294. package/screenshots/truth/sticky-note/gray.png +0 -0
  295. package/screenshots/truth/sticky-note/green-color.png +0 -0
  296. package/screenshots/truth/sticky-note/green.png +0 -0
  297. package/screenshots/truth/sticky-note/pink-color.png +0 -0
  298. package/screenshots/truth/sticky-note/pink.png +0 -0
  299. package/screenshots/truth/sticky-note/yellow-color.png +0 -0
  300. package/screenshots/truth/sticky-note/yellow.png +0 -0
  301. package/src/{charcount → display}/CharCount.ts +164 -2
  302. package/src/{vectoricon/VectorIcon.ts → display/Icon.ts} +1 -1
  303. package/src/{leafletmap → display}/LeafletMap.ts +19 -1
  304. package/src/{thumbnail → display}/Thumbnail.ts +1 -1
  305. package/src/{tip → display}/Tip.ts +1 -2
  306. package/src/{contacts/events.ts → events.ts} +1 -64
  307. package/src/flow/Editor.ts +655 -165
  308. package/src/flow/EditorNode.ts +337 -22
  309. package/src/flow/Plumber.ts +186 -79
  310. package/src/flow/StickyNote.ts +165 -9
  311. package/src/flow/config.ts +114 -18
  312. package/src/flow/render.ts +398 -11
  313. package/src/{checkbox → form}/Checkbox.ts +2 -2
  314. package/src/{colorpicker → form}/ColorPicker.ts +2 -2
  315. package/src/{completion → form}/Completion.ts +3 -3
  316. package/src/{compose → form}/Compose.ts +7 -7
  317. package/src/{contactsearch → form}/ContactSearch.ts +6 -6
  318. package/src/{datepicker → form}/DatePicker.ts +1 -1
  319. package/src/{FormElement.ts → form/FormElement.ts} +1 -1
  320. package/src/{imagepicker → form}/ImagePicker.ts +2 -2
  321. package/src/{mediapicker → form}/MediaPicker.ts +1 -1
  322. package/src/{datepicker → form}/RangePicker.ts +3 -2
  323. package/src/{slider → form}/TembaSlider.ts +1 -1
  324. package/src/{templates → form}/TemplateEditor.ts +2 -2
  325. package/src/{textinput → form}/TextInput.ts +5 -5
  326. package/src/{omnibox → form/select}/Omnibox.ts +2 -2
  327. package/src/{select → form/select}/PopupSelect.ts +1 -1
  328. package/src/{select → form/select}/Select.ts +124 -126
  329. package/src/{select → form/select}/UserSelect.ts +1 -1
  330. package/src/{select → form/select}/WorkspaceSelect.ts +1 -1
  331. package/src/interfaces.ts +2 -1
  332. package/src/{dialog → layout}/Dialog.ts +1 -1
  333. package/src/list/NotificationList.ts +2 -2
  334. package/src/list/RunList.ts +3 -3
  335. package/src/list/ShortcutList.ts +1 -1
  336. package/src/list/TembaMenu.ts +2 -2
  337. package/src/list/TicketList.ts +1 -1
  338. package/src/{aliaseditor → live}/AliasEditor.ts +3 -3
  339. package/src/{contacts → live}/ContactBadges.ts +1 -1
  340. package/src/{contacts → live}/ContactChat.ts +118 -8
  341. package/src/{contacts → live}/ContactDetails.ts +1 -1
  342. package/src/{contacts → live}/ContactFieldEditor.ts +4 -4
  343. package/src/{contacts → live}/ContactFields.ts +1 -1
  344. package/src/{contacts → live}/ContactNotepad.ts +1 -1
  345. package/src/{contacts → live}/ContactPending.ts +1 -1
  346. package/src/{chart → live}/TembaChart.ts +5 -2
  347. package/src/locales/es.ts +13 -18
  348. package/src/locales/fr.ts +13 -18
  349. package/src/locales/locale-codes.ts +2 -11
  350. package/src/locales/pt.ts +13 -18
  351. package/src/store/AppState.ts +75 -29
  352. package/src/store/Store.ts +1 -1
  353. package/src/store/flow-definition.d.ts +125 -0
  354. package/src/{utils/index.ts → utils.ts} +26 -10
  355. package/src/webchat/WebChat.ts +1 -1
  356. package/static/css/temba-components.css +1 -0
  357. package/svg.js +1 -4
  358. package/temba-components.ts +2 -2
  359. package/temba-modules.ts +54 -54
  360. package/temba-webchat.ts +2 -2
  361. package/test/temba-alert.test.ts +1 -1
  362. package/test/temba-appstate-language.test.ts +108 -0
  363. package/test/temba-charcount.test.ts +1 -1
  364. package/test/temba-chart.test.ts +1 -1
  365. package/test/temba-checkbox.test.ts +1 -1
  366. package/test/temba-color-picker.test.ts +1 -1
  367. package/test/temba-completion.test.ts +1 -1
  368. package/test/temba-compose.test.ts +1 -1
  369. package/test/temba-contact-badges.test.ts +1 -1
  370. package/test/temba-contact-chat.test.ts +6 -4
  371. package/test/temba-contact-details.test.ts +1 -1
  372. package/test/temba-contact-fields.test.ts +1 -1
  373. package/test/temba-contact-search.test.ts +2 -2
  374. package/test/temba-date.test.ts +8 -3
  375. package/test/temba-datepicker.test.ts +1 -1
  376. package/test/temba-dialog.test.ts +1 -1
  377. package/test/temba-dropdown.test.ts +1 -1
  378. package/test/temba-excellent-helpers.test.ts +417 -0
  379. package/test/temba-field-manager.test.ts +2 -2
  380. package/test/temba-flow-editor-node.test.ts +536 -1
  381. package/test/temba-flow-editor.test.ts +224 -0
  382. package/test/temba-flow-editor.test.ts.backup +563 -0
  383. package/test/temba-flow-plumber-connections.test.ts +142 -0
  384. package/test/temba-flow-plumber.test.ts +83 -120
  385. package/test/temba-flow-render.test.ts +787 -4
  386. package/test/temba-flow-self-routing.test.ts +215 -0
  387. package/test/temba-formfield.test.ts +1 -1
  388. package/test/temba-icon.test.ts +1 -1
  389. package/test/temba-integration-markdown.test.ts +1 -1
  390. package/test/temba-label.test.ts +1 -1
  391. package/test/temba-lightbox.test.ts +1 -1
  392. package/test/temba-markdown.test.ts +162 -0
  393. package/test/temba-menu.test.ts +1 -1
  394. package/test/temba-modax.test.ts +2 -2
  395. package/test/temba-modules.test.ts +56 -0
  396. package/test/temba-omnibox.test.ts +1 -1
  397. package/test/temba-options.test.ts +1 -1
  398. package/test/temba-range-picker.test.ts +17 -2
  399. package/test/temba-rapid-element.test.ts +341 -0
  400. package/test/temba-resize-element.test.ts +104 -0
  401. package/test/temba-select.test.ts +2 -2
  402. package/test/temba-slider.test.ts +1 -1
  403. package/test/temba-sticky-note.test.ts +281 -0
  404. package/test/temba-template-editor.test.ts +1 -1
  405. package/test/temba-textinput.test.ts +1 -1
  406. package/test/temba-tip.test.ts +1 -1
  407. package/test/temba-toast.test.ts +1 -1
  408. package/test/temba-utils-index.test.ts +1 -1
  409. package/test/temba-utils-index.test.ts.backup +1737 -0
  410. package/test/temba-utils-uuid.test.ts +48 -0
  411. package/test/temba-webchat.test.ts +30 -12
  412. package/test/utils.test.ts +5 -9
  413. package/web-dev-server.config.mjs +1 -1
  414. package/demo/alert/example.html +0 -65
  415. package/demo/button/example.html +0 -71
  416. package/demo/chart/horizontal-demo.html +0 -81
  417. package/demo/checkbox/example.html +0 -72
  418. package/demo/compose/example.html +0 -72
  419. package/demo/dropdown/example.html +0 -99
  420. package/demo/progress/example.html +0 -59
  421. package/demo/select/drag-and-drop.html +0 -142
  422. package/demo/select/example.html +0 -82
  423. package/demo/select/multi.html +0 -73
  424. package/demo/slider/example.html +0 -59
  425. package/demo/tabs/example.html +0 -91
  426. package/demo/textinput/example.html +0 -61
  427. package/out-tsc/src/FormElement.js.map +0 -1
  428. package/out-tsc/src/alert/Alert.js.map +0 -1
  429. package/out-tsc/src/aliaseditor/AliasEditor.js.map +0 -1
  430. package/out-tsc/src/anchor/Anchor.js.map +0 -1
  431. package/out-tsc/src/button/Button.js.map +0 -1
  432. package/out-tsc/src/charcount/CharCount.js.map +0 -1
  433. package/out-tsc/src/charcount/helpers.js +0 -159
  434. package/out-tsc/src/charcount/helpers.js.map +0 -1
  435. package/out-tsc/src/chart/TembaChart.js.map +0 -1
  436. package/out-tsc/src/chat/Chat.js.map +0 -1
  437. package/out-tsc/src/checkbox/Checkbox.js.map +0 -1
  438. package/out-tsc/src/colorpicker/ColorPicker.js.map +0 -1
  439. package/out-tsc/src/completion/Completion.js.map +0 -1
  440. package/out-tsc/src/completion/ExcellentParser.js.map +0 -1
  441. package/out-tsc/src/completion/helpers.js.map +0 -1
  442. package/out-tsc/src/compose/Compose.js.map +0 -1
  443. package/out-tsc/src/contacts/ContactBadges.js.map +0 -1
  444. package/out-tsc/src/contacts/ContactChat.js.map +0 -1
  445. package/out-tsc/src/contacts/ContactDetails.js.map +0 -1
  446. package/out-tsc/src/contacts/ContactFieldEditor.js.map +0 -1
  447. package/out-tsc/src/contacts/ContactFields.js.map +0 -1
  448. package/out-tsc/src/contacts/ContactName.js.map +0 -1
  449. package/out-tsc/src/contacts/ContactNameFetch.js.map +0 -1
  450. package/out-tsc/src/contacts/ContactPending.js.map +0 -1
  451. package/out-tsc/src/contacts/ContactStoreElement.js.map +0 -1
  452. package/out-tsc/src/contacts/ContactUrn.js.map +0 -1
  453. package/out-tsc/src/contacts/events.js +0 -65
  454. package/out-tsc/src/contacts/events.js.map +0 -1
  455. package/out-tsc/src/contacts/helpers.js +0 -77
  456. package/out-tsc/src/contacts/helpers.js.map +0 -1
  457. package/out-tsc/src/contactsearch/ContactSearch.js.map +0 -1
  458. package/out-tsc/src/date/TembaDate.js.map +0 -1
  459. package/out-tsc/src/datepicker/DatePicker.js.map +0 -1
  460. package/out-tsc/src/datepicker/RangePicker.js.map +0 -1
  461. package/out-tsc/src/dialog/Dialog.js.map +0 -1
  462. package/out-tsc/src/dropdown/Dropdown.js.map +0 -1
  463. package/out-tsc/src/fields/FieldManager.js.map +0 -1
  464. package/out-tsc/src/formfield/FormField.js.map +0 -1
  465. package/out-tsc/src/imagepicker/CroppieCSS.js.map +0 -1
  466. package/out-tsc/src/imagepicker/ImagePicker.js.map +0 -1
  467. package/out-tsc/src/label/Label.js.map +0 -1
  468. package/out-tsc/src/leafletmap/LeafletMap.js.map +0 -1
  469. package/out-tsc/src/leafletmap/helpers.js +0 -17
  470. package/out-tsc/src/leafletmap/helpers.js.map +0 -1
  471. package/out-tsc/src/lightbox/Lightbox.js.map +0 -1
  472. package/out-tsc/src/mask/Mask.js.map +0 -1
  473. package/out-tsc/src/mediapicker/MediaPicker.js.map +0 -1
  474. package/out-tsc/src/omnibox/Omnibox.js.map +0 -1
  475. package/out-tsc/src/options/helpers.js +0 -28
  476. package/out-tsc/src/options/helpers.js.map +0 -1
  477. package/out-tsc/src/progress/ProgressBar.js.map +0 -1
  478. package/out-tsc/src/progress/StartProgress.js.map +0 -1
  479. package/out-tsc/src/resizer/Resizer.js.map +0 -1
  480. package/out-tsc/src/select/PopupSelect.js.map +0 -1
  481. package/out-tsc/src/select/Select.js.map +0 -1
  482. package/out-tsc/src/select/UserSelect.js.map +0 -1
  483. package/out-tsc/src/select/WorkspaceSelect.js.map +0 -1
  484. package/out-tsc/src/select/helpers.js +0 -1
  485. package/out-tsc/src/select/helpers.js.map +0 -1
  486. package/out-tsc/src/shadowless/Shadowless.js +0 -33
  487. package/out-tsc/src/shadowless/Shadowless.js.map +0 -1
  488. package/out-tsc/src/slider/TembaSlider.js.map +0 -1
  489. package/out-tsc/src/sms/gsmsplitter.js.map +0 -1
  490. package/out-tsc/src/sms/gsmvalidator.js.map +0 -1
  491. package/out-tsc/src/sms/index.js.map +0 -1
  492. package/out-tsc/src/sms/unicodesplitter.js.map +0 -1
  493. package/out-tsc/src/tabpane/Tab.js.map +0 -1
  494. package/out-tsc/src/tabpane/TabPane.js.map +0 -1
  495. package/out-tsc/src/templates/TemplateEditor.js.map +0 -1
  496. package/out-tsc/src/textinput/TextInput.js.map +0 -1
  497. package/out-tsc/src/textinput/helpers.js +0 -12
  498. package/out-tsc/src/textinput/helpers.js.map +0 -1
  499. package/out-tsc/src/thumbnail/Thumbnail.js.map +0 -1
  500. package/out-tsc/src/tip/Tip.js.map +0 -1
  501. package/out-tsc/src/tip/helpers.js +0 -7
  502. package/out-tsc/src/tip/helpers.js.map +0 -1
  503. package/out-tsc/src/toast/Toast.js.map +0 -1
  504. package/out-tsc/src/user/TembaUser.js.map +0 -1
  505. package/out-tsc/src/utils/index.js.map +0 -1
  506. package/out-tsc/src/vectoricon/VectorIcon.js.map +0 -1
  507. package/out-tsc/src/vectoricon/index.js.map +0 -1
  508. package/src/charcount/helpers.ts +0 -162
  509. package/src/contacts/helpers.ts +0 -103
  510. package/src/leafletmap/helpers.ts +0 -18
  511. package/src/options/helpers.ts +0 -37
  512. package/src/select/helpers.ts +0 -0
  513. package/src/shadowless/Shadowless.ts +0 -32
  514. package/src/textinput/helpers.ts +0 -11
  515. package/src/tip/helpers.ts +0 -7
  516. /package/out-tsc/src/{alert → display}/Alert.js +0 -0
  517. /package/out-tsc/src/{anchor → display}/Anchor.js +0 -0
  518. /package/out-tsc/src/{button → display}/Button.js +0 -0
  519. /package/out-tsc/src/{chat → display}/Chat.js +0 -0
  520. /package/out-tsc/src/{contacts → display}/ContactName.js +0 -0
  521. /package/out-tsc/src/{contacts → display}/ContactUrn.js +0 -0
  522. /package/out-tsc/src/{dropdown → display}/Dropdown.js +0 -0
  523. /package/out-tsc/src/{label → display}/Label.js +0 -0
  524. /package/out-tsc/src/{lightbox → display}/Lightbox.js +0 -0
  525. /package/out-tsc/src/{loading → display}/Loading.js +0 -0
  526. /package/out-tsc/src/{options → display}/Options.js +0 -0
  527. /package/out-tsc/src/{progress → display}/ProgressBar.js +0 -0
  528. /package/out-tsc/src/{date → display}/TembaDate.js +0 -0
  529. /package/out-tsc/src/{user → display}/TembaUser.js +0 -0
  530. /package/out-tsc/src/{thumbnail → display}/Thumbnail.js +0 -0
  531. /package/out-tsc/src/{toast → display}/Toast.js +0 -0
  532. /package/out-tsc/src/{sms → display/sms}/gsmsplitter.js +0 -0
  533. /package/out-tsc/src/{sms → display/sms}/gsmvalidator.js +0 -0
  534. /package/out-tsc/src/{sms → display/sms}/index.js +0 -0
  535. /package/out-tsc/src/{sms → display/sms}/unicodesplitter.js +0 -0
  536. /package/out-tsc/src/{completion → excellent}/ExcellentParser.js +0 -0
  537. /package/out-tsc/src/{completion → excellent}/helpers.js +0 -0
  538. /package/out-tsc/src/{imagepicker → form}/CroppieCSS.js +0 -0
  539. /package/out-tsc/src/{formfield → form}/FormField.js +0 -0
  540. /package/out-tsc/src/{dialog → layout}/Dialog.js +0 -0
  541. /package/out-tsc/src/{mask → layout}/Mask.js +0 -0
  542. /package/out-tsc/src/{dialog → layout}/Modax.js +0 -0
  543. /package/out-tsc/src/{resizer → layout}/Resizer.js +0 -0
  544. /package/out-tsc/src/{tabpane → layout}/Tab.js +0 -0
  545. /package/out-tsc/src/{tabpane → layout}/TabPane.js +0 -0
  546. /package/out-tsc/src/{contacts → live}/ContactFields.js +0 -0
  547. /package/out-tsc/src/{contacts → live}/ContactNameFetch.js +0 -0
  548. /package/out-tsc/src/{contacts → live}/ContactStoreElement.js +0 -0
  549. /package/out-tsc/src/{fields → live}/FieldManager.js +0 -0
  550. /package/out-tsc/src/{progress → live}/StartProgress.js +0 -0
  551. /package/src/{vectoricon/index.ts → Icons.ts} +0 -0
  552. /package/src/{alert → display}/Alert.ts +0 -0
  553. /package/src/{anchor → display}/Anchor.ts +0 -0
  554. /package/src/{button → display}/Button.ts +0 -0
  555. /package/src/{chat → display}/Chat.ts +0 -0
  556. /package/src/{contacts → display}/ContactName.ts +0 -0
  557. /package/src/{contacts → display}/ContactUrn.ts +0 -0
  558. /package/src/{dropdown → display}/Dropdown.ts +0 -0
  559. /package/src/{label → display}/Label.ts +0 -0
  560. /package/src/{lightbox → display}/Lightbox.ts +0 -0
  561. /package/src/{loading → display}/Loading.ts +0 -0
  562. /package/src/{options → display}/Options.ts +0 -0
  563. /package/src/{progress → display}/ProgressBar.ts +0 -0
  564. /package/src/{date → display}/TembaDate.ts +0 -0
  565. /package/src/{user → display}/TembaUser.ts +0 -0
  566. /package/src/{toast → display}/Toast.ts +0 -0
  567. /package/src/{sms → display/sms}/gsmsplitter.ts +0 -0
  568. /package/src/{sms → display/sms}/gsmvalidator.ts +0 -0
  569. /package/src/{sms → display/sms}/index.ts +0 -0
  570. /package/src/{sms → display/sms}/unicodesplitter.ts +0 -0
  571. /package/src/{completion → excellent}/ExcellentParser.ts +0 -0
  572. /package/src/{completion → excellent}/helpers.ts +0 -0
  573. /package/src/{imagepicker → form}/CroppieCSS.ts +0 -0
  574. /package/src/{formfield → form}/FormField.ts +0 -0
  575. /package/src/{mask → layout}/Mask.ts +0 -0
  576. /package/src/{dialog → layout}/Modax.ts +0 -0
  577. /package/src/{resizer → layout}/Resizer.ts +0 -0
  578. /package/src/{tabpane → layout}/Tab.ts +0 -0
  579. /package/src/{tabpane → layout}/TabPane.ts +0 -0
  580. /package/src/{contacts → live}/ContactNameFetch.ts +0 -0
  581. /package/src/{contacts → live}/ContactStoreElement.ts +0 -0
  582. /package/src/{fields → live}/FieldManager.ts +0 -0
  583. /package/src/{progress → live}/StartProgress.ts +0 -0
@@ -0,0 +1,563 @@
1
+ import { html, fixture, expect } from '@open-wc/testing';
2
+ import { Editor } from '../src/flow/Editor';
3
+ import { Plumber } from '../src/flow/Plumber';
4
+ import { stub, restore } from 'sinon';
5
+
6
+ // Register the component
7
+ customElements.define('temba-flow-editor', Editor);
8
+
9
+ describe('Editor', () => {
10
+ let editor: Editor;
11
+
12
+ beforeEach(() => {
13
+ // Reset any stubs
14
+ restore();
15
+ });
16
+
17
+ afterEach(() => {
18
+ restore();
19
+ });
20
+
21
+ describe('basic functionality', () => {
22
+ it('creates render root as element itself', () => {
23
+ const editor = new Editor();
24
+ expect(editor.createRenderRoot()).to.equal(editor);
25
+ });
26
+
27
+ it('has correct CSS styles defined', () => {
28
+ const styles = Editor.styles;
29
+ expect(styles).to.exist;
30
+ expect(styles.cssText).to.contain('#editor');
31
+ expect(styles.cssText).to.contain('#grid');
32
+ expect(styles.cssText).to.contain('#canvas');
33
+ expect(styles.cssText).to.contain('.plumb-source');
34
+ expect(styles.cssText).to.contain('.plumb-target');
35
+ expect(styles.cssText).to.contain('.plumb-connector');
36
+ });
37
+
38
+ it('creates with default properties', () => {
39
+ editor = new Editor();
40
+ expect(editor.flow).to.be.undefined;
41
+ expect(editor.version).to.be.undefined;
42
+ });
43
+
44
+ it('accepts flow and version properties without getStore call', async () => {
45
+ editor = document.createElement('temba-flow-editor') as Editor;
46
+ editor.flow = 'test-flow-uuid';
47
+ editor.version = '1.0';
48
+
49
+ expect(editor.flow).to.equal('test-flow-uuid');
50
+ expect(editor.version).to.equal('1.0');
51
+ });
52
+ });
53
+
54
+ describe('lifecycle methods', () => {
55
+ it('calls firstUpdated and initializes plumber', async () => {
56
+ editor = await fixture(html`
57
+ <temba-flow-editor>
58
+ <div id="canvas"></div>
59
+ </temba-flow-editor>
60
+ `);
61
+
62
+ // Verify that plumber is initialized
63
+ expect((editor as any).plumber).to.be.instanceOf(Plumber);
64
+ });
65
+
66
+ it('verifies firstUpdated method exists and can be called', () => {
67
+ editor = new Editor();
68
+
69
+ // Mock canvas element
70
+ const mockCanvas = document.createElement('div');
71
+ mockCanvas.id = 'canvas';
72
+
73
+ // Mock querySelector to return our mock canvas
74
+ stub(editor, 'querySelector').returns(mockCanvas);
75
+
76
+ // Verify firstUpdated method exists
77
+ expect(typeof (editor as any).firstUpdated).to.equal('function');
78
+
79
+ // Test that calling firstUpdated doesn't throw (without getStore)
80
+ expect(() => {
81
+ // Only test the plumber initialization part
82
+ (editor as any).plumber = new Plumber(mockCanvas);
83
+ }).to.not.throw();
84
+ });
85
+
86
+ it('handles updated with canvasSize changes', async () => {
87
+ editor = await fixture(html`
88
+ <temba-flow-editor>
89
+ <div id="canvas"></div>
90
+ </temba-flow-editor>
91
+ `);
92
+
93
+ // Simulate canvasSize change
94
+ (editor as any).canvasSize = { width: 800, height: 600 };
95
+ const changes = new Map();
96
+ changes.set('canvasSize', true);
97
+
98
+ (editor as any).updated(changes);
99
+
100
+ // Verify the canvasSize was set correctly
101
+ expect((editor as any).canvasSize).to.deep.equal({
102
+ width: 800,
103
+ height: 600
104
+ });
105
+ });
106
+
107
+ it('handles updated without canvasSize changes', async () => {
108
+ editor = await fixture(html`
109
+ <temba-flow-editor>
110
+ <div id="canvas"></div>
111
+ </temba-flow-editor>
112
+ `);
113
+
114
+ const consoleStub = stub(console, 'log');
115
+
116
+ const changes = new Map();
117
+ changes.set('other', true);
118
+
119
+ (editor as any).updated(changes);
120
+
121
+ expect(consoleStub).to.not.have.been.called;
122
+
123
+ consoleStub.restore();
124
+ });
125
+ });
126
+
127
+ describe('render method', () => {
128
+ it('renders loading when no definition', async () => {
129
+ editor = await fixture(html`
130
+ <temba-flow-editor>
131
+ <div id="canvas"></div>
132
+ </temba-flow-editor>
133
+ `);
134
+
135
+ // Set canvas size to avoid undefined errors
136
+ (editor as any).canvasSize = { width: 800, height: 600 };
137
+ await editor.updateComplete;
138
+
139
+ const loadingElement = editor.querySelector('temba-loading');
140
+ expect(loadingElement).to.exist;
141
+ });
142
+
143
+ it('renders nodes when definition exists', async () => {
144
+ const mockDefinition = {
145
+ nodes: [
146
+ {
147
+ uuid: 'node-1',
148
+ actions: [],
149
+ exits: []
150
+ },
151
+ {
152
+ uuid: 'node-2',
153
+ actions: [],
154
+ exits: []
155
+ }
156
+ ],
157
+ _ui: {
158
+ nodes: {
159
+ 'node-1': { position: { left: 100, top: 200 } },
160
+ 'node-2': { position: { left: 300, top: 400 } }
161
+ }
162
+ }
163
+ };
164
+
165
+ editor = await fixture(html`
166
+ <temba-flow-editor>
167
+ <div id="canvas"></div>
168
+ </temba-flow-editor>
169
+ `);
170
+
171
+ // Set properties
172
+ (editor as any).definition = mockDefinition;
173
+ (editor as any).canvasSize = { width: 800, height: 600 };
174
+ await editor.updateComplete;
175
+
176
+ const flowNodes = editor.querySelectorAll('temba-flow-node');
177
+ expect(flowNodes).to.have.length(2);
178
+ });
179
+
180
+ it('includes style elements in light DOM', async () => {
181
+ editor = await fixture(html`
182
+ <temba-flow-editor>
183
+ <div id="canvas"></div>
184
+ </temba-flow-editor>
185
+ `);
186
+
187
+ // Set canvas size
188
+ (editor as any).canvasSize = { width: 800, height: 600 };
189
+ await editor.updateComplete;
190
+
191
+ const styleElements = editor.querySelectorAll('style');
192
+ expect(styleElements.length).to.be.greaterThan(0);
193
+ });
194
+
195
+ it('renders with correct grid dimensions', async () => {
196
+ editor = await fixture(html`
197
+ <temba-flow-editor>
198
+ <div id="canvas"></div>
199
+ </temba-flow-editor>
200
+ `);
201
+
202
+ // Set canvas size to specific dimensions
203
+ (editor as any).canvasSize = { width: 1200, height: 900 };
204
+ await editor.updateComplete;
205
+
206
+ const gridElement = editor.querySelector('#grid');
207
+ expect(gridElement).to.exist;
208
+
209
+ // Check that grid has correct dimensions
210
+ const gridStyle = gridElement.getAttribute('style');
211
+ expect(gridStyle).to.contain('width:1200px');
212
+ expect(gridStyle).to.contain('height:900px');
213
+ });
214
+ });
215
+
216
+ describe('multi-selection functionality', () => {
217
+ let mockDefinition: any;
218
+
219
+ beforeEach(() => {
220
+ mockDefinition = {
221
+ nodes: [
222
+ {
223
+ uuid: 'node-1',
224
+ actions: [],
225
+ exits: []
226
+ },
227
+ {
228
+ uuid: 'node-2',
229
+ actions: [],
230
+ exits: []
231
+ }
232
+ ],
233
+ _ui: {
234
+ nodes: {
235
+ 'node-1': { position: { left: 100, top: 200 } },
236
+ 'node-2': { position: { left: 300, top: 400 } }
237
+ },
238
+ stickies: {
239
+ 'sticky-1': {
240
+ position: { left: 200, top: 100 },
241
+ title: 'Test Sticky',
242
+ body: 'Test content',
243
+ color: 'yellow'
244
+ }
245
+ }
246
+ }
247
+ };
248
+ });
249
+
250
+ it('initializes with empty selection', async () => {
251
+ editor = await fixture(html`
252
+ <temba-flow-editor>
253
+ <div id="canvas"></div>
254
+ </temba-flow-editor>
255
+ `);
256
+
257
+ expect((editor as any).selectedItems.size).to.equal(0);
258
+ expect((editor as any).isSelecting).to.be.false;
259
+ expect((editor as any).selectionBox).to.be.null;
260
+ });
261
+
262
+ it('adds visual selection styling to selected items', async () => {
263
+ editor = await fixture(html`
264
+ <temba-flow-editor>
265
+ <div id="canvas"></div>
266
+ </temba-flow-editor>
267
+ `);
268
+
269
+ // Set up mock definition and selection
270
+ (editor as any).definition = mockDefinition;
271
+ (editor as any).canvasSize = { width: 800, height: 600 };
272
+ (editor as any).selectedItems = new Set(['node-1']);
273
+ await editor.updateComplete;
274
+
275
+ const selectedNode = editor.querySelector('temba-flow-node[uuid="node-1"]');
276
+ expect(selectedNode).to.exist;
277
+ expect(selectedNode.classList.contains('selected')).to.be.true;
278
+ });
279
+
280
+ it('renders selection box when selecting', async () => {
281
+ editor = await fixture(html`
282
+ <temba-flow-editor>
283
+ <div id="canvas"></div>
284
+ </temba-flow-editor>
285
+ `);
286
+
287
+ // Set up selection state
288
+ (editor as any).definition = mockDefinition;
289
+ (editor as any).canvasSize = { width: 800, height: 600 };
290
+ (editor as any).isSelecting = true;
291
+ (editor as any).selectionBox = {
292
+ startX: 50,
293
+ startY: 50,
294
+ endX: 150,
295
+ endY: 150
296
+ };
297
+ await editor.updateComplete;
298
+
299
+ const selectionBox = editor.querySelector('.selection-box');
300
+ expect(selectionBox).to.exist;
301
+
302
+ const style = selectionBox.getAttribute('style');
303
+ expect(style).to.contain('left: 50px');
304
+ expect(style).to.contain('top: 50px');
305
+ expect(style).to.contain('width: 100px');
306
+ expect(style).to.contain('height: 100px');
307
+ });
308
+
309
+ it('does not render selection box when not selecting', async () => {
310
+ editor = await fixture(html`
311
+ <temba-flow-editor>
312
+ <div id="canvas"></div>
313
+ </temba-flow-editor>
314
+ `);
315
+
316
+ (editor as any).definition = mockDefinition;
317
+ (editor as any).canvasSize = { width: 800, height: 600 };
318
+ (editor as any).isSelecting = false;
319
+ await editor.updateComplete;
320
+
321
+ const selectionBox = editor.querySelector('.selection-box');
322
+ expect(selectionBox).to.not.exist;
323
+ });
324
+
325
+ it('handles canvas mouse down for selection start', async () => {
326
+ editor = await fixture(html`
327
+ <temba-flow-editor>
328
+ <div id="canvas"></div>
329
+ </temba-flow-editor>
330
+ `);
331
+
332
+ (editor as any).definition = mockDefinition;
333
+ (editor as any).canvasSize = { width: 800, height: 600 };
334
+
335
+ // Pre-select an item
336
+ (editor as any).selectedItems.add('node-1');
337
+ await editor.updateComplete;
338
+
339
+ const canvas = editor.querySelector('#canvas');
340
+ const mockEvent = new MouseEvent('mousedown', {
341
+ clientX: 100,
342
+ clientY: 100,
343
+ bubbles: true
344
+ });
345
+
346
+ // Mock getBoundingClientRect for canvas
347
+ stub(canvas, 'getBoundingClientRect').returns({
348
+ left: 0,
349
+ top: 0,
350
+ width: 800,
351
+ height: 600
352
+ } as DOMRect);
353
+
354
+ // Set event target to canvas
355
+ Object.defineProperty(mockEvent, 'target', {
356
+ value: canvas,
357
+ writable: false
358
+ });
359
+
360
+ (editor as any).handleCanvasMouseDown(mockEvent);
361
+
362
+ // Should clear selection and start selection box
363
+ expect((editor as any).selectedItems.size).to.equal(0);
364
+ expect((editor as any).canvasMouseDown).to.be.true;
365
+ expect((editor as any).selectionBox).to.not.be.null;
366
+ });
367
+
368
+ it('handles keyboard delete key with confirmation', async () => {
369
+ editor = await fixture(html`
370
+ <temba-flow-editor>
371
+ <div id="canvas"></div>
372
+ </temba-flow-editor>
373
+ `);
374
+
375
+ // Mock window.confirm
376
+ const confirmStub = stub(window, 'confirm');
377
+ confirmStub.returns(true);
378
+
379
+ // Set up selection
380
+ (editor as any).selectedItems.add('node-1');
381
+
382
+ const mockEvent = new KeyboardEvent('keydown', { key: 'Delete' });
383
+ (editor as any).handleKeyDown(mockEvent);
384
+
385
+ expect(confirmStub).to.have.been.calledWith('Are you sure you want to delete 1 item?');
386
+
387
+ confirmStub.restore();
388
+ });
389
+
390
+ it('handles escape key to clear selection', async () => {
391
+ editor = await fixture(html`
392
+ <temba-flow-editor>
393
+ <div id="canvas"></div>
394
+ </temba-flow-editor>
395
+ `);
396
+
397
+ // Set up selection
398
+ (editor as any).selectedItems.add('node-1');
399
+ (editor as any).selectedItems.add('node-2');
400
+
401
+ const mockEvent = new KeyboardEvent('keydown', { key: 'Escape' });
402
+ (editor as any).handleKeyDown(mockEvent);
403
+
404
+ expect((editor as any).selectedItems.size).to.equal(0);
405
+ });
406
+
407
+ it('updates selection box coordinates during mouse move', async () => {
408
+ editor = await fixture(html`
409
+ <temba-flow-editor>
410
+ <div id="canvas"></div>
411
+ </temba-flow-editor>
412
+ `);
413
+
414
+ const canvas = editor.querySelector('#canvas');
415
+
416
+ // Mock getBoundingClientRect
417
+ stub(canvas, 'getBoundingClientRect').returns({
418
+ left: 0,
419
+ top: 0,
420
+ width: 800,
421
+ height: 600
422
+ } as DOMRect);
423
+
424
+ // Set up initial selection state
425
+ (editor as any).canvasMouseDown = true;
426
+ (editor as any).selectionBox = {
427
+ startX: 50,
428
+ startY: 50,
429
+ endX: 50,
430
+ endY: 50
431
+ };
432
+ (editor as any).definition = mockDefinition;
433
+
434
+ const mockEvent = new MouseEvent('mousemove', {
435
+ clientX: 150,
436
+ clientY: 150
437
+ });
438
+
439
+ (editor as any).updateSelectionBox(mockEvent);
440
+
441
+ expect((editor as any).selectionBox.endX).to.equal(150);
442
+ expect((editor as any).selectionBox.endY).to.equal(150);
443
+ });
444
+
445
+ it('calculates intersections correctly for node selection', async () => {
446
+ editor = await fixture(html`
447
+ <temba-flow-editor>
448
+ <div id="canvas"></div>
449
+ </temba-flow-editor>
450
+ `);
451
+
452
+ (editor as any).definition = mockDefinition;
453
+ (editor as any).canvasSize = { width: 800, height: 600 };
454
+ await editor.updateComplete;
455
+
456
+ // Mock node element and its bounding rect
457
+ const nodeElement = editor.querySelector('temba-flow-node[uuid="node-1"]');
458
+ stub(nodeElement, 'getBoundingClientRect').returns({
459
+ width: 200,
460
+ height: 100
461
+ } as DOMRect);
462
+
463
+ // Set selection box that intersects with node-1 (position: left: 100, top: 200)
464
+ (editor as any).selectionBox = {
465
+ startX: 50, // Selection box from 50,150 to 250,250
466
+ startY: 150, // This should intersect node-1 at 100,200 with size 200x100
467
+ endX: 250,
468
+ endY: 250
469
+ };
470
+
471
+ (editor as any).updateSelectedItemsFromBox();
472
+
473
+ expect((editor as any).selectedItems.has('node-1')).to.be.true;
474
+ });
475
+ });
476
+
477
+ describe('canvas initialization', () => {
478
+ <div id="canvas"></div>
479
+ </temba-flow-editor>
480
+ `);
481
+
482
+ (editor as any).canvasSize = { width: 800, height: 600 };
483
+ await editor.updateComplete;
484
+
485
+ const editorElement = editor.querySelector('#editor');
486
+ expect(editorElement).to.exist;
487
+
488
+ const gridElement = editor.querySelector('#grid');
489
+ expect(gridElement).to.exist;
490
+
491
+ const canvasElement = editor.querySelector('#canvas');
492
+ expect(canvasElement).to.exist;
493
+ });
494
+ });
495
+
496
+ describe('property handling', () => {
497
+ it('handles flow property change', async () => {
498
+ editor = await fixture(html`
499
+ <temba-flow-editor>
500
+ <div id="canvas"></div>
501
+ </temba-flow-editor>
502
+ `);
503
+
504
+ // Change flow property
505
+ editor.flow = 'new-flow-uuid';
506
+ await editor.updateComplete;
507
+
508
+ expect(editor.flow).to.equal('new-flow-uuid');
509
+ });
510
+
511
+ it('handles version property change', async () => {
512
+ editor = await fixture(html`
513
+ <temba-flow-editor>
514
+ <div id="canvas"></div>
515
+ </temba-flow-editor>
516
+ `);
517
+
518
+ editor.version = '2.0';
519
+ await editor.updateComplete;
520
+
521
+ expect(editor.version).to.equal('2.0');
522
+ });
523
+ });
524
+
525
+ describe('store integration', () => {
526
+ it('has fromStore decorators for definition and canvasSize', () => {
527
+ editor = new Editor();
528
+
529
+ // Check that the properties exist (they are private but we can verify they exist)
530
+ expect(editor).to.have.property('definition');
531
+ expect(editor).to.have.property('canvasSize');
532
+ });
533
+ });
534
+
535
+ describe('constructor behavior', () => {
536
+ it('calls super in constructor', () => {
537
+ // This mainly verifies the constructor doesn't throw
538
+ expect(() => {
539
+ new Editor();
540
+ }).to.not.throw();
541
+ });
542
+ });
543
+
544
+ describe('canvas initialization', () => {
545
+ it('initializes plumber with canvas element', async () => {
546
+ editor = await fixture(html`
547
+ <temba-flow-editor>
548
+ <div id="canvas"></div>
549
+ </temba-flow-editor>
550
+ `);
551
+
552
+ const plumber = (editor as any).plumber;
553
+ expect(plumber).to.be.instanceOf(Plumber);
554
+ });
555
+
556
+ it('handles missing canvas element gracefully', async () => {
557
+ editor = await fixture(html`<temba-flow-editor></temba-flow-editor>`);
558
+
559
+ // Should not throw even without canvas
560
+ expect((editor as any).plumber).to.be.instanceOf(Plumber);
561
+ });
562
+ });
563
+ });
@@ -0,0 +1,142 @@
1
+ import { expect } from '@open-wc/testing';
2
+ import { stub, useFakeTimers, SinonFakeTimers } from 'sinon';
3
+ import { Plumber } from '../src/flow/Plumber';
4
+
5
+ describe('Plumber - Connection Management', () => {
6
+ let plumber: Plumber;
7
+ let mockCanvas: HTMLElement;
8
+ let clock: SinonFakeTimers;
9
+
10
+ beforeEach(() => {
11
+ // Use fake timers to control setTimeout
12
+ clock = useFakeTimers();
13
+
14
+ // Create mock canvas and make getElementById return a mock element
15
+ mockCanvas = document.createElement('div');
16
+ const mockElement = document.createElement('div');
17
+ stub(document, 'getElementById').returns(mockElement);
18
+
19
+ // Create a new plumber instance
20
+ plumber = new Plumber(mockCanvas);
21
+
22
+ // Replace the internal jsPlumb instance with mocks
23
+ (plumber as any).jsPlumb = {
24
+ getConnections: stub().returns([]),
25
+ addClass: stub(),
26
+ removeClass: stub(),
27
+ batch: stub().callsFake((fn: any) => fn()),
28
+ addEndpoint: stub().returns({}),
29
+ connect: stub(),
30
+ selectEndpoints: stub().returns({
31
+ deleteAll: stub()
32
+ }),
33
+ deleteConnection: stub(),
34
+ removeAllEndpoints: stub(),
35
+ repaintEverything: stub()
36
+ };
37
+ });
38
+
39
+ afterEach(() => {
40
+ // Restore the original document.getElementById
41
+ (document.getElementById as any).restore?.();
42
+ clock.restore();
43
+ });
44
+
45
+ describe('setConnectionRemovingState', () => {
46
+ it('returns false when no connections are found', () => {
47
+ const result = plumber.setConnectionRemovingState('test-exit', true);
48
+ expect(result).to.be.false;
49
+ expect((plumber as any).jsPlumb.getConnections).to.have.been.called;
50
+ });
51
+
52
+ it('sets removing class on connections when isRemoving is true', () => {
53
+ const mockConnections = [
54
+ { id: 'conn1', addClass: stub() },
55
+ { id: 'conn2', addClass: stub() }
56
+ ];
57
+
58
+ (plumber as any).jsPlumb.getConnections = stub().returns(mockConnections);
59
+
60
+ const result = plumber.setConnectionRemovingState('test-exit', true);
61
+ expect(result).to.be.true;
62
+ expect(mockConnections[0].addClass).to.have.been.calledWith('removing');
63
+ expect(mockConnections[1].addClass).to.have.been.calledWith('removing');
64
+ });
65
+
66
+ it('removes removing class from connections when isRemoving is false', () => {
67
+ const mockConnections = [
68
+ { id: 'conn1', removeClass: stub() },
69
+ { id: 'conn2', removeClass: stub() }
70
+ ];
71
+
72
+ (plumber as any).jsPlumb.getConnections = stub().returns(mockConnections);
73
+
74
+ const result = plumber.setConnectionRemovingState('test-exit', false);
75
+ expect(result).to.be.true;
76
+ expect(mockConnections[0].removeClass).to.have.been.calledWith(
77
+ 'removing'
78
+ );
79
+ expect(mockConnections[1].removeClass).to.have.been.calledWith(
80
+ 'removing'
81
+ );
82
+ });
83
+ });
84
+
85
+ describe('connectIds and processPendingConnections', () => {
86
+ it('adds connection to pending connections', () => {
87
+ // Call connectIds which should add to pending connections
88
+ plumber.connectIds('test-node', 'test-from', 'test-to');
89
+
90
+ // Verify pendingConnections has the new connection
91
+ expect((plumber as any).pendingConnections.length).to.equal(1);
92
+
93
+ // Advance timer to trigger the timeout
94
+ clock.tick(51); // Just past the 50ms timeout
95
+
96
+ // Now the batch should have been called
97
+ expect((plumber as any).jsPlumb.batch).to.have.been.called;
98
+ expect((plumber as any).jsPlumb.addEndpoint).to.have.been.called;
99
+ expect((plumber as any).jsPlumb.connect).to.have.been.called;
100
+ });
101
+
102
+ it('clears previous timeout when called multiple times', () => {
103
+ // Set up spies for window.setTimeout and window.clearTimeout instead of global
104
+ const clearTimeoutSpy = stub(window, 'clearTimeout');
105
+ const setTimeoutSpy = stub(window, 'setTimeout').returns(123 as any);
106
+
107
+ // Call twice
108
+ plumber.processPendingConnections();
109
+ plumber.processPendingConnections();
110
+
111
+ // Should have called clearTimeout once and setTimeout twice
112
+ expect(clearTimeoutSpy).to.have.been.calledOnce;
113
+ expect(setTimeoutSpy).to.have.been.calledTwice;
114
+
115
+ // Clean up
116
+ clearTimeoutSpy.restore();
117
+ setTimeoutSpy.restore();
118
+ });
119
+ });
120
+
121
+ describe('removeExitConnection', () => {
122
+ it('removes connections for an exit', () => {
123
+ const mockConnections = [{ id: 'conn1' }, { id: 'conn2' }];
124
+ (plumber as any).jsPlumb.getConnections = stub().returns(mockConnections);
125
+
126
+ const result = plumber.removeExitConnection('test-exit');
127
+
128
+ expect(result).to.be.true;
129
+ expect((plumber as any).jsPlumb.deleteConnection).to.have.been
130
+ .calledTwice;
131
+ expect((plumber as any).jsPlumb.removeAllEndpoints).to.have.been.called;
132
+ });
133
+
134
+ it('returns false when no connections exist', () => {
135
+ (plumber as any).jsPlumb.getConnections = stub().returns([]);
136
+
137
+ const result = plumber.removeExitConnection('test-exit');
138
+
139
+ expect(result).to.be.false;
140
+ });
141
+ });
142
+ });