@nyaruka/temba-components 0.141.1 → 0.142.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/static/svg/index.svg +1 -1
  3. package/dist/temba-components.js +849 -655
  4. package/dist/temba-components.js.map +1 -1
  5. package/out-tsc/src/Icons.js +3 -1
  6. package/out-tsc/src/Icons.js.map +1 -1
  7. package/out-tsc/src/display/Button.js +2 -2
  8. package/out-tsc/src/display/Button.js.map +1 -1
  9. package/out-tsc/src/display/FloatingTab.js.map +1 -1
  10. package/out-tsc/src/flow/CanvasMenu.js +24 -1
  11. package/out-tsc/src/flow/CanvasMenu.js.map +1 -1
  12. package/out-tsc/src/flow/CanvasNode.js +7 -2
  13. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  14. package/out-tsc/src/flow/Editor.js +654 -66
  15. package/out-tsc/src/flow/Editor.js.map +1 -1
  16. package/out-tsc/src/flow/NodeEditor.js +8 -5
  17. package/out-tsc/src/flow/NodeEditor.js.map +1 -1
  18. package/out-tsc/src/flow/Plumber.js +40 -28
  19. package/out-tsc/src/flow/Plumber.js.map +1 -1
  20. package/out-tsc/src/flow/actions/send_msg.js +2 -1
  21. package/out-tsc/src/flow/actions/send_msg.js.map +1 -1
  22. package/out-tsc/src/flow/nodes/wait_for_response.js +1 -1
  23. package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
  24. package/out-tsc/src/flow/reflow.js +393 -0
  25. package/out-tsc/src/flow/reflow.js.map +1 -0
  26. package/out-tsc/src/flow/types.js.map +1 -1
  27. package/out-tsc/src/flow/utils.js +18 -3
  28. package/out-tsc/src/flow/utils.js.map +1 -1
  29. package/out-tsc/src/form/Compose.js +5 -0
  30. package/out-tsc/src/form/Compose.js.map +1 -1
  31. package/out-tsc/src/form/FieldRenderer.js +1 -3
  32. package/out-tsc/src/form/FieldRenderer.js.map +1 -1
  33. package/out-tsc/src/layout/Dialog.js +2 -0
  34. package/out-tsc/src/layout/Dialog.js.map +1 -1
  35. package/out-tsc/src/list/SortableList.js +39 -19
  36. package/out-tsc/src/list/SortableList.js.map +1 -1
  37. package/out-tsc/test/temba-canvas-menu.test.js +44 -0
  38. package/out-tsc/test/temba-canvas-menu.test.js.map +1 -1
  39. package/out-tsc/test/temba-flow-collision.test.js +25 -0
  40. package/out-tsc/test/temba-flow-collision.test.js.map +1 -1
  41. package/out-tsc/test/temba-flow-editor-zoom.test.js +491 -0
  42. package/out-tsc/test/temba-flow-editor-zoom.test.js.map +1 -0
  43. package/out-tsc/test/temba-flow-editor.test.js +145 -1
  44. package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
  45. package/out-tsc/test/temba-flow-node-drag.test.js +123 -0
  46. package/out-tsc/test/temba-flow-node-drag.test.js.map +1 -1
  47. package/out-tsc/test/temba-flow-plumber.test.js +31 -0
  48. package/out-tsc/test/temba-flow-plumber.test.js.map +1 -1
  49. package/out-tsc/test/temba-flow-reflow.test.js +472 -0
  50. package/out-tsc/test/temba-flow-reflow.test.js.map +1 -0
  51. package/out-tsc/test/temba-sortable-list.test.js +93 -0
  52. package/out-tsc/test/temba-sortable-list.test.js.map +1 -1
  53. package/package.json +1 -1
  54. package/screenshots/truth/actions/add_contact_groups/editor/descriptive-group-names.png +0 -0
  55. package/screenshots/truth/actions/add_contact_groups/editor/long-group-names.png +0 -0
  56. package/screenshots/truth/actions/add_contact_groups/editor/many-groups.png +0 -0
  57. package/screenshots/truth/actions/add_contact_urn/editor/expression-facebook.png +0 -0
  58. package/screenshots/truth/actions/add_contact_urn/editor/expression-phone.png +0 -0
  59. package/screenshots/truth/actions/add_contact_urn/editor/facebook-id.png +0 -0
  60. package/screenshots/truth/actions/add_contact_urn/editor/instagram-handle.png +0 -0
  61. package/screenshots/truth/actions/add_contact_urn/editor/line-id.png +0 -0
  62. package/screenshots/truth/actions/add_contact_urn/editor/phone-number.png +0 -0
  63. package/screenshots/truth/actions/add_contact_urn/editor/telegram-id.png +0 -0
  64. package/screenshots/truth/actions/add_contact_urn/editor/viber-id.png +0 -0
  65. package/screenshots/truth/actions/add_contact_urn/editor/wechat-id.png +0 -0
  66. package/screenshots/truth/actions/add_contact_urn/editor/whatsapp.png +0 -0
  67. package/screenshots/truth/actions/enter_flow/editor/basic-flow.png +0 -0
  68. package/screenshots/truth/actions/enter_flow/editor/long-flow-name.png +0 -0
  69. package/screenshots/truth/actions/remove_contact_groups/editor/long-descriptive-group-names.png +0 -0
  70. package/screenshots/truth/actions/remove_contact_groups/editor/many-groups.png +0 -0
  71. package/screenshots/truth/actions/say_msg/editor/multiline-text.png +0 -0
  72. package/screenshots/truth/actions/say_msg/editor/simple-text.png +0 -0
  73. package/screenshots/truth/actions/say_msg/editor/text-with-audio-url.png +0 -0
  74. package/screenshots/truth/actions/send_broadcast/editor/contacts-only.png +0 -0
  75. package/screenshots/truth/actions/send_broadcast/editor/groups-and-contacts.png +0 -0
  76. package/screenshots/truth/actions/send_broadcast/editor/groups-only.png +0 -0
  77. package/screenshots/truth/actions/send_broadcast/editor/many-groups.png +0 -0
  78. package/screenshots/truth/actions/send_broadcast/editor/multiline-text.png +0 -0
  79. package/screenshots/truth/actions/send_email/editor/empty-body.png +0 -0
  80. package/screenshots/truth/actions/send_email/editor/empty-subject.png +0 -0
  81. package/screenshots/truth/actions/send_email/editor/long-subject.png +0 -0
  82. package/screenshots/truth/actions/send_email/editor/multiline-body.png +0 -0
  83. package/screenshots/truth/actions/send_email/editor/multiple-recipients.png +0 -0
  84. package/screenshots/truth/actions/send_email/editor/simple-email.png +0 -0
  85. package/screenshots/truth/actions/send_email/editor/with-expressions.png +0 -0
  86. package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
  87. package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
  88. package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
  89. package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
  90. package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
  91. package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
  92. package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
  93. package/screenshots/truth/actions/set_contact_channel/editor/sms-channel.png +0 -0
  94. package/screenshots/truth/actions/set_contact_channel/editor/whatsapp-channel.png +0 -0
  95. package/screenshots/truth/actions/set_contact_field/editor/clear-value.png +0 -0
  96. package/screenshots/truth/actions/set_contact_field/editor/set-value.png +0 -0
  97. package/screenshots/truth/actions/set_contact_language/editor/english.png +0 -0
  98. package/screenshots/truth/actions/set_contact_language/editor/french.png +0 -0
  99. package/screenshots/truth/actions/set_contact_status/editor/active.png +0 -0
  100. package/screenshots/truth/actions/set_contact_status/editor/archived.png +0 -0
  101. package/screenshots/truth/actions/set_contact_status/editor/blocked.png +0 -0
  102. package/screenshots/truth/actions/set_run_result/editor/expression-value.png +0 -0
  103. package/screenshots/truth/actions/set_run_result/editor/with-category.png +0 -0
  104. package/screenshots/truth/actions/start_session/editor/contact-query.png +0 -0
  105. package/screenshots/truth/actions/start_session/editor/contacts-only.png +0 -0
  106. package/screenshots/truth/actions/start_session/editor/create-contact.png +0 -0
  107. package/screenshots/truth/actions/start_session/editor/groups-and-contacts.png +0 -0
  108. package/screenshots/truth/actions/start_session/editor/groups-only.png +0 -0
  109. package/screenshots/truth/actions/start_session/editor/many-recipients.png +0 -0
  110. package/screenshots/truth/list/fields-dragging.png +0 -0
  111. package/screenshots/truth/list/sortable-dragging.png +0 -0
  112. package/screenshots/truth/modax/simple.png +0 -0
  113. package/screenshots/truth/nodes/split_by_llm/editor/information-extraction.png +0 -0
  114. package/screenshots/truth/nodes/split_by_llm/editor/sentiment-analysis.png +0 -0
  115. package/screenshots/truth/nodes/split_by_llm/editor/summarization.png +0 -0
  116. package/screenshots/truth/nodes/split_by_llm/editor/translation-task.png +0 -0
  117. package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
  118. package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
  119. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  120. package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
  121. package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
  122. package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
  123. package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
  124. package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
  125. package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
  126. package/screenshots/truth/nodes/wait_for_dial/editor/dial-with-limits.png +0 -0
  127. package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
  128. package/screenshots/truth/nodes/wait_for_digits/editor/digits-with-rules.png +0 -0
  129. package/screenshots/truth/nodes/wait_for_menu/editor/menu-with-digits.png +0 -0
  130. package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
  131. package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
  132. package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
  133. package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
  134. package/src/Icons.ts +3 -1
  135. package/src/display/Button.ts +2 -2
  136. package/src/display/FloatingTab.ts +1 -1
  137. package/src/flow/CanvasMenu.ts +28 -3
  138. package/src/flow/CanvasNode.ts +7 -2
  139. package/src/flow/Editor.ts +755 -75
  140. package/src/flow/NodeEditor.ts +8 -4
  141. package/src/flow/Plumber.ts +65 -35
  142. package/src/flow/actions/send_msg.ts +2 -1
  143. package/src/flow/nodes/wait_for_response.ts +1 -1
  144. package/src/flow/reflow.ts +534 -0
  145. package/src/flow/types.ts +1 -0
  146. package/src/flow/utils.ts +19 -3
  147. package/src/form/Compose.ts +5 -0
  148. package/src/form/FieldRenderer.ts +1 -3
  149. package/src/layout/Dialog.ts +2 -0
  150. package/src/list/SortableList.ts +40 -19
  151. package/static/svg/index.svg +1 -1
  152. package/static/svg/work/traced/expand-06.svg +1 -0
  153. package/static/svg/work/used/expand-06.svg +3 -0
  154. package/test/temba-canvas-menu.test.ts +55 -0
  155. package/test/temba-flow-collision.test.ts +31 -0
  156. package/test/temba-flow-editor-zoom.test.ts +583 -0
  157. package/test/temba-flow-editor.test.ts +187 -1
  158. package/test/temba-flow-node-drag.test.ts +171 -0
  159. package/test/temba-flow-plumber.test.ts +38 -0
  160. package/test/temba-flow-reflow.test.ts +703 -0
  161. package/test/temba-sortable-list.test.ts +120 -0
  162. package/screenshots/truth/actions/call_llm/editor/information-extraction.png +0 -0
  163. package/screenshots/truth/actions/call_llm/editor/sentiment-analysis.png +0 -0
  164. package/screenshots/truth/actions/call_llm/editor/summarization.png +0 -0
  165. package/screenshots/truth/actions/call_llm/editor/translation-task.png +0 -0
  166. package/screenshots/truth/actions/call_llm/render/information-extraction.png +0 -0
  167. package/screenshots/truth/actions/call_llm/render/sentiment-analysis.png +0 -0
  168. package/screenshots/truth/actions/call_llm/render/summarization.png +0 -0
  169. package/screenshots/truth/actions/call_llm/render/translation-task.png +0 -0
  170. package/screenshots/truth/actions/send_broadcast/editor/with-attachments.png +0 -0
  171. package/screenshots/truth/actions/send_broadcast/render/with-attachments.png +0 -0
  172. package/screenshots/truth/compose/attachments-with-failures.png +0 -0
  173. package/screenshots/truth/compose/attachments-with-files-and-failures.png +0 -0
  174. package/screenshots/truth/contacts/tickets-assignment.png +0 -0
  175. package/screenshots/truth/contacts/tickets.png +0 -0
  176. package/screenshots/truth/flow/editor-basic.png +0 -0
  177. package/screenshots/truth/formfield/markdown-errors.png +0 -0
  178. package/screenshots/truth/formfield/no-errors.png +0 -0
  179. package/screenshots/truth/formfield/plain-text-errors.png +0 -0
  180. package/screenshots/truth/formfield/widget-only-markdown-errors.png +0 -0
  181. package/screenshots/truth/omnibox/selected.png +0 -0
  182. package/screenshots/truth/select/enabled-multi-selection.png +0 -0
  183. package/screenshots/truth/select/endpoint-initial-value-updated.png +0 -0
  184. package/screenshots/truth/select/endpoint-initial-value.png +0 -0
  185. package/screenshots/truth/select/initial-value.png +0 -0
  186. package/screenshots/truth/select/multi-reorder-final.png +0 -0
  187. package/screenshots/truth/select/multi-reorder-initial.png +0 -0
  188. package/screenshots/truth/select/selected-multi-test.png +0 -0
  189. package/screenshots/truth/select/value-initial.png +0 -0
  190. package/screenshots/truth/wait-for-response/rules-editor.png +0 -0
  191. package/screenshots/truth/wait-for-response/timeout-editor-unchecked.png +0 -0
  192. package/screenshots/truth/wait-for-response/timeout-editor.png +0 -0
  193. package/screenshots/truth/webchat/connecting-state.png +0 -0
@@ -1,6 +1,7 @@
1
1
  import { expect, fixture, oneEvent } from '@open-wc/testing';
2
2
  import { html } from 'lit';
3
3
  import { CustomEventType } from '../src/interfaces';
4
+ import { SortableList } from '../src/list/SortableList';
4
5
  import { assertScreenshot, getClip } from './utils.test';
5
6
  import { useFakeTimers } from 'sinon';
6
7
  const BORING_LIST = html `
@@ -171,5 +172,97 @@ describe('temba-sortable-list', () => {
171
172
  const dragStopEvent = await dragStop;
172
173
  expect(dragStopEvent.detail.isExternal).to.be.true;
173
174
  });
175
+ describe('zoom-aware dimensions', () => {
176
+ it('stores layout dimensions on mousedown via offsetWidth/offsetHeight', async () => {
177
+ const list = await createSorter(BORING_LIST);
178
+ await list.updateComplete;
179
+ const bounds = list.getBoundingClientRect();
180
+ // Start drag
181
+ await moveMouse(bounds.left + 20, bounds.bottom - 10);
182
+ await mouseDown();
183
+ // originalLayoutSize should be set using offsetWidth/offsetHeight
184
+ expect(list.originalLayoutSize).to.not.be.null;
185
+ // At zoom=1.0, layout size equals viewport size
186
+ expect(list.originalLayoutSize.width).to.equal(list.originalElementRect.width);
187
+ expect(list.originalLayoutSize.height).to.equal(list.originalElementRect.height);
188
+ await mouseUp();
189
+ clock.runAll();
190
+ });
191
+ it('ghost uses scale(1.03) without ancestor transform', async () => {
192
+ const list = await createSorter(BORING_LIST);
193
+ await list.updateComplete;
194
+ const bounds = list.getBoundingClientRect();
195
+ // Start drag past threshold to create ghost
196
+ await moveMouse(bounds.left + 20, bounds.bottom - 10);
197
+ await mouseDown();
198
+ await moveMouse(bounds.left + 30, bounds.bottom - 10);
199
+ clock.runAll();
200
+ expect(list.ghostElement).to.not.be.null;
201
+ expect(list.ghostElement.style.transform).to.equal('scale(1.03)');
202
+ // transformOrigin should NOT be set to '0 0' when there's no ancestor scale
203
+ expect(list.ghostElement.style.transformOrigin).to.not.equal('0 0');
204
+ await mouseUp();
205
+ clock.runAll();
206
+ });
207
+ it('detects ancestor scale and applies it to ghost', () => {
208
+ // Unit test the ghost scaling logic directly:
209
+ // When originalElementRect (viewport) differs from originalLayoutSize (layout),
210
+ // the ancestor scale is detected and applied to the ghost.
211
+ const list = new SortableList();
212
+ // Simulate being inside a container with transform: scale(0.5)
213
+ // Layout dimensions: 100x20, viewport dimensions: 50x10
214
+ list.originalElementRect = {
215
+ width: 50,
216
+ height: 10,
217
+ left: 0,
218
+ top: 0,
219
+ right: 50,
220
+ bottom: 10,
221
+ x: 0,
222
+ y: 0,
223
+ toJSON: () => ({})
224
+ };
225
+ list.originalLayoutSize = { width: 100, height: 20 };
226
+ // Calculate ancestor scale the same way the component does
227
+ const ancestorScale = list.originalLayoutSize.width > 0
228
+ ? list.originalElementRect.width / list.originalLayoutSize.width
229
+ : 1;
230
+ const hasAncestorScale = Math.abs(ancestorScale - 1) > 0.001;
231
+ expect(hasAncestorScale).to.be.true;
232
+ expect(ancestorScale).to.equal(0.5);
233
+ // The ghost would get: transform: scale(0.5 * 1.03) = scale(0.515)
234
+ const expectedScale = ancestorScale * 1.03;
235
+ expect(expectedScale).to.be.closeTo(0.515, 0.001);
236
+ });
237
+ it('uses originalLayoutSize for placeholder sizing', () => {
238
+ // Verify that the component stores separate layout vs viewport dimensions
239
+ const list = new SortableList();
240
+ // At zoom=1.0, both should be the same
241
+ const mockRect = {
242
+ width: 100,
243
+ height: 20,
244
+ left: 0,
245
+ top: 0,
246
+ right: 100,
247
+ bottom: 20,
248
+ x: 0,
249
+ y: 0,
250
+ toJSON: () => ({})
251
+ };
252
+ list.originalElementRect = mockRect;
253
+ list.originalLayoutSize = { width: 100, height: 20 };
254
+ // At zoom=1.0, they match
255
+ expect(list.originalLayoutSize.width).to.equal(list.originalElementRect.width);
256
+ // At zoom=0.5, viewport rect would be half but layout stays the same
257
+ list.originalElementRect = {
258
+ ...mockRect,
259
+ width: 50,
260
+ height: 10
261
+ };
262
+ // Layout size is independent of zoom
263
+ expect(list.originalLayoutSize.width).to.equal(100);
264
+ expect(list.originalLayoutSize.height).to.equal(20);
265
+ });
266
+ });
174
267
  });
175
268
  //# sourceMappingURL=temba-sortable-list.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"temba-sortable-list.test.js","sourceRoot":"","sources":["../../test/temba-sortable-list.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, fixture, oneEvent } from '@open-wc/testing';\nimport { html, TemplateResult } from 'lit';\nimport { CustomEventType } from '../src/interfaces';\nimport { SortableList } from '../src/list/SortableList';\nimport { assertScreenshot, getClip } from './utils.test';\nimport Sinon, { useFakeTimers } from 'sinon';\n\nconst BORING_LIST = html`\n <temba-sortable-list>\n <style>\n .sortable {\n display: flex;\n align-items: center;\n justify-content: center;\n text-align: center;\n height: 20px;\n }\n </style>\n <div class=\"sortable\" id=\"chicken\" style=\"\">Chicken</div>\n <div class=\"sortable\" id=\"fish\">Fish</div>\n </temba-sortable-list>\n`;\n\nconst HORIZONTAL_LIST = html`\n <temba-sortable-list horizontal>\n <style>\n .sortable {\n display: flex;\n align-items: center;\n justify-content: center;\n text-align: center;\n height: 20px;\n width: 50px;\n }\n </style>\n <div class=\"sortable\" id=\"red\">Red</div>\n <div class=\"sortable\" id=\"blue\">Blue</div>\n <div class=\"sortable\" id=\"green\">Green</div>\n </temba-sortable-list>\n`;\n\nconst createSorter = async (def: TemplateResult) => {\n const parentNode = document.createElement('div');\n parentNode.setAttribute('style', 'width: 100px;');\n return (await fixture(def, { parentNode })) as SortableList;\n};\n\ndescribe('temba-sortable-list', () => {\n let clock: Sinon.SinonFakeTimers;\n beforeEach(function () {\n clock = useFakeTimers();\n clock.runAll();\n });\n\n afterEach(function () {\n clock.restore();\n });\n\n it('renders default', async () => {\n const list: SortableList = await createSorter(BORING_LIST);\n await assertScreenshot('list/sortable', getClip(list));\n });\n\n it('can get ids of sortable elements', async () => {\n const list: SortableList = await createSorter(BORING_LIST);\n await list.updateComplete;\n\n const ids = list.getIds();\n expect(ids).to.deep.equal(['chicken', 'fish']);\n });\n\n it('works with horizontal layout', async () => {\n const list: SortableList = await createSorter(HORIZONTAL_LIST);\n await list.updateComplete;\n\n const ids = list.getIds();\n expect(ids).to.deep.equal(['red', 'blue', 'green']);\n\n // Test horizontal drag behavior\n const bounds = list.getBoundingClientRect();\n const orderChanged = oneEvent(list, CustomEventType.OrderChanged, false);\n\n // Drag the first item (red) to after the second item (blue)\n await moveMouse(bounds.left + 10, bounds.top + 10);\n await mouseDown();\n await moveMouse(bounds.left + 80, bounds.top + 10);\n await mouseUp();\n clock.runAll();\n\n const orderEvent = await orderChanged;\n expect(orderEvent.detail).to.deep.equal({\n swap: [0, 2]\n });\n });\n\n it('handles prepareGhost callback', async () => {\n const list: SortableList = await createSorter(BORING_LIST);\n let ghostPrepared = false;\n\n list.prepareGhost = (ghost: HTMLElement) => {\n ghostPrepared = true;\n ghost.style.backgroundColor = 'red';\n };\n\n const bounds = list.getBoundingClientRect();\n\n // Start dragging to trigger ghost creation\n await moveMouse(bounds.left + 20, bounds.bottom - 10);\n await mouseDown();\n await moveMouse(bounds.left + 30, bounds.bottom - 10);\n\n expect(ghostPrepared).to.be.true;\n\n // Clean up\n await mouseUp();\n clock.runAll();\n });\n\n it('drags', async () => {\n const list: SortableList = await createSorter(BORING_LIST);\n const updated = oneEvent(list, 'change', false);\n\n const bounds = list.getBoundingClientRect();\n\n await moveMouse(bounds.left + 20, bounds.bottom - 10);\n await mouseDown();\n await moveMouse(bounds.left + 20, bounds.top + 5);\n\n // should be hovered\n await assertScreenshot('list/sortable-dragging', getClip(list));\n\n // now lets drop - this will fire the order changed event\n const orderChanged = oneEvent(list, CustomEventType.OrderChanged, false);\n await mouseUp();\n clock.runAll();\n await list.updateComplete;\n clock.runAll();\n\n // we should fire an order changed event on drop\n const orderEvent = await orderChanged;\n expect(orderEvent.detail).to.deep.equal({\n swap: [1, 0]\n });\n\n await assertScreenshot('list/sortable-dropped', getClip(list));\n\n // we should fire a change event\n const changeEvent = await updated;\n expect(changeEvent.type).to.equal('change');\n });\n\n it('detects external drag when dragging outside container', async () => {\n const list: SortableList = await createSorter(BORING_LIST);\n list.externalDrag = true;\n await list.updateComplete;\n\n const bounds = list.getBoundingClientRect();\n\n // track external drag events\n let externalDragFired = false;\n let internalDragFired = false;\n\n list.addEventListener(CustomEventType.DragExternal, () => {\n externalDragFired = true;\n });\n\n list.addEventListener(CustomEventType.DragInternal, () => {\n internalDragFired = true;\n });\n\n // start dragging an item\n await moveMouse(bounds.left + 20, bounds.bottom - 10);\n await mouseDown();\n\n // drag outside the container (far to the right)\n await moveMouse(bounds.right + 100, bounds.top + 10);\n clock.runAll();\n\n // should have fired external drag event\n expect(externalDragFired).to.be.true;\n\n // drag back inside\n externalDragFired = false; // reset\n await moveMouse(bounds.left + 20, bounds.top + 10);\n clock.runAll();\n\n // should have fired internal drag event\n expect(internalDragFired).to.be.true;\n\n // clean up\n await mouseUp();\n clock.runAll();\n });\n\n it('fires DragStop with isExternal=true when dropped outside container', async () => {\n const list: SortableList = await createSorter(BORING_LIST);\n list.externalDrag = true;\n await list.updateComplete;\n\n const bounds = list.getBoundingClientRect();\n\n // start dragging an item\n await moveMouse(bounds.left + 20, bounds.bottom - 10);\n await mouseDown();\n\n // drag outside the container\n await moveMouse(bounds.right + 100, bounds.top + 10);\n clock.runAll();\n\n // listen for drag stop event\n const dragStop = oneEvent(list, CustomEventType.DragStop, false);\n\n // drop outside\n await mouseUp();\n clock.runAll();\n\n const dragStopEvent = await dragStop;\n expect(dragStopEvent.detail.isExternal).to.be.true;\n });\n});\n"]}
1
+ {"version":3,"file":"temba-sortable-list.test.js","sourceRoot":"","sources":["../../test/temba-sortable-list.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, fixture, oneEvent } from '@open-wc/testing';\nimport { html, TemplateResult } from 'lit';\nimport { CustomEventType } from '../src/interfaces';\nimport { SortableList } from '../src/list/SortableList';\nimport { assertScreenshot, getClip } from './utils.test';\nimport Sinon, { useFakeTimers } from 'sinon';\n\nconst BORING_LIST = html`\n <temba-sortable-list>\n <style>\n .sortable {\n display: flex;\n align-items: center;\n justify-content: center;\n text-align: center;\n height: 20px;\n }\n </style>\n <div class=\"sortable\" id=\"chicken\" style=\"\">Chicken</div>\n <div class=\"sortable\" id=\"fish\">Fish</div>\n </temba-sortable-list>\n`;\n\nconst HORIZONTAL_LIST = html`\n <temba-sortable-list horizontal>\n <style>\n .sortable {\n display: flex;\n align-items: center;\n justify-content: center;\n text-align: center;\n height: 20px;\n width: 50px;\n }\n </style>\n <div class=\"sortable\" id=\"red\">Red</div>\n <div class=\"sortable\" id=\"blue\">Blue</div>\n <div class=\"sortable\" id=\"green\">Green</div>\n </temba-sortable-list>\n`;\n\nconst createSorter = async (def: TemplateResult) => {\n const parentNode = document.createElement('div');\n parentNode.setAttribute('style', 'width: 100px;');\n return (await fixture(def, { parentNode })) as SortableList;\n};\n\ndescribe('temba-sortable-list', () => {\n let clock: Sinon.SinonFakeTimers;\n beforeEach(function () {\n clock = useFakeTimers();\n clock.runAll();\n });\n\n afterEach(function () {\n clock.restore();\n });\n\n it('renders default', async () => {\n const list: SortableList = await createSorter(BORING_LIST);\n await assertScreenshot('list/sortable', getClip(list));\n });\n\n it('can get ids of sortable elements', async () => {\n const list: SortableList = await createSorter(BORING_LIST);\n await list.updateComplete;\n\n const ids = list.getIds();\n expect(ids).to.deep.equal(['chicken', 'fish']);\n });\n\n it('works with horizontal layout', async () => {\n const list: SortableList = await createSorter(HORIZONTAL_LIST);\n await list.updateComplete;\n\n const ids = list.getIds();\n expect(ids).to.deep.equal(['red', 'blue', 'green']);\n\n // Test horizontal drag behavior\n const bounds = list.getBoundingClientRect();\n const orderChanged = oneEvent(list, CustomEventType.OrderChanged, false);\n\n // Drag the first item (red) to after the second item (blue)\n await moveMouse(bounds.left + 10, bounds.top + 10);\n await mouseDown();\n await moveMouse(bounds.left + 80, bounds.top + 10);\n await mouseUp();\n clock.runAll();\n\n const orderEvent = await orderChanged;\n expect(orderEvent.detail).to.deep.equal({\n swap: [0, 2]\n });\n });\n\n it('handles prepareGhost callback', async () => {\n const list: SortableList = await createSorter(BORING_LIST);\n let ghostPrepared = false;\n\n list.prepareGhost = (ghost: HTMLElement) => {\n ghostPrepared = true;\n ghost.style.backgroundColor = 'red';\n };\n\n const bounds = list.getBoundingClientRect();\n\n // Start dragging to trigger ghost creation\n await moveMouse(bounds.left + 20, bounds.bottom - 10);\n await mouseDown();\n await moveMouse(bounds.left + 30, bounds.bottom - 10);\n\n expect(ghostPrepared).to.be.true;\n\n // Clean up\n await mouseUp();\n clock.runAll();\n });\n\n it('drags', async () => {\n const list: SortableList = await createSorter(BORING_LIST);\n const updated = oneEvent(list, 'change', false);\n\n const bounds = list.getBoundingClientRect();\n\n await moveMouse(bounds.left + 20, bounds.bottom - 10);\n await mouseDown();\n await moveMouse(bounds.left + 20, bounds.top + 5);\n\n // should be hovered\n await assertScreenshot('list/sortable-dragging', getClip(list));\n\n // now lets drop - this will fire the order changed event\n const orderChanged = oneEvent(list, CustomEventType.OrderChanged, false);\n await mouseUp();\n clock.runAll();\n await list.updateComplete;\n clock.runAll();\n\n // we should fire an order changed event on drop\n const orderEvent = await orderChanged;\n expect(orderEvent.detail).to.deep.equal({\n swap: [1, 0]\n });\n\n await assertScreenshot('list/sortable-dropped', getClip(list));\n\n // we should fire a change event\n const changeEvent = await updated;\n expect(changeEvent.type).to.equal('change');\n });\n\n it('detects external drag when dragging outside container', async () => {\n const list: SortableList = await createSorter(BORING_LIST);\n list.externalDrag = true;\n await list.updateComplete;\n\n const bounds = list.getBoundingClientRect();\n\n // track external drag events\n let externalDragFired = false;\n let internalDragFired = false;\n\n list.addEventListener(CustomEventType.DragExternal, () => {\n externalDragFired = true;\n });\n\n list.addEventListener(CustomEventType.DragInternal, () => {\n internalDragFired = true;\n });\n\n // start dragging an item\n await moveMouse(bounds.left + 20, bounds.bottom - 10);\n await mouseDown();\n\n // drag outside the container (far to the right)\n await moveMouse(bounds.right + 100, bounds.top + 10);\n clock.runAll();\n\n // should have fired external drag event\n expect(externalDragFired).to.be.true;\n\n // drag back inside\n externalDragFired = false; // reset\n await moveMouse(bounds.left + 20, bounds.top + 10);\n clock.runAll();\n\n // should have fired internal drag event\n expect(internalDragFired).to.be.true;\n\n // clean up\n await mouseUp();\n clock.runAll();\n });\n\n it('fires DragStop with isExternal=true when dropped outside container', async () => {\n const list: SortableList = await createSorter(BORING_LIST);\n list.externalDrag = true;\n await list.updateComplete;\n\n const bounds = list.getBoundingClientRect();\n\n // start dragging an item\n await moveMouse(bounds.left + 20, bounds.bottom - 10);\n await mouseDown();\n\n // drag outside the container\n await moveMouse(bounds.right + 100, bounds.top + 10);\n clock.runAll();\n\n // listen for drag stop event\n const dragStop = oneEvent(list, CustomEventType.DragStop, false);\n\n // drop outside\n await mouseUp();\n clock.runAll();\n\n const dragStopEvent = await dragStop;\n expect(dragStopEvent.detail.isExternal).to.be.true;\n });\n\n describe('zoom-aware dimensions', () => {\n it('stores layout dimensions on mousedown via offsetWidth/offsetHeight', async () => {\n const list: SortableList = await createSorter(BORING_LIST);\n await list.updateComplete;\n\n const bounds = list.getBoundingClientRect();\n\n // Start drag\n await moveMouse(bounds.left + 20, bounds.bottom - 10);\n await mouseDown();\n\n // originalLayoutSize should be set using offsetWidth/offsetHeight\n expect(list.originalLayoutSize).to.not.be.null;\n // At zoom=1.0, layout size equals viewport size\n expect(list.originalLayoutSize.width).to.equal(\n list.originalElementRect.width\n );\n expect(list.originalLayoutSize.height).to.equal(\n list.originalElementRect.height\n );\n\n await mouseUp();\n clock.runAll();\n });\n\n it('ghost uses scale(1.03) without ancestor transform', async () => {\n const list: SortableList = await createSorter(BORING_LIST);\n await list.updateComplete;\n\n const bounds = list.getBoundingClientRect();\n\n // Start drag past threshold to create ghost\n await moveMouse(bounds.left + 20, bounds.bottom - 10);\n await mouseDown();\n await moveMouse(bounds.left + 30, bounds.bottom - 10);\n clock.runAll();\n\n expect(list.ghostElement).to.not.be.null;\n expect(list.ghostElement.style.transform).to.equal('scale(1.03)');\n // transformOrigin should NOT be set to '0 0' when there's no ancestor scale\n expect(list.ghostElement.style.transformOrigin).to.not.equal('0 0');\n\n await mouseUp();\n clock.runAll();\n });\n\n it('detects ancestor scale and applies it to ghost', () => {\n // Unit test the ghost scaling logic directly:\n // When originalElementRect (viewport) differs from originalLayoutSize (layout),\n // the ancestor scale is detected and applied to the ghost.\n const list = new SortableList();\n\n // Simulate being inside a container with transform: scale(0.5)\n // Layout dimensions: 100x20, viewport dimensions: 50x10\n list.originalElementRect = {\n width: 50,\n height: 10,\n left: 0,\n top: 0,\n right: 50,\n bottom: 10,\n x: 0,\n y: 0,\n toJSON: () => ({})\n } as DOMRect;\n list.originalLayoutSize = { width: 100, height: 20 };\n\n // Calculate ancestor scale the same way the component does\n const ancestorScale =\n list.originalLayoutSize.width > 0\n ? list.originalElementRect.width / list.originalLayoutSize.width\n : 1;\n const hasAncestorScale = Math.abs(ancestorScale - 1) > 0.001;\n\n expect(hasAncestorScale).to.be.true;\n expect(ancestorScale).to.equal(0.5);\n\n // The ghost would get: transform: scale(0.5 * 1.03) = scale(0.515)\n const expectedScale = ancestorScale * 1.03;\n expect(expectedScale).to.be.closeTo(0.515, 0.001);\n });\n\n it('uses originalLayoutSize for placeholder sizing', () => {\n // Verify that the component stores separate layout vs viewport dimensions\n const list = new SortableList();\n\n // At zoom=1.0, both should be the same\n const mockRect = {\n width: 100,\n height: 20,\n left: 0,\n top: 0,\n right: 100,\n bottom: 20,\n x: 0,\n y: 0,\n toJSON: () => ({})\n } as DOMRect;\n\n list.originalElementRect = mockRect;\n list.originalLayoutSize = { width: 100, height: 20 };\n\n // At zoom=1.0, they match\n expect(list.originalLayoutSize.width).to.equal(\n list.originalElementRect.width\n );\n\n // At zoom=0.5, viewport rect would be half but layout stays the same\n list.originalElementRect = {\n ...mockRect,\n width: 50,\n height: 10\n } as DOMRect;\n\n // Layout size is independent of zoom\n expect(list.originalLayoutSize.width).to.equal(100);\n expect(list.originalLayoutSize.height).to.equal(20);\n });\n });\n});\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nyaruka/temba-components",
3
- "version": "0.141.1",
3
+ "version": "0.142.0",
4
4
  "description": "Web components to support rapidpro and related projects",
5
5
  "author": "Nyaruka <code@nyaruka.coim>",
6
6
  "main": "dist/index.js",
Binary file
package/src/Icons.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable @typescript-eslint/no-duplicate-enum-values */
2
2
  // for cache busting we dynamically generate a fingerprint, use yarn svg to update
3
- export const SVG_FINGERPRINT = '129c04a9f99f9f4e99c436a68bc95470';
3
+ export const SVG_FINGERPRINT = 'de580a861152b5d164592ae63b65e5aa';
4
4
 
5
5
  // only icons below are included in the sprite sheet
6
6
  export enum Icon {
@@ -198,6 +198,8 @@ export enum Icon {
198
198
  video = 'video-recorder',
199
199
  webhook = 'link-external-01',
200
200
  workspace = 'folder',
201
+ zoom_fit = 'maximize-02',
202
+ zoom_in = 'expand-06',
201
203
 
202
204
  // channel types
203
205
  channel_a = 'channel-android',
@@ -46,7 +46,7 @@ export class Button extends LitElement {
46
46
  }
47
47
 
48
48
  .secondary-button:hover .button-mask {
49
- border: 1px solid var(--color-button-secondary);
49
+ border: 1px solid rgba(0, 0, 0, 0.15);
50
50
  }
51
51
 
52
52
  .button-mask:hover {
@@ -100,7 +100,7 @@ export class Button extends LitElement {
100
100
  }
101
101
 
102
102
  .secondary-button.active-button .button-mask {
103
- border: none;
103
+ border: 1px solid transparent;
104
104
  }
105
105
 
106
106
  .button-container.secondary-button.active-button:focus .button-mask {
@@ -104,7 +104,7 @@ export class FloatingTab extends RapidElement {
104
104
  FloatingTab.updateAllPositions();
105
105
  }
106
106
 
107
- private static updateAllPositions() {
107
+ public static updateAllPositions() {
108
108
  const sorted = [...FloatingTab.allTabs].sort((a, b) => a.order - b.order);
109
109
  sorted.forEach((tab, index) => {
110
110
  tab.top =
@@ -7,7 +7,7 @@ import { CustomEventType } from '../interfaces';
7
7
  * Event detail for canvas menu selection
8
8
  */
9
9
  export interface CanvasMenuSelection {
10
- action: 'sticky' | 'action' | 'split';
10
+ action: 'sticky' | 'action' | 'split' | 'reflow';
11
11
  position: { x: number; y: number };
12
12
  }
13
13
 
@@ -90,6 +90,9 @@ export class CanvasMenu extends RapidElement {
90
90
  @property({ type: Boolean })
91
91
  public showStickyNote = true;
92
92
 
93
+ @property({ type: Boolean })
94
+ public showReflow = false;
95
+
93
96
  @state()
94
97
  private clickPosition = { x: 0, y: 0 };
95
98
 
@@ -127,12 +130,14 @@ export class CanvasMenu extends RapidElement {
127
130
  x: number,
128
131
  y: number,
129
132
  clickPosition: { x: number; y: number },
130
- showStickyNote: boolean = true
133
+ showStickyNote: boolean = true,
134
+ showReflow: boolean = false
131
135
  ) {
132
136
  this.x = x;
133
137
  this.y = y;
134
138
  this.clickPosition = clickPosition;
135
139
  this.showStickyNote = showStickyNote;
140
+ this.showReflow = showReflow;
136
141
  this.open = true;
137
142
 
138
143
  // Adjust position after menu renders to ensure it fits on screen
@@ -180,7 +185,9 @@ export class CanvasMenu extends RapidElement {
180
185
  }
181
186
  }
182
187
 
183
- private handleMenuItemClick(action: 'sticky' | 'action' | 'split') {
188
+ private handleMenuItemClick(
189
+ action: 'sticky' | 'action' | 'split' | 'reflow'
190
+ ) {
184
191
  this.fireCustomEvent(CustomEventType.Selection, {
185
192
  action,
186
193
  position: this.clickPosition
@@ -238,6 +245,24 @@ export class CanvasMenu extends RapidElement {
238
245
  </div>
239
246
  `
240
247
  : ''}
248
+ ${this.showReflow
249
+ ? html`
250
+ <div class="divider"></div>
251
+
252
+ <div
253
+ class="menu-item"
254
+ @click=${() => this.handleMenuItemClick('reflow')}
255
+ >
256
+ <temba-icon name="flow" size="1.25"></temba-icon>
257
+ <div class="menu-item-content">
258
+ <div class="menu-item-title">Reflow</div>
259
+ <div class="menu-item-description">
260
+ Auto-arrange nodes in this flow
261
+ </div>
262
+ </div>
263
+ </div>
264
+ `
265
+ : ''}
241
266
  </div>
242
267
  `;
243
268
  }
@@ -6,6 +6,7 @@ import { Action, Exit, Node, NodeUI, Router } from '../store/flow-definition';
6
6
  import { property } from 'lit/decorators.js';
7
7
  import { RapidElement } from '../RapidElement';
8
8
  import { getClasses } from '../utils';
9
+ import { isRightClick } from './utils';
9
10
  import { Plumber } from './Plumber';
10
11
  import { getStore } from '../store/Store';
11
12
  import { CustomEventType } from '../interfaces';
@@ -873,8 +874,8 @@ export class CanvasNode extends RapidElement {
873
874
  const actionElement = this.querySelector(`#${actionId}`) as HTMLElement;
874
875
 
875
876
  if (actionElement) {
876
- const rect = actionElement.getBoundingClientRect();
877
- this.draggedActionHeight = rect.height;
877
+ // Use offsetHeight (unaffected by ancestor CSS transforms like zoom)
878
+ this.draggedActionHeight = actionElement.offsetHeight;
878
879
  } else {
879
880
  // Fallback to a reasonable default
880
881
  this.draggedActionHeight = 60;
@@ -970,6 +971,8 @@ export class CanvasNode extends RapidElement {
970
971
  }
971
972
 
972
973
  private handleActionMouseDown(event: MouseEvent, action: Action): void {
974
+ if (isRightClick(event)) return;
975
+
973
976
  // Don't handle clicks on the remove button, drag handle, or when action is in removing state
974
977
  const target = event.target as HTMLElement;
975
978
  if (
@@ -1097,6 +1100,8 @@ export class CanvasNode extends RapidElement {
1097
1100
  }
1098
1101
 
1099
1102
  private handleNodeMouseDown(event: MouseEvent): void {
1103
+ if (isRightClick(event)) return;
1104
+
1100
1105
  // Don't handle clicks on the remove button, exits, drag handle, or when node is in removing state
1101
1106
  const target = event.target as HTMLElement;
1102
1107
  if (