@brightspot/ui 3.0.1-cms-ui-migration.2 → 3.0.1-cms-ui-migration.4

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 (320) hide show
  1. package/README.md +56 -37
  2. package/dist/custom-elements.json +1807 -1807
  3. package/dist/storybook/assets/{ActionBar.stories-hJ_5cm-P.js → ActionBar.stories-BZAVK1QG.js} +1 -1
  4. package/dist/storybook/assets/{ActionItem.stories-Bjx2803w.js → ActionItem.stories-BqiWvlWi.js} +1 -1
  5. package/dist/storybook/assets/{Avatar.stories-Cj0YgZ6f.js → Avatar.stories-DkdB6hd_.js} +1 -1
  6. package/dist/storybook/assets/{AvatarGroup.stories-Lh_sQFCU.js → AvatarGroup.stories-DODqaIix.js} +1 -1
  7. package/dist/storybook/assets/{Badge.stories-BL7RUibx.js → Badge.stories-DZoum08S.js} +1 -1
  8. package/dist/storybook/assets/{Button-BPHNcxqK.js → Button-BkmBgNO-.js} +1 -1
  9. package/dist/storybook/assets/{Button.stories-CAYO4gdU.js → Button.stories-BFhCL2dp.js} +1 -1
  10. package/dist/storybook/assets/{ButtonGroup.stories-Cd13Us5K.js → ButtonGroup.stories-DlTGTvkq.js} +1 -1
  11. package/dist/storybook/assets/{Celebrate.stories-D_KE3Qze.js → Celebrate.stories-m4d4zTEz.js} +1 -1
  12. package/dist/storybook/assets/{Checkbox.stories-Aj1xgZVn.js → Checkbox.stories-Y253YeU7.js} +1 -1
  13. package/dist/storybook/assets/{CircularProgress.stories-BecV_v6d.js → CircularProgress.stories-6BD8uV5G.js} +1 -1
  14. package/dist/storybook/assets/{ClipboardMixin.stories-DU-WiZ2f.js → ClipboardMixin.stories-BkH66rIU.js} +1 -1
  15. package/dist/storybook/assets/{Color-6BZIO3FS-BYl4KZZn.js → Color-6BZIO3FS-iG0OjPBU.js} +1 -1
  16. package/dist/storybook/assets/{Colors.stories-BMUVUy2q.js → Colors.stories-BxiyQnEg.js} +1 -1
  17. package/dist/storybook/assets/{CombinedEffects.stories-FHcPKFm6.js → CombinedEffects.stories-Bs3U7qRl.js} +1 -1
  18. package/dist/storybook/assets/{ComponentStatesMixin-EMUnfT5y.js → ComponentStatesMixin-DampYb5c.js} +1 -1
  19. package/dist/storybook/assets/{ComponentStatesMixin.stories-SXq0kzS9.js → ComponentStatesMixin.stories-CZ2OW7as.js} +1 -1
  20. package/dist/storybook/assets/{CopyToClipboard.stories-u43lhvcI.js → CopyToClipboard.stories-CdlghjaE.js} +1 -1
  21. package/dist/storybook/assets/{Debounce.stories-BdCn5qgO.js → Debounce.stories-CymT8PnT.js} +1 -1
  22. package/dist/storybook/assets/{DocsRenderer-LL677BLK-DxiEJ_jx.js → DocsRenderer-LL677BLK-dqHCo-GE.js} +3 -3
  23. package/dist/storybook/assets/{Dropdown.stories-BWSRwjIF.js → Dropdown.stories-B6JwKg-I.js} +1 -1
  24. package/dist/storybook/assets/{EmptyState.stories-BpobeZL5.js → EmptyState.stories-Bk229lPH.js} +1 -1
  25. package/dist/storybook/assets/{Events.stories-CP5kMzpr.js → Events.stories-C8-k9cx8.js} +1 -1
  26. package/dist/storybook/assets/{Heading.stories-CbJD-oTB.js → Heading.stories-DIdnAQRG.js} +1 -1
  27. package/dist/storybook/assets/{HueRipple.stories-BOABJ7zw.js → HueRipple.stories-B1gXAEaH.js} +1 -1
  28. package/dist/storybook/assets/{Icon.stories-CWlUHL4j.js → Icon.stories--1VJ0_yt.js} +1 -1
  29. package/dist/storybook/assets/{IconButton.stories-BWBs-OLT.js → IconButton.stories-CS2rEIir.js} +1 -1
  30. package/dist/storybook/assets/{LinearProgress.stories-LZ0GZoxF.js → LinearProgress.stories-Bx0CQQls.js} +1 -1
  31. package/dist/storybook/assets/{Pagination.stories-CbLaR3P9.js → Pagination.stories-B4AogTXB.js} +1 -1
  32. package/dist/storybook/assets/{Popover.stories-JHrWqYZw.js → Popover.stories-BR88DPgU.js} +1 -1
  33. package/dist/storybook/assets/{ReadyMixin-B1H2a9x8.js → ReadyMixin-n6qZO39Y.js} +1 -1
  34. package/dist/storybook/assets/{RovingTabindexMixin.stories-UMHpYG73.js → RovingTabindexMixin.stories-1xTJSMSv.js} +1 -1
  35. package/dist/storybook/assets/{Rtc.stories-VQtNSlls.js → Rtc.stories-Rt0A_rUZ.js} +1 -1
  36. package/dist/storybook/assets/{ScrollShadow.stories-CYdi8Tgp.js → ScrollShadow.stories-CjLdJyYu.js} +1 -1
  37. package/dist/storybook/assets/{Switch.stories-D8F2hZCf.js → Switch.stories-CF8wO4v2.js} +1 -1
  38. package/dist/storybook/assets/{Tab.stories-wgBP0lTj.js → Tab.stories-C8XNshog.js} +1 -1
  39. package/dist/storybook/assets/{Tabs.stories-C00rr5sf.js → Tabs.stories-B01l4rQx.js} +1 -1
  40. package/dist/storybook/assets/{Throttle.stories-BhQEfJbS.js → Throttle.stories-Bpi0K5j9.js} +1 -1
  41. package/dist/storybook/assets/{Tooltip.stories-CGoZ5qTn.js → Tooltip.stories-BC4zMiZ1.js} +1 -1
  42. package/dist/storybook/assets/{Upload.stories-B3K-HAXw.js → Upload.stories-2pWv_fZ1.js} +1 -1
  43. package/dist/storybook/assets/{UploadItem.stories-71ArSoUh.js → UploadItem.stories-aLeAUM7y.js} +1 -1
  44. package/dist/storybook/assets/{Welcome.stories-CihlfFXS.js → Welcome.stories-DJV83eb7.js} +1 -1
  45. package/dist/storybook/assets/{Widget.stories-1u4KbiJM.js → Widget.stories-Cz0i2o6L.js} +1 -1
  46. package/dist/storybook/assets/{WithTooltip-65CFNBJE-B3Jitxw9.js → WithTooltip-65CFNBJE-D8QwVYG8.js} +1 -1
  47. package/dist/storybook/assets/{blocks-C1HaXuQB.js → blocks-BFmpEZRy.js} +5 -5
  48. package/dist/storybook/assets/{formatter-EIJCOSYU-Dy9Lt9fs.js → formatter-EIJCOSYU-CdwdJOPy.js} +1 -1
  49. package/dist/storybook/assets/if-defined-gbJXriW-.js +1 -0
  50. package/dist/storybook/assets/{iframe-Dx6IxWXF.js → iframe-CyssRDCd.js} +4 -4
  51. package/dist/storybook/assets/{index-OrjedSVh.js → index-B82i8dhg.js} +1 -1
  52. package/dist/storybook/assets/{onFind-YTqjw6W0.js → onFind-BFI1uIxr.js} +1 -1
  53. package/dist/storybook/assets/{onFind.stories-DEvwTrmx.js → onFind.stories-zZjQs_Gn.js} +1 -1
  54. package/dist/storybook/assets/{onRemove.stories-D5mO-Lin.js → onRemove.stories-pFBwAIex.js} +1 -1
  55. package/dist/storybook/assets/{onVisible.stories-C3Rcz0Eb.js → onVisible.stories-5dErOVRq.js} +1 -1
  56. package/dist/storybook/assets/{style-map-CiMHry7H.js → style-map-D0EcLEWO.js} +1 -1
  57. package/dist/storybook/assets/{syntaxhighlighter-ED5Y7EFY-DIZnuhb2.js → syntaxhighlighter-ED5Y7EFY--2h_dVga.js} +1 -1
  58. package/dist/storybook/iframe.html +1 -1
  59. package/dist/storybook/project.json +1 -1
  60. package/package.json +16 -2
  61. package/src/legacy/tool-ui/src/AnalyticsWidget.css +1 -1
  62. package/src/legacy/tool-ui/src/Board.css +1 -1
  63. package/src/legacy/tool-ui/src/BulkUpload.css +1 -1
  64. package/src/legacy/tool-ui/src/ComboInput.css +1 -1
  65. package/src/legacy/tool-ui/src/Compat.css +5 -5
  66. package/src/legacy/tool-ui/src/ContentEditDrawer.css +1 -1
  67. package/src/legacy/tool-ui/src/Dialog.css +1 -1
  68. package/src/legacy/tool-ui/src/FormFilter.css +1 -1
  69. package/src/legacy/tool-ui/src/Icon/index.css +1 -1
  70. package/src/legacy/tool-ui/src/ImageEditor.css +1 -1
  71. package/src/legacy/tool-ui/src/Incompatible.css +2 -2
  72. package/src/legacy/tool-ui/src/LinkCarousel.css +1 -1
  73. package/src/legacy/tool-ui/src/Page.css +1 -1
  74. package/src/legacy/tool-ui/src/RepeatableContentInputGroup.css +1 -1
  75. package/src/legacy/tool-ui/src/RichText.css +1 -1
  76. package/src/legacy/tool-ui/src/SearchWidget.css +2 -2
  77. package/src/legacy/tool-ui/src/SearchWidgetAdvanced.css +1 -1
  78. package/src/legacy/tool-ui/src/Widget.css +1 -1
  79. package/src/legacy/tool-ui/src/main/webapp/dist/v5.5e5d7f655e174ddd85f5.css +5 -0
  80. package/dist/storybook/assets/if-defined-CA2KmTqA.js +0 -1
  81. package/docs/adr/0001-retire-cms-ui-package-fold-under-src-legacy.md +0 -78
  82. package/docs/adr/0002-yarn-workspaces-preserve-cms-ui-deps.md +0 -130
  83. package/docs/adr/0003-bundle-equivalence-as-fold-acceptance-criterion.md +0 -286
  84. package/src/legacy/tool-ui/src/main/resources/settings.properties +0 -1
  85. package/src/legacy/tool-ui/src/main/webapp/WEB-INF/web.xml +0 -81
  86. package/src/legacy/tool-ui/src/main/webapp/dist/v5.5e3fdf0f0b20b4e3c170.css +0 -5
  87. package/src/legacy/tool-ui/src/main/webapp/script/bsp-uploader.js +0 -170
  88. package/src/legacy/tool-ui/src/main/webapp/script/bsp-utils.js +0 -393
  89. package/src/legacy/tool-ui/src/main/webapp/script/content/layout-element.js +0 -141
  90. package/src/legacy/tool-ui/src/main/webapp/script/input/query.js +0 -78
  91. package/src/legacy/tool-ui/src/main/webapp/script/input/workflow.js +0 -718
  92. package/src/legacy/tool-ui/src/main/webapp/script/jquery.extra.js +0 -633
  93. package/src/legacy/tool-ui/src/main/webapp/script/v3/Dropbox.js +0 -18
  94. package/src/legacy/tool-ui/src/main/webapp/script/v3/EditFieldUpdate.js +0 -406
  95. package/src/legacy/tool-ui/src/main/webapp/script/v3/EditFieldUpdateCache.js +0 -1
  96. package/src/legacy/tool-ui/src/main/webapp/script/v3/Notification.js +0 -151
  97. package/src/legacy/tool-ui/src/main/webapp/script/v3/content/edit.js +0 -194
  98. package/src/legacy/tool-ui/src/main/webapp/script/v3/content/state.js +0 -785
  99. package/src/legacy/tool-ui/src/main/webapp/script/v3/csrf.js +0 -35
  100. package/src/legacy/tool-ui/src/main/webapp/script/v3/dashboard.js +0 -65
  101. package/src/legacy/tool-ui/src/main/webapp/script/v3/input/dataTransfer.js +0 -129
  102. package/src/legacy/tool-ui/src/main/webapp/script/v3/input/file.js +0 -433
  103. package/src/legacy/tool-ui/src/main/webapp/script/v3/input/object.js +0 -743
  104. package/src/legacy/tool-ui/src/main/webapp/script/v3/input/read-only.js +0 -17
  105. package/src/legacy/tool-ui/src/main/webapp/script/v3/jquery.frame.js +0 -478
  106. package/src/legacy/tool-ui/src/main/webapp/script/v3/jquery.repeatable.js +0 -2406
  107. package/src/legacy/tool-ui/src/main/webapp/script/v3/plugin/popup.d.ts +0 -2
  108. package/src/legacy/tool-ui/src/main/webapp/script/v3/plugin/popup.js +0 -446
  109. package/src/legacy/tool-ui/src/main/webapp/script/v3/search-filters.js +0 -62
  110. package/src/legacy/tool-ui/src/main/webapp/script/v3/search.js +0 -53
  111. package/src/legacy/tool-ui/src/main/webapp/script/v3.js +0 -1049
  112. package/src/legacy/tool-ui/src/main/webapp/v4/Admin.js +0 -16
  113. package/src/legacy/tool-ui/src/main/webapp/v4/AutoExpand.js +0 -84
  114. package/src/legacy/tool-ui/src/main/webapp/v4/AutoSubmit.js +0 -68
  115. package/src/legacy/tool-ui/src/main/webapp/v4/Bridge.js +0 -536
  116. package/src/legacy/tool-ui/src/main/webapp/v4/CheckboxInput.js +0 -22
  117. package/src/legacy/tool-ui/src/main/webapp/v4/ColorInput.js +0 -5
  118. package/src/legacy/tool-ui/src/main/webapp/v4/ColorInputSpectrum.js +0 -107
  119. package/src/legacy/tool-ui/src/main/webapp/v4/ComboInput.js +0 -1491
  120. package/src/legacy/tool-ui/src/main/webapp/v4/CommunityWidget.js +0 -29
  121. package/src/legacy/tool-ui/src/main/webapp/v4/ContentEdit.js +0 -2427
  122. package/src/legacy/tool-ui/src/main/webapp/v4/ContentLock.js +0 -470
  123. package/src/legacy/tool-ui/src/main/webapp/v4/ContentReporting.js +0 -32
  124. package/src/legacy/tool-ui/src/main/webapp/v4/DataTable.js +0 -31
  125. package/src/legacy/tool-ui/src/main/webapp/v4/DateStringField.js +0 -485
  126. package/src/legacy/tool-ui/src/main/webapp/v4/Entry.js +0 -264
  127. package/src/legacy/tool-ui/src/main/webapp/v4/ExternalItemAuth.js +0 -16
  128. package/src/legacy/tool-ui/src/main/webapp/v4/Form.js +0 -31
  129. package/src/legacy/tool-ui/src/main/webapp/v4/Hierarchy.js +0 -100
  130. package/src/legacy/tool-ui/src/main/webapp/v4/Icon.ts +0 -49
  131. package/src/legacy/tool-ui/src/main/webapp/v4/ImageEditor.js +0 -2403
  132. package/src/legacy/tool-ui/src/main/webapp/v4/ImageEditorBundle.js +0 -5
  133. package/src/legacy/tool-ui/src/main/webapp/v4/LinkCarousel.js +0 -40
  134. package/src/legacy/tool-ui/src/main/webapp/v4/LinkList.js +0 -14
  135. package/src/legacy/tool-ui/src/main/webapp/v4/LinkTable.js +0 -123
  136. package/src/legacy/tool-ui/src/main/webapp/v4/Location.js +0 -19
  137. package/src/legacy/tool-ui/src/main/webapp/v4/LocationMap.js +0 -148
  138. package/src/legacy/tool-ui/src/main/webapp/v4/LookingGlass.js +0 -24
  139. package/src/legacy/tool-ui/src/main/webapp/v4/Message.js +0 -14
  140. package/src/legacy/tool-ui/src/main/webapp/v4/NumberBar.js +0 -32
  141. package/src/legacy/tool-ui/src/main/webapp/v4/Page.js +0 -890
  142. package/src/legacy/tool-ui/src/main/webapp/v4/Preview.js +0 -758
  143. package/src/legacy/tool-ui/src/main/webapp/v4/PreviewEditor.js +0 -86
  144. package/src/legacy/tool-ui/src/main/webapp/v4/PreviewOverlay.js +0 -1005
  145. package/src/legacy/tool-ui/src/main/webapp/v4/PubSub.js +0 -47
  146. package/src/legacy/tool-ui/src/main/webapp/v4/QueryField.js +0 -211
  147. package/src/legacy/tool-ui/src/main/webapp/v4/RegionMap.js +0 -215
  148. package/src/legacy/tool-ui/src/main/webapp/v4/RepeatableContentInputGroup.js +0 -160
  149. package/src/legacy/tool-ui/src/main/webapp/v4/RichTextEditor.js +0 -154
  150. package/src/legacy/tool-ui/src/main/webapp/v4/SearchFields.js +0 -281
  151. package/src/legacy/tool-ui/src/main/webapp/v4/SearchResult.js +0 -255
  152. package/src/legacy/tool-ui/src/main/webapp/v4/SharePreview.js +0 -56
  153. package/src/legacy/tool-ui/src/main/webapp/v4/Sortable.js +0 -874
  154. package/src/legacy/tool-ui/src/main/webapp/v4/StyleEmbeddedContent.js +0 -100
  155. package/src/legacy/tool-ui/src/main/webapp/v4/StyleguidePresets.js +0 -357
  156. package/src/legacy/tool-ui/src/main/webapp/v4/TabContainer.js +0 -360
  157. package/src/legacy/tool-ui/src/main/webapp/v4/Taxonomy.js +0 -27
  158. package/src/legacy/tool-ui/src/main/webapp/v4/ThemeBundleEditor.js +0 -224
  159. package/src/legacy/tool-ui/src/main/webapp/v4/TimedContent.js +0 -147
  160. package/src/legacy/tool-ui/src/main/webapp/v4/TimedContentBundle.js +0 -8
  161. package/src/legacy/tool-ui/src/main/webapp/v4/VideoEditor.js +0 -2417
  162. package/src/legacy/tool-ui/src/main/webapp/v4/VideoEditorBundle.js +0 -8
  163. package/src/legacy/tool-ui/src/main/webapp/v4/ViewMirror.js +0 -52
  164. package/src/legacy/tool-ui/src/main/webapp/v4/ViewPreview.d.ts +0 -13
  165. package/src/legacy/tool-ui/src/main/webapp/v4/ViewPreview.js +0 -177
  166. package/src/legacy/tool-ui/src/main/webapp/v4/Widget.js +0 -90
  167. package/src/legacy/tool-ui/src/main/webapp/v4/__mocks__/fileMock.js +0 -1
  168. package/src/legacy/tool-ui/src/main/webapp/v4/__mocks__/styleMock.js +0 -1
  169. package/src/legacy/tool-ui/src/main/webapp/v4/__mocks__/textArea.mock.js +0 -20
  170. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/globals.js +0 -770
  171. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/ProseMirror.test.js +0 -16
  172. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/index.html +0 -54
  173. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/comment_manager/CommentManager.test.js +0 -29
  174. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/comment_manager/index.html +0 -35
  175. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/custom_keyboard/CustomKeyboard.js +0 -42
  176. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/custom_keyboard/index.html +0 -37
  177. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/enhancement_manager/EnhancementManager.test.js +0 -288
  178. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/enhancement_manager/block.html +0 -38
  179. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/enhancement_manager/inline.html +0 -38
  180. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/enhancement_manager/no-popups.html +0 -38
  181. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/list_manager/ListManager.js +0 -257
  182. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/list_manager/index.html +0 -38
  183. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/menubar/hierarchal.html +0 -33
  184. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/menubar/index.html +0 -33
  185. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/menubar/menubar.test.js +0 -195
  186. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/menubar/small.html +0 -34
  187. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/menubar/tags.html +0 -34
  188. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/placeholder_manager/PlaceholderManager.test.js +0 -134
  189. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/placeholder_manager/has-editable-placeholder.html +0 -32
  190. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/placeholder_manager/has-text.html +0 -34
  191. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/placeholder_manager/index.html +0 -31
  192. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/table_manager/TableManager.test.js +0 -63
  193. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/table_manager/existing.html +0 -48
  194. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/track_manager/TrackManager.test.js +0 -291
  195. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/track_manager/existing.html +0 -39
  196. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/track_manager/insert.html +0 -37
  197. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/unit/Sortable.test.js +0 -105
  198. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/unit/rte/ProseMirror.test.js +0 -41
  199. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/unit/rte/codemirror-shim.test.js +0 -72
  200. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/unit/rte/plugins/collab_manager/CollabManager.test.js +0 -46
  201. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/unit/rte/plugins/enhancement_manager/EnhancementManager.test.js +0 -84
  202. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/unit/rte/plugins/list_manager/ListManager.test.js +0 -54
  203. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/unit/rte/plugins/menubar/menubar.test.js +0 -183
  204. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/unit/rte/plugins/spellcheck/SpellCheck.test.js +0 -45
  205. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/unit/rte/settings/BSSerializer.test.js +0 -346
  206. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/unit/rte/settings/menuItemsBuilder.test.js +0 -226
  207. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/unit/rte/utilities.test.js +0 -118
  208. package/src/legacy/tool-ui/src/main/webapp/v4/appetizeio/Appetizeio.js +0 -5
  209. package/src/legacy/tool-ui/src/main/webapp/v4/appetizeio/AppetizeioEmbedded.js +0 -113
  210. package/src/legacy/tool-ui/src/main/webapp/v4/compat/Fetch.js +0 -16
  211. package/src/legacy/tool-ui/src/main/webapp/v4/compat/jquery.js +0 -32
  212. package/src/legacy/tool-ui/src/main/webapp/v4/compat/requirejs.js +0 -13
  213. package/src/legacy/tool-ui/src/main/webapp/v4/dom/Tether.js +0 -1
  214. package/src/legacy/tool-ui/src/main/webapp/v4/dom/TetherLayout.js +0 -1
  215. package/src/legacy/tool-ui/src/main/webapp/v4/dom/closest.js +0 -1
  216. package/src/legacy/tool-ui/src/main/webapp/v4/dom/create.js +0 -1
  217. package/src/legacy/tool-ui/src/main/webapp/v4/dom/find.js +0 -1
  218. package/src/legacy/tool-ui/src/main/webapp/v4/dom/findAll.js +0 -1
  219. package/src/legacy/tool-ui/src/main/webapp/v4/dom/ifClick.js +0 -1
  220. package/src/legacy/tool-ui/src/main/webapp/v4/dom/ifMatches.js +0 -1
  221. package/src/legacy/tool-ui/src/main/webapp/v4/dom/ifUnmodified.js +0 -1
  222. package/src/legacy/tool-ui/src/main/webapp/v4/dom/index.js +0 -5
  223. package/src/legacy/tool-ui/src/main/webapp/v4/dom/insertBefore.js +0 -1
  224. package/src/legacy/tool-ui/src/main/webapp/v4/dom/insertFirst.js +0 -1
  225. package/src/legacy/tool-ui/src/main/webapp/v4/dom/insertLast.js +0 -1
  226. package/src/legacy/tool-ui/src/main/webapp/v4/dom/onFind.js +0 -1
  227. package/src/legacy/tool-ui/src/main/webapp/v4/dom/onFindOnce.js +0 -1
  228. package/src/legacy/tool-ui/src/main/webapp/v4/dom/onRTEReady.js +0 -1
  229. package/src/legacy/tool-ui/src/main/webapp/v4/dom/onRemove.js +0 -1
  230. package/src/legacy/tool-ui/src/main/webapp/v4/dom/onVisible.js +0 -1
  231. package/src/legacy/tool-ui/src/main/webapp/v4/dom/previousUntil.js +0 -1
  232. package/src/legacy/tool-ui/src/main/webapp/v4/dom/resolveIconCompat.js +0 -40
  233. package/src/legacy/tool-ui/src/main/webapp/v4/rtc/Socket.js +0 -1
  234. package/src/legacy/tool-ui/src/main/webapp/v4/rtc/index.js +0 -1
  235. package/src/legacy/tool-ui/src/main/webapp/v4/rte/ProseMirror.js +0 -909
  236. package/src/legacy/tool-ui/src/main/webapp/v4/rte/README.md +0 -68
  237. package/src/legacy/tool-ui/src/main/webapp/v4/rte/codemirror-shim.d.ts +0 -8
  238. package/src/legacy/tool-ui/src/main/webapp/v4/rte/codemirror-shim.js +0 -274
  239. package/src/legacy/tool-ui/src/main/webapp/v4/rte/collab-workflow.jpeg +0 -0
  240. package/src/legacy/tool-ui/src/main/webapp/v4/rte/interchangeable.ts +0 -250
  241. package/src/legacy/tool-ui/src/main/webapp/v4/rte/mention.js +0 -90
  242. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/PluginProvider.js +0 -124
  243. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/README.md +0 -46
  244. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/ai_inline_manager/AIInlineManager.ts +0 -124
  245. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/ai_inline_manager/views/AIInlineView.ts +0 -1019
  246. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/ai_manager/AiManager.ts +0 -199
  247. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/collab_manager/CollabManager.js +0 -339
  248. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/collab_manager/views/AvatarView.js +0 -96
  249. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/comment_manager/CommentManager.js +0 -348
  250. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/custom_keyboard/CustomKeyboard.js +0 -110
  251. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/custom_keyboard/README.md +0 -29
  252. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/EnhancementManager.js +0 -428
  253. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/README.md +0 -63
  254. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/commands.js +0 -690
  255. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/constants.js +0 -12
  256. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/enhancement-creation.jpeg +0 -0
  257. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/index.js +0 -15
  258. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/rte-flow.jpeg +0 -0
  259. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/views/ActionButtonView.js +0 -86
  260. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/views/BlockSubmenuView.js +0 -60
  261. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/views/EnhancementView.js +0 -208
  262. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/views/PreviewView.js +0 -102
  263. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/views/SubmenuView.js +0 -365
  264. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/find_replace_manager/FindReplaceManager.js +0 -239
  265. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/find_replace_manager/views/FindView.js +0 -604
  266. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/fullscreen_manager/FullscreenManager.js +0 -57
  267. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/fullscreen_manager/README.md +0 -26
  268. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/fullscreen_manager/commands.js +0 -16
  269. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/fullscreen_manager/index.js +0 -4
  270. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/fullscreen_manager/views/FullscreenView.js +0 -474
  271. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/html_editor_manager/htmlEditorManager.js +0 -66
  272. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/html_editor_manager/views/HtmlEditorView.js +0 -97
  273. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/list_manager/ListManager.js +0 -342
  274. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/list_manager/README.md +0 -50
  275. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/list_manager/commands.js +0 -207
  276. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/list_manager/constants.js +0 -26
  277. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/list_manager/index.js +0 -4
  278. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/menubar/Menubar.js +0 -485
  279. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/menubar/README.md +0 -40
  280. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/menubar/views/MenuView.js +0 -842
  281. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/paste_manager/PasteManager.js +0 -368
  282. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/placeholder_manager/PlaceHolderManager.js +0 -128
  283. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/raw_text_manager/README.md +0 -13
  284. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/raw_text_manager/RawTextManager.js +0 -96
  285. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/spellcheck/index.js +0 -3
  286. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/spellcheck/spellcheck-plugin.js +0 -280
  287. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/spellcheck/spellcheck-service.js +0 -94
  288. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/table_manager/TableManager.js +0 -57
  289. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/table_manager/commands.js +0 -97
  290. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/table_manager/views/TableSizerView.js +0 -88
  291. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/table_manager/views/TableView.js +0 -613
  292. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/track_manager/README.md +0 -13
  293. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/track_manager/TrackManager.js +0 -905
  294. package/src/legacy/tool-ui/src/main/webapp/v4/rte/settings/BSSerializer.js +0 -819
  295. package/src/legacy/tool-ui/src/main/webapp/v4/rte/settings/README.md +0 -80
  296. package/src/legacy/tool-ui/src/main/webapp/v4/rte/settings/commands.js +0 -98
  297. package/src/legacy/tool-ui/src/main/webapp/v4/rte/settings/constants.d.ts +0 -84
  298. package/src/legacy/tool-ui/src/main/webapp/v4/rte/settings/constants.js +0 -87
  299. package/src/legacy/tool-ui/src/main/webapp/v4/rte/settings/index.js +0 -13
  300. package/src/legacy/tool-ui/src/main/webapp/v4/rte/settings/keymapBuilder.js +0 -223
  301. package/src/legacy/tool-ui/src/main/webapp/v4/rte/settings/menuItemsBuilder.js +0 -559
  302. package/src/legacy/tool-ui/src/main/webapp/v4/rte/settings/schemaBuilder.js +0 -1281
  303. package/src/legacy/tool-ui/src/main/webapp/v4/rte/utilities.d.ts +0 -4
  304. package/src/legacy/tool-ui/src/main/webapp/v4/rte/utilities.js +0 -359
  305. package/src/legacy/tool-ui/src/main/webapp/v4/theme/ColorRotator.js +0 -1
  306. package/src/legacy/tool-ui/src/main/webapp/v4/util/debounce.js +0 -1
  307. package/src/legacy/tool-ui/src/main/webapp/v4/util/getComponentKey.js +0 -1
  308. package/src/legacy/tool-ui/src/main/webapp/v4/util/noise.js +0 -1
  309. package/src/legacy/tool-ui/src/main/webapp/v4/util/repaint.js +0 -1
  310. package/src/legacy/tool-ui/src/main/webapp/v4/util/storage.js +0 -1
  311. package/src/legacy/tool-ui/src/main/webapp/v4/util/throttle.js +0 -1
  312. package/src/legacy/tool-ui/src/main/webapp/v4/widget/AssignmentContent.js +0 -33
  313. package/src/legacy/tool-ui/src/main/webapp/v4/widget/AssignmentDeskDashboard.js +0 -217
  314. package/src/legacy/tool-ui/src/main/webapp/v4/widget/AssociatedContentWidget.js +0 -7
  315. package/src/legacy/tool-ui/src/main/webapp/v4/widget/BulkUpload.js +0 -19
  316. package/src/legacy/tool-ui/src/main/webapp/v4/widget/Calendar.js +0 -7
  317. package/src/legacy/tool-ui/src/main/webapp/v4/widget/ClosableWindow.js +0 -13
  318. package/src/legacy/tool-ui/src/main/webapp/v4/widget/PitchAssignments.js +0 -25
  319. package/src/legacy/tool-ui/src/main/webapp/v4/widget/PitchContent.js +0 -33
  320. package/src/legacy/tool-ui/src/main/webapp/v4/widget/Revisions.js +0 -61
@@ -1,2403 +0,0 @@
1
- import closest from './dom/closest'
2
- import create from './dom/create'
3
- import Cropper from 'cropperjs'
4
- import find from './dom/find'
5
- import Noise from './util/noise'
6
- import onFind from './dom/onFind'
7
- import PlainDraggable from 'plain-draggable/plain-draggable.esm.js'
8
- import TabContainer from './TabContainer'
9
- import findAll from './dom/findAll'
10
- import { toggleMenu, setupMenuForAria } from '../../../dom/popupMenu'
11
- import 'cropperjs/dist/cropper.css'
12
- import onRemove from './dom/onRemove'
13
-
14
- if (!window.BRIGHTSPOT?.ui.cms.enableV5UI) {
15
- await import('./ImageEditor.less')
16
- }
17
-
18
- const tooltips = window.BRIGHTSPOT?.ui.tooltips
19
- const zIndex = '400'
20
- const SPOT_SIZE = 40
21
- const filtersApplied = new Map()
22
- const FILTERABLE_SELECTORS =
23
- '.cropper-canvas > img, .cropper-view-box > img, .imageEditor-sizePreview'
24
-
25
- export default class ImageEditor {
26
- constructor(element) {
27
- // Then convert it to a popup.
28
- const STATE_OPEN = 'is-imageEditorOpen'
29
- const STATE_LOADING = 'is-imageEditorLoading'
30
- const STATE_LOADED = 'is-imageEditorLoaded'
31
- const STATE_READY = 'is-imageEditorReady'
32
- const row = closest(element, '.inputContainer')
33
- const fileSelector = find(row, '.fileSelector')
34
- const large = element.parentNode
35
- const image = find(element, '.imageEditor-image > img')
36
- const rcigItem = element.closest('.RCIG-item')
37
-
38
- if (!large || !image) return
39
-
40
- this._element = element
41
- this._renditions = new Map()
42
- this._focusUI = null
43
- this._editUI = null
44
- this._initialContainerHeight = null
45
- this._form = this._element.closest('form')
46
- this._implicitFocus = element.dataset.implicitFocus
47
- this._isContextual = 'contextual' in element.dataset
48
-
49
- // Hide the whole editor from screen readers.
50
- this._element.setAttribute('hidden', '')
51
-
52
- let rootCIG = large
53
- while (rootCIG.parentElement?.closest('.CIG-row')) {
54
- rootCIG = rootCIG.parentElement?.closest('.CIG-row')
55
- }
56
-
57
- // Hide hotspot inputs from the content edit UI.
58
- const fields = this.getForm().querySelector(
59
- '.inputContainer[data-name$=".hotspots"]',
60
- )
61
- if (fields) fields.style.display = 'none'
62
-
63
- const resizeObserver = new ResizeObserver((entries) => {
64
- for (let entry of entries) {
65
- this.repaint(entry.contentRect.height)
66
- }
67
- })
68
-
69
- resizeObserver.observe(document.body)
70
-
71
- const loader = create('div', { className: 'ImageEditor-preview-loader' })
72
- const previewImage = image.cloneNode()
73
- previewImage.onload = () => {
74
- large.classList.add('is-imagePreview')
75
- large.classList.add(STATE_LOADING)
76
- this.getElement().classList.add(STATE_LOADING)
77
- loader.style.width = `${previewImage.offsetWidth}px`
78
- loader.style.height = `${previewImage.offsetHeight}px`
79
- }
80
-
81
- const escapeToClose = (event) => {
82
- if (event.key === 'Escape') {
83
- this._element.querySelector('.ImageEditor-closeButton')?.click()
84
- }
85
- }
86
-
87
- const editButton = create('button', {
88
- className:
89
- this.getElement().getAttribute('editor') === 'true'
90
- ? 'ImageEditor-open'
91
- : 'ImageEditor-open noEditor',
92
- 'aria-haspopup': true,
93
- 'aria-label': tooltips.imageEditor.open,
94
- title: tooltips.imageEditor.open,
95
- type: 'button',
96
- onclick: (event) => {
97
- this._element.removeAttribute('hidden')
98
- large.classList.add(STATE_OPEN)
99
- rootCIG.style.zIndex = zIndex
100
- const left = this.getElement().closest('.ContentEdit-left')
101
- if (left) {
102
- left.style.position = 'fixed'
103
- left.style.zIndex = zIndex
104
- }
105
-
106
- // If this is the first time opening the editor UI...
107
- if (!large.classList.contains(STATE_READY)) {
108
- large.classList.add(STATE_READY)
109
- this._initialContainerHeight = this.getElement().offsetHeight
110
- this._renditions = this.flattenAndSortRenditions(this.getRenditions())
111
- this.renderRenditionPreviews(this._renditions)
112
- this.renderTabs()
113
- }
114
- if (rcigItem) {
115
- rcigItem.classList.add(STATE_OPEN)
116
- }
117
-
118
- this._element.querySelector('.ImageEditor-edit button')?.focus()
119
- window.addEventListener('keydown', escapeToClose)
120
-
121
- // TODO: repaint after open transition completes instead of guessing.
122
- setTimeout(() => {
123
- this.repaint(document.body.offsetHeight)
124
- if (this._hotspotUI) {
125
- this._hotspotUI.reflow()
126
- this._hotspotUI.reveal()
127
- }
128
- }, 250)
129
- },
130
- })
131
-
132
- const aside = find(this.getElement(), '.imageEditor-aside')
133
- const editList = find(aside, '.imageEditor-tools ul')
134
- editList.classList.add('ImageEditor-editTools')
135
- editList.setAttribute('role', 'menu')
136
-
137
- const editToggle = create('button', {
138
- className: 'ImageEditor-editToggle',
139
- type: 'button',
140
- title: tooltips.imageEditor.actions,
141
- 'aria-label': tooltips.imageEditor.actions,
142
- 'aria-haspopup': 'menu',
143
- 'aria-expanded': false,
144
- onclick: (event) => {
145
- toggleMenu(editList, editToggle)
146
- },
147
- })
148
-
149
- setupMenuForAria(editList, editToggle)
150
-
151
- const ratios = find(this.getElement(), '.ImageEditor-groups')
152
- if (ratios) {
153
- onRemove.pause()
154
- large.appendChild(ratios)
155
- requestAnimationFrame(onRemove.resume)
156
- }
157
-
158
- const focusText = this.getElement().getAttribute('data-focus-message')
159
- const message = create(
160
- 'div',
161
- {
162
- className: 'ImageEditor-focusMessage',
163
- },
164
- focusText,
165
- )
166
- const implicitFocusMessage = create(
167
- 'span',
168
- { className: `ImageEditor-implicitMessage is-hidden` },
169
- this.getElement().dataset.implicitFocusMessage,
170
- )
171
-
172
- const showFocusMessage = function (size) {
173
- message.textContent = focusText + ' ' + size
174
- message.classList.remove('is-hidden')
175
- }
176
-
177
- this._cropSelect = large.querySelector('.ImageEditor-groups > select')
178
- if (this._cropSelect) {
179
- this._cropSelect.addEventListener('change', (event) => {
180
- const index = this._cropSelect.selectedIndex
181
- const selectedCrop = this._cropSelect.options[index]
182
- message.classList.remove('is-hidden')
183
- showFocusMessage(selectedCrop.textContent)
184
- let coords = this._focusUI.getFocusCoordinatesRelative(
185
- selectedCrop.value,
186
- )
187
- const focusPoint = find(large, '.ImageEditor-focus-point')
188
- if (coords) {
189
- const focusPointCoords = this.getFocusCoords(selectedCrop.value)
190
- // remove implicit info if set.
191
- if (
192
- focusPointCoords.xPercent !== null &&
193
- focusPointCoords.yPercent !== null
194
- ) {
195
- focusPoint && focusPoint.classList.remove('is-implicit')
196
- implicitFocusMessage.classList.add('is-hidden')
197
- } else if (this.getImplicitFocus()) {
198
- focusPoint && focusPoint.classList.add('is-implicit')
199
- focusPoint && focusPoint.classList.remove('is-hidden')
200
- implicitFocusMessage.classList.remove('is-hidden')
201
- }
202
- focusPoint && focusPoint.classList.remove('is-hidden')
203
- this._focusUI.moveTo(coords)
204
- } else {
205
- if (this.getImplicitFocus()) {
206
- const implicitFocus = this.getImplicitFocus().split(',')
207
- coords = {
208
- x: implicitFocus[0],
209
- xPercent: implicitFocus[0] * 100,
210
- y: implicitFocus[1],
211
- yPercent: implicitFocus[1] * 100,
212
- }
213
- implicitFocusMessage.classList.remove('is-hidden')
214
- focusPoint && focusPoint.classList.add('is-implicit')
215
- this._focusUI.moveTo(coords)
216
- } else {
217
- focusPoint && focusPoint.classList.add('is-hidden')
218
- }
219
- }
220
- this.showPreview()
221
- })
222
- }
223
-
224
- if (fileSelector) {
225
- find(fileSelector, 'select').addEventListener('change', (e) => {
226
- editButton.style.display = e.target.value === 'keep' ? '' : 'none'
227
- editToggle.style.display = e.target.value === 'keep' ? '' : 'none'
228
- })
229
- }
230
-
231
- large.insertBefore(
232
- create('div', { className: 'ImageEditor-preview' }, previewImage, loader),
233
- element,
234
- )
235
-
236
- large.append(editButton, editToggle, editList)
237
-
238
- const done = create(
239
- 'button',
240
- {
241
- className: 'ImageEditor-closeButton',
242
- name: 'action-done',
243
- onclick: (event) => {
244
- rootCIG.style.zIndex = 'auto'
245
- const left = this.getElement().closest('.ContentEdit-left')
246
- if (left) {
247
- left.style.position = 'initial'
248
- left.style.zIndex = 'auto'
249
- }
250
- this._element.setAttribute('hidden', '')
251
-
252
- this.renderPreview()
253
- if (this._focusUI) {
254
- this.showPreview()
255
- }
256
- large.classList.remove(STATE_OPEN)
257
- if (rcigItem) {
258
- rcigItem.classList.remove(STATE_OPEN)
259
- }
260
- editButton.focus()
261
- window.removeEventListener('keydown', escapeToClose)
262
- event.preventDefault()
263
- },
264
- },
265
- 'Done',
266
- )
267
-
268
- this.getElement().appendChild(
269
- create('div', { className: 'ActionBar' }, done),
270
- )
271
-
272
- const applyEditsOnPreview = (data) => {
273
- const {
274
- brightness,
275
- contrast,
276
- invert,
277
- grayscale,
278
- sepia,
279
- flipH,
280
- flipV,
281
- rotate,
282
- } = { ...data }
283
- this.applyFilter(
284
- 'brightness',
285
- `${this.convertValueToPercent(brightness)}%`,
286
- )
287
- this.applyFilter('contrast', `${this.convertValueToPercent(contrast)}%`)
288
- this.applyFilter('sepia', sepia ? '100%' : null)
289
- this.applyFilter('invert', invert ? '100%' : null)
290
- this.applyFilter('grayscale', grayscale ? '100%' : null)
291
- flipH
292
- ? this._croptool.scale(-1, flipV ? -1 : 1)
293
- : this._croptool.scale(1, flipV ? -1 : 1)
294
-
295
- flipV
296
- ? this._croptool.scale(flipH ? -1 : 1, -1)
297
- : this._croptool.scale(flipH ? -1 : 1, 1)
298
-
299
- if (rotate) {
300
- this._croptool.rotateTo(rotate)
301
- }
302
- }
303
-
304
- const calculatePreviewRatios = () => {
305
- const groups = findAll(this._cropSelect, 'option:not([value=""])')
306
- const sizeInfos = Object.values(this.getRenditions())
307
- groups.forEach((group) => {
308
- const filtered = sizeInfos.filter((obj) => {
309
- return obj.group === group.value && obj.aspectRatio !== 0
310
- })
311
-
312
- if (filtered.length > 0) {
313
- const avgAspectRatio =
314
- filtered.reduce((a, b) => a + b.aspectRatio, 0) / filtered.length
315
- group.setAttribute('data-preview-ratio', avgAspectRatio)
316
- } else {
317
- this._cropSelect.removeChild(group)
318
- }
319
- })
320
- if (this._cropSelect.children.length === 1) {
321
- const input = find(ratios, '.ComboInput')
322
- if (input) {
323
- input.style.display = 'none'
324
- }
325
- }
326
- }
327
-
328
- const handleImageLoaded = (image) => {
329
- this.initCropTool(image).then((data) => {
330
- this.getElement().classList.remove(STATE_LOADING)
331
- large.classList.remove(STATE_LOADING)
332
- large.classList.add(STATE_LOADED)
333
- applyEditsOnPreview(this.getEditValues())
334
- this.renderPreview()
335
- if (implicitFocusMessage) {
336
- large.appendChild(implicitFocusMessage)
337
- }
338
- if (this._cropSelect) {
339
- if (message) {
340
- large.appendChild(message)
341
- }
342
- const index = this._cropSelect.selectedIndex
343
- const selectedCrop = this._cropSelect.options[index]
344
- showFocusMessage(selectedCrop.textContent)
345
- this.imageMask = create('div', {
346
- className: 'ImageEditor-preview-border',
347
- })
348
- large.querySelector('.ImageEditor-preview').append(this.imageMask)
349
- this._focusUI = this.createFocusUI(large, () => {
350
- this.updateAllRenditionPreviews()
351
- })
352
- calculatePreviewRatios()
353
- this.showPreview()
354
- }
355
- })
356
- }
357
-
358
- if (!image.complete) {
359
- image.onload = (event) => {
360
- handleImageLoaded(image)
361
- }
362
- } else {
363
- handleImageLoaded(image)
364
- }
365
- }
366
-
367
- // Applies a CSS filter to the "filterable" images.
368
- // Combines filters if multiple are used in the UI.
369
- // @name: the CSS filter function to use.
370
- // @val: the CSS filter function percentage to use. *Note: passing null will remove the filter.
371
- applyFilter(name, val) {
372
- let cssFilterProp = ''
373
- const images = [
374
- ...this.getElement().querySelectorAll(FILTERABLE_SELECTORS),
375
- this.getPreviewContainer(),
376
- ]
377
-
378
- val ? filtersApplied.set(name, val) : filtersApplied.delete(name)
379
-
380
- filtersApplied.forEach((val, name) => {
381
- cssFilterProp += ` ${name}(${val}) `
382
- })
383
-
384
- images.forEach((image) => (image.style.filter = `${cssFilterProp.trim()}`))
385
- }
386
-
387
- // Server values range from -1 to 1 and must be converted to percentage.
388
- convertValueToPercent(val) {
389
- let amount = parseFloat(val)
390
- amount = amount * 100 + 100
391
- return amount
392
- }
393
-
394
- async initCropTool(image) {
395
- await new Promise((resolve, reject) => {
396
- this._croptool = new Cropper(image, {
397
- autoCrop: false,
398
- autoCropArea: 1,
399
- movable: false,
400
- restore: false,
401
- responsive: true,
402
- scalable: true, // needed for flipping
403
- toggleDragModeOnDblclick: false,
404
- viewMode: 1,
405
- zoomable: false,
406
- zoomOnTouch: false,
407
- zoomOnWheel: false,
408
- // calculates the correct width & height of the container
409
- // instead of setting them to default dimensions (200px x 100px) when 'resize' is triggered
410
- minContainerHeight: -1,
411
- minContainerWidth: -1,
412
-
413
- ready: (event) => {
414
- resolve()
415
- },
416
-
417
- crop: (event) => {
418
- const rendition = this.getCurrentRendition()
419
- if (rendition) {
420
- this.updateRenditionPreview(
421
- rendition,
422
- this._croptool.getCroppedCanvas(),
423
- )
424
- }
425
- },
426
-
427
- // synonymous with "the crop was changed"
428
- cropend: (event) => {
429
- const rendition = this.getCurrentRendition()
430
- const cropData = this._croptool.getCropBoxData()
431
- if (rendition) {
432
- rendition.modifiedCropData = cropData
433
- this.showRenditionResetButton(rendition)
434
- const renditionEl = this.getRenditionElFromRendition(rendition)
435
- renditionEl.classList.add('ImageEditor-sizeModified')
436
- this.setCropInputValues(rendition)
437
- }
438
- },
439
- })
440
- })
441
- }
442
-
443
- // To trigger the (private) resize handler in Cropper.js utility which renders the canvas again in the right size.
444
- // This is needed because it sometimes incorrectly resizes the canvas within the container
445
- // causing the image to be cropped off or misaligned within the container, usually when rotating.
446
- // see: Cropper.js - handlers.resize()
447
- repaint(height) {
448
- const imageEditorMain = this.getElement().querySelector('.ImageEditor-main')
449
- const containerHeight =
450
- height - document.querySelector('.Page-header').offsetHeight
451
- const containerWidth = imageEditorMain.getBoundingClientRect().width
452
- window.dispatchEvent(new Event('resize'))
453
- if (containerHeight === this._croptool?.getCanvasData().height) {
454
- this._croptool.setCanvasData({
455
- top: this._croptool.getCanvasData().top + 24,
456
- left: this._croptool.getCanvasData().left + 24,
457
- height: this._croptool.getCanvasData().height - 48,
458
- })
459
- }
460
- if (containerWidth === this._croptool?.getCanvasData().width) {
461
- this._croptool.setCanvasData({
462
- top: this._croptool.getCanvasData().top + 24,
463
- left: this._croptool.getCanvasData().left + 24,
464
- width: this._croptool.getCanvasData().width - 48,
465
- })
466
- }
467
- this.updateAllRenditionPreviews()
468
- }
469
-
470
- // Returns the x,y whole percentage values eg: x: 75.33 (%)
471
- // If a focus point is not set this returns null values.
472
- getFocusCoords(cropRatio) {
473
- let inputX, inputY
474
- if (this._cropSelect) {
475
- const index = this._cropSelect.selectedIndex
476
- if (!cropRatio) {
477
- cropRatio = this._cropSelect.options[index].value
478
- }
479
- }
480
- if (!cropRatio || cropRatio === '' || cropRatio === 'independent') {
481
- inputX = this.getElement().querySelector('input[name$=".focusX"]')
482
- inputY = this.getElement().querySelector('input[name$=".focusY"]')
483
- } else {
484
- inputX = this.getElement().querySelector(
485
- 'input[name$=".focusX.' + cropRatio + '"]',
486
- )
487
- inputY = this.getElement().querySelector(
488
- 'input[name$=".focusY.' + cropRatio + '"]',
489
- )
490
- if (inputX.value === '' || inputY.value === '') {
491
- inputX = this.getElement().querySelector('input[name$=".focusX"]')
492
- inputY = this.getElement().querySelector('input[name$=".focusY"]')
493
- }
494
- }
495
- return {
496
- xPercent: inputX.value !== '' ? inputX.value * 100 : null,
497
- yPercent: inputY.value !== '' ? inputY.value * 100 : null,
498
- }
499
- }
500
-
501
- getDefaults() {
502
- let defaults = {
503
- brightness: 0,
504
- contrast: 0,
505
- flipH: false,
506
- flipV: false,
507
- grayscale: false,
508
- invert: false,
509
- rotate: '0',
510
- sepia: false,
511
- 'no-filter': true,
512
- }
513
-
514
- if (!this._isContextual) return defaults
515
- // In contextual image editing
516
- // Update the image defaults with original shared image edits (if there are any)
517
- let defaultMetadata = this._element.dataset.defaultEdits
518
- if (!defaultMetadata) return defaults
519
- const parentDefaults = JSON.parse(defaultMetadata)
520
- const { grayscale, sepia, invert } = parentDefaults
521
- if (grayscale || sepia || invert) {
522
- parentDefaults['no-filter'] = false
523
- }
524
- defaults = Object.assign(defaults, parentDefaults)
525
- return defaults
526
- }
527
-
528
- getEditValues() {
529
- const editorAccess = this.getElement().getAttribute('editor')
530
- return [
531
- ...(editorAccess === 'true'
532
- ? this.getElement().querySelectorAll('.ImageEditor-edit input[name]')
533
- : Object.entries(JSON.parse(this.getElement().getAttribute('edits')))),
534
- ].reduce((accum, val) => {
535
- if (editorAccess === 'true') {
536
- const propname = val.name.substring(val.name.lastIndexOf('.') + 1)
537
- Object.assign(accum, {
538
- [propname]: val.type === 'checkbox' ? val.checked : val.value,
539
- })
540
- } else {
541
- const propname = val[0]
542
- Object.assign(accum, {
543
- [propname]: val[1],
544
- })
545
- }
546
- return accum
547
- }, {})
548
- }
549
-
550
- createHotspotUI(tab, areaWidthUnscaled, areaHeightUnscaled, callback) {
551
- let isDragging = false
552
-
553
- const inputAddHotspot = this.getForm().querySelector(
554
- '.inputContainer[data-name$=".hotspots"]',
555
- )
556
-
557
- // Abort creating the Hotspot UI if the hotspot form controls aren't present.
558
- if (!inputAddHotspot) return
559
-
560
- // Reveal hotspot inputs within the image editor hotspot tab UI.
561
- inputAddHotspot.style.display = 'block'
562
-
563
- const hotspotContainer = create('div', {
564
- class: 'ImageEditor-hotspot-container',
565
- onclick: (event) => {
566
- event.preventDefault()
567
- },
568
- })
569
-
570
- const hotspotArea = create('div', {
571
- class: 'ImageEditor-hotspotableArea',
572
- onclick: (event) => {
573
- // Allow positioning of hotspot overlays when hotspots tab is active.
574
- if (!hotspotContainer.classList.contains('is-hidden')) {
575
- const clickCoords = this.getClickCoordsLocal(event.target, event)
576
- clickCoords.xPercent *= 100
577
- clickCoords.yPercent *= 100
578
- // NOTE: this is where we could add the feature to create a new hotspot overlay when clicking the image.
579
- }
580
- },
581
- })
582
-
583
- hotspotContainer.appendChild(hotspotArea)
584
-
585
- this.getElement()
586
- .querySelector('.ImageEditor-main')
587
- .appendChild(hotspotContainer)
588
-
589
- tab.append(inputAddHotspot)
590
-
591
- onFind(tab, '.ImageEditor-hotspot-item', (hotspotItem) => {
592
- const onClassChange = (mutations) => {
593
- mutations.forEach((mutation) => {
594
- const target = mutation.target
595
- const isRemoving = target.classList.contains('is-removing')
596
- isRemoving
597
- ? _handleHotspotItemRemove(hotspotItem)
598
- : _handleHotspotItemRestore(hotspotItem)
599
- })
600
- }
601
- const observer = new MutationObserver(onClassChange)
602
- observer.observe(hotspotItem, { attributeFilter: ['class'] })
603
- })
604
-
605
- // Reflow the hotspot area UI by dispatching a browser-resize event and
606
- // recalculating the relative pixel coordinates for all hotspot overlays.
607
- // (needed to support responsive viewport).
608
- const reflow = () => {
609
- // Position each hotspot overlay.
610
- tab
611
- .querySelectorAll('.ImageEditor-hotspot-item')
612
- .forEach((hotspotItem) => {
613
- const spotSizeOffset = SPOT_SIZE / 2
614
- const { x: offsetX, y: offsetY } = hotspotArea.getBoundingClientRect()
615
- const areaWidth = hotspotArea.offsetWidth
616
- const areaHeight = hotspotArea.offsetHeight
617
- const draggable = hotspotItem.draggableRef
618
-
619
- // Convert from normalized coordinates back to the relative pixels
620
- const { x: normalX, y: normalY } = _getInputCoordsByItem(hotspotItem)
621
- const { xPercent, yPercent } = _getNormalizedPointInPercent({
622
- x: normalX,
623
- y: normalY,
624
- })
625
- const relX = areaWidth * (xPercent / 100)
626
- const relY = areaHeight * (yPercent / 100)
627
-
628
- // Sync the draggable model (specific to the library)
629
- draggable.position()
630
-
631
- // Reposition the draggable element to the relative pixels.
632
- draggable.left = relX + offsetX - spotSizeOffset
633
- draggable.top = relY + offsetY - spotSizeOffset
634
- })
635
-
636
- window.dispatchEvent(new Event('resize'))
637
- }
638
-
639
- const _handleHotspotItemRemove = (itemEl) => {
640
- itemEl.hotspotRefEl.classList.add('is-removing')
641
- }
642
-
643
- const _handleHotspotItemRestore = (itemEl) => {
644
- itemEl.hotspotRefEl.classList.remove('is-removing')
645
- }
646
-
647
- const _getInputCoordsByItem = (item) => {
648
- return {
649
- x: item.querySelector('input[name$="cms.image.x"]').value,
650
- y: item.querySelector('input[name$="cms.image.y"]').value,
651
- }
652
- }
653
-
654
- // Convert a Point (x,y) from the scaled coordinate space in the editor UI
655
- // to a new Point (x,y) as a percentage relative to the natural image's coordinate space.
656
- const _getNormalizedPointInPercent = ({ x, y }) => {
657
- const xPercent = (x / areaWidthUnscaled) * 100
658
- const yPercent = (y / areaHeightUnscaled) * 100
659
-
660
- return {
661
- xPercent,
662
- yPercent,
663
- }
664
- }
665
-
666
- // Visualize all the Hotspot overlays whether they are pre-existing or are added at runtime.
667
- const _visualizeHotspots = () => {
668
- let currentHotspot = null
669
-
670
- const _setInputCoordsByItem = (item, coords) => {
671
- const inputX = item.querySelector('input[name$="/cms.image.x"]')
672
- const inputY = item.querySelector('input[name$="/cms.image.y"]')
673
-
674
- inputX.value = coords.x
675
- inputY.value = coords.y
676
-
677
- inputX.dispatchEvent(new Event('change', { bubbles: true }))
678
- inputY.dispatchEvent(new Event('change', { bubbles: true }))
679
- }
680
-
681
- // Convert a Point (x,y) from the scaled coordinate space in the editor UI
682
- // to a new Point (x,y) as pixels relative to the natural image's coordinate space.
683
- const _getNormalizedPointInPixels = ({ x, y }) => {
684
- const scaleX = hotspotArea.offsetWidth / areaWidthUnscaled
685
- const scaleY = hotspotArea.offsetHeight / areaHeightUnscaled
686
-
687
- x = parseInt(x / scaleX)
688
- y = parseInt(y / scaleY)
689
-
690
- return {
691
- x,
692
- y,
693
- }
694
- }
695
-
696
- const _updatePosition = (spot, position) => {
697
- // Get the offset coords since drag API returns global coords.
698
- const parentCoords = spot.parentNode.getBoundingClientRect()
699
- let localLeft = position.left - parentCoords.left
700
- let localTop = position.top - parentCoords.top
701
-
702
- // convert from top-left origin to center origin based on spot size
703
- localLeft = localLeft + spot.offsetWidth / 2
704
- localTop = localTop + spot.offsetHeight / 2
705
-
706
- _setInputCoordsByItem(
707
- spot.itemRefEl,
708
- _getNormalizedPointInPixels({ x: localLeft, y: localTop }),
709
- )
710
- }
711
-
712
- const _collapseAllHotspotItems = (item) => {
713
- const hotspotItemList = item.parentNode
714
-
715
- // Collapse all hotspot items in the list.
716
- hotspotItemList
717
- .querySelectorAll(
718
- '.ImageEditor-hotspot-item:not(.collapsed):not(.is-collapsed)',
719
- )
720
- .forEach((item) => {
721
- item.classList.add('is-collapsed')
722
- item.classList.add('collapsed')
723
- })
724
- }
725
-
726
- // Create a single hotspot overlay given the item that has the form inputs and an x/y initial position.
727
- // Returns a reference to the newly created element.
728
- const _visualizeHotspot = ({ item, x, y, label }) => {
729
- const _handleClick = (spot) => {
730
- const hotspotItem = spot.itemRefEl
731
-
732
- spot.classList.remove('is-added')
733
-
734
- if (hotspotItem.classList.contains('is-collapsed')) {
735
- hotspotItem.querySelector(':scope .repeatableLabel')?.click()
736
- }
737
-
738
- // Delayed to provide a better UX.
739
- setTimeout(() => {
740
- hotspotItem.scrollIntoView({
741
- behavior: 'smooth',
742
- block: 'start',
743
- inline: 'nearest',
744
- })
745
- }, 100)
746
-
747
- currentHotspot = spot
748
- }
749
-
750
- const spot = create('div', {
751
- className: 'ImageEditor-hotspot',
752
- onclick: (event) => {
753
- _handleClick(event.target)
754
- },
755
- onmouseenter: (event) => {
756
- if (!isDragging) {
757
- event.target.classList.remove('is-added')
758
- event.target.classList.add('is-hovered')
759
- event.target.itemRefEl.classList.add('is-hovered')
760
- event.preventDefault()
761
- event.stopPropagation()
762
- }
763
- },
764
- onmouseleave: (event) => {
765
- if (!isDragging) {
766
- event.target.classList.remove('is-hovered')
767
- event.target.itemRefEl.classList.remove('is-hovered')
768
- event.preventDefault()
769
- event.stopPropagation()
770
- }
771
- },
772
- })
773
-
774
- spot.innerHTML = label
775
- spot.itemRefEl = item
776
- hotspotArea.appendChild(spot)
777
-
778
- // Make the hotspot overlay draggable.
779
- const draggable = new PlainDraggable(spot, {
780
- leftTop: false,
781
- containment: hotspotArea,
782
- })
783
-
784
- PlainDraggable.draggableClass = 'is-draggable'
785
- PlainDraggable.draggingClass = 'is-dragging'
786
- PlainDraggable.movingClass = 'is-moving'
787
-
788
- draggable.onMove = (position) => {
789
- hotspotArea.classList.add('is-dragging')
790
- isDragging = true
791
- }
792
-
793
- draggable.onDragEnd = (position) => {
794
- isDragging = false
795
- hotspotArea.classList.remove('is-dragging')
796
- _updatePosition(spot, position)
797
- }
798
-
799
- // keep a reference to the draggable object on the DOM node.
800
- item.draggableRef = draggable
801
-
802
- // Initially position a runtime added spot.
803
- if (x === '' && y === '') {
804
- // Gentle randomization of starting coordinates.
805
- // This positions the overlay around the center of the image.
806
- const noise = new Noise()
807
- const distribution = 0.3
808
- const plusOrMinusX = Math.random() < 0.5 ? -1 : 1
809
- const plusOrMinusY = Math.random() < 0.5 ? -1 : 1
810
- const parentCoords = spot.parentNode.getBoundingClientRect()
811
- const globalCenterX = parentCoords.left + parentCoords.width / 2
812
- const globalCenterY = parentCoords.top + parentCoords.height / 2
813
- const globalLeft =
814
- globalCenterX +
815
- plusOrMinusX *
816
- (SPOT_SIZE * noise.getVal(plusOrMinusX * distribution))
817
- const globalTop =
818
- globalCenterY +
819
- plusOrMinusY *
820
- (SPOT_SIZE * noise.getVal(plusOrMinusY * distribution))
821
- _updatePosition(spot, { left: globalLeft, top: globalTop })
822
- reflow()
823
- spot.classList.add('is-added')
824
- }
825
-
826
- return spot
827
- }
828
-
829
- // Process Hotspot items from the list.
830
- onFind(tab, '.RCIG-item[data-hotspot-point]', (item) => {
831
- const { x, y } = _getInputCoordsByItem(item)
832
-
833
- // Collapse hotspot items.
834
- _collapseAllHotspotItems(item)
835
-
836
- // Scroll to this item in the list.
837
- // Delayed to provide a better UX.
838
- setTimeout(() => {
839
- item.scrollIntoView({
840
- behavior: 'smooth',
841
- block: 'start',
842
- inline: 'nearest',
843
- })
844
- }, 100)
845
-
846
- item.classList.add('ImageEditor-hotspot-item')
847
- item.hotspotRefEl = _visualizeHotspot({
848
- item,
849
- x,
850
- y,
851
- label: Array.from(item.parentElement.children).indexOf(item) + 1,
852
- })
853
-
854
- item.onmouseenter = (event) => {
855
- event.target.hotspotRefEl.classList.remove('is-added')
856
- event.target.hotspotRefEl.classList.add('is-hovered')
857
- event.preventDefault()
858
- event.stopPropagation()
859
- }
860
-
861
- item.onmouseleave = (event) => {
862
- event.target.hotspotRefEl.classList.remove('is-hovered')
863
- event.preventDefault()
864
- event.stopPropagation()
865
- }
866
- })
867
- }
868
-
869
- // Observe size changes to the cropped image to keep the hotspot area
870
- // dimensions and position in sync (mostly to support browser resize).
871
- const mutations = new MutationObserver((mutations) => {
872
- for (const mutation of mutations) {
873
- if (mutation.attributeName === 'style') {
874
- _hotspotAreaResize(mutation.target)
875
- }
876
- }
877
- })
878
- mutations.observe(this.getElement().querySelector('.cropper-canvas'), {
879
- attributes: true,
880
- })
881
-
882
- // Resize the hotspot area according to the reference element
883
- const _hotspotAreaResize = (referenceEl) => {
884
- hotspotArea.style.height = `${referenceEl.offsetHeight}px`
885
- hotspotArea.style.width = `${referenceEl.offsetWidth}px`
886
- hotspotArea.style.transform = referenceEl.style.transform
887
-
888
- reflow()
889
- }
890
-
891
- // Show all hotspot overlays.
892
- const reveal = () => {
893
- tab
894
- .querySelectorAll('.ImageEditor-hotspot-item')
895
- .forEach((hotspotItem) => {
896
- hotspotItem.hotspotRefEl.classList.remove('is-hidden')
897
- })
898
- }
899
-
900
- // Initialize the size of the hotspot area to match the croppable canvas.
901
- _hotspotAreaResize(this.getElement().querySelector('.cropper-canvas'))
902
-
903
- _visualizeHotspots()
904
-
905
- // Hide all the initial hotspot overlays while they are being positioned.
906
- hotspotArea
907
- .querySelectorAll('.ImageEditor-hotspot')
908
- .forEach((hotspotOverlay) => {
909
- hotspotOverlay.classList.add('is-hidden')
910
- })
911
-
912
- callback()
913
-
914
- return {
915
- reflow,
916
- reveal,
917
- }
918
- }
919
-
920
- createFocusUI(preview, syncRenditions) {
921
- const focusPoint = create('div', {
922
- className: 'ImageEditor-focus-point',
923
- title: tooltips.imageEditor.focus,
924
- })
925
-
926
- let initialFocusX = this.getElement().querySelector(
927
- 'input[name$=".focusX"]',
928
- ).value
929
- let initialFocusY = this.getElement().querySelector(
930
- 'input[name$=".focusY"]',
931
- ).value
932
-
933
- if (!initialFocusX && !initialFocusY) {
934
- if (this.getImplicitFocus()) {
935
- const implicitFocus = this.getImplicitFocus().split(',')
936
- initialFocusX = implicitFocus[0]
937
- initialFocusY = implicitFocus[1]
938
- focusPoint.classList.add('is-implicit')
939
- preview
940
- .querySelector('.ImageEditor-implicitMessage')
941
- .classList.remove('is-hidden')
942
- }
943
- }
944
-
945
- const focusRender = (xPercent, yPercent) => {
946
- focusPoint.style.left = `${xPercent}%`
947
- focusPoint.style.top = `${yPercent}%`
948
- }
949
-
950
- const enable = () => {
951
- focusContainer.style.pointerEvents = 'initial'
952
- }
953
-
954
- const disable = () => {
955
- focusContainer.style.pointerEvents = 'none'
956
- }
957
-
958
- const resetFocusValues = () => {
959
- const cropSizes = this._cropSelect.querySelectorAll(
960
- 'option:not([value=""])',
961
- )
962
- cropSizes.forEach((size) => {
963
- const inputX = this.getElement().querySelector(
964
- 'input[name$=".focusX.' + size.value + '"]',
965
- )
966
- const inputY = this.getElement().querySelector(
967
- 'input[name$=".focusY.' + size.value + '"]',
968
- )
969
- if (inputX && inputY) {
970
- inputX.value = ''
971
- inputY.value = ''
972
- }
973
- })
974
- }
975
-
976
- const setFocusInputValues = ({ xPercent, yPercent }) => {
977
- const index = this._cropSelect.selectedIndex
978
- const selectedCrop = this._cropSelect.options[index]
979
- const inputX =
980
- selectedCrop.value === ''
981
- ? this.getElement().querySelector('input[name$=".focusX"]')
982
- : this.getElement().querySelector(
983
- 'input[name$=".focusX.' + selectedCrop.value + '"]',
984
- )
985
- const inputY =
986
- selectedCrop.value === ''
987
- ? this.getElement().querySelector('input[name$=".focusY"]')
988
- : this.getElement().querySelector(
989
- 'input[name$=".focusY.' + selectedCrop.value + '"]',
990
- )
991
- const coords = getFocusCoordinatesNormalized({ xPercent, yPercent })
992
-
993
- inputX.value = coords.xPercent / 100
994
- inputY.value = coords.yPercent / 100
995
-
996
- if (selectedCrop.value === '') {
997
- resetFocusValues()
998
- }
999
-
1000
- inputX.dispatchEvent(new Event('change', { bubbles: true }))
1001
- inputY.dispatchEvent(new Event('change', { bubbles: true }))
1002
- }
1003
-
1004
- const moveTo = ({ xPercent, yPercent }) => {
1005
- if (!find(preview, '.ImageEditor-focus-point')) {
1006
- preview.append(focusPoint)
1007
- }
1008
- focusPoint.classList.remove('is-hidden')
1009
- focusRender(xPercent, yPercent)
1010
- }
1011
-
1012
- // Normalizing the coordinates undoes the client-side transformations (eg: rotation and flipping)
1013
- // This is needed for storing the x/y coordinates on the backend.
1014
- const getFocusCoordinatesNormalized = ({ xPercent, yPercent }) => {
1015
- let focusPointData = {}
1016
-
1017
- // no focus point is set; exit early.
1018
- if (xPercent === '' && yPercent === '') {
1019
- return null
1020
- }
1021
-
1022
- const { rotate, scaleX, scaleY } = this._croptool.getImageData()
1023
- const { width, height } = this._croptool.getImageData()
1024
- const { width: nWidth, height: nHeight } = this._croptool.getCanvasData()
1025
-
1026
- const x = (width * xPercent) / 100
1027
- const y = (height * yPercent) / 100
1028
-
1029
- focusPointData = { xPercent, yPercent }
1030
-
1031
- const rotateCoords = (r, x, y, width, height, focusPointData) => {
1032
- let newX
1033
- let newY
1034
-
1035
- if (r === 90 || r === -270) {
1036
- newX = y
1037
- newY = width - x
1038
- } else if (r === 180 || r === -180) {
1039
- newX = width - x
1040
- newY = height - y
1041
- } else if (r === -90 || r === 270) {
1042
- newX = height - y
1043
- newY = x
1044
- } else {
1045
- newX = x
1046
- newY = y
1047
- }
1048
- focusPointData.xPercent = (newX / nWidth) * 100
1049
- focusPointData.yPercent = (newY / nHeight) * 100
1050
- }
1051
-
1052
- rotateCoords(rotate, x, y, width, height, focusPointData)
1053
-
1054
- // Translate point for flip:
1055
- if (scaleX === -1 && scaleY === -1) {
1056
- // if both have flipped, just calculate the new rotation from that.
1057
- let newRotate = rotate - 180
1058
- rotateCoords(newRotate, x, y, width, height, focusPointData)
1059
- } else {
1060
- // image flipped horizontally?
1061
- if (scaleX === -1) {
1062
- if (rotate === 90 || rotate === -270) {
1063
- focusPointData.xPercent = 100 - focusPointData.xPercent
1064
- } else if (rotate === 180 || rotate === -180) {
1065
- focusPointData.xPercent = xPercent
1066
- } else if (rotate === -90 || rotate === 270) {
1067
- focusPointData.xPercent = 100 - focusPointData.xPercent
1068
- } else {
1069
- focusPointData.xPercent = 100 - xPercent
1070
- }
1071
- }
1072
-
1073
- // image flipped vertically?
1074
- if (scaleY === -1) {
1075
- if (rotate === 90 || rotate === -270) {
1076
- focusPointData.xPercent = yPercent
1077
- focusPointData.yPercent = xPercent
1078
- } else if (rotate === 180 || rotate === -180) {
1079
- focusPointData.xPercent = 100 - xPercent
1080
- focusPointData.yPercent = yPercent
1081
- } else if (rotate === -90 || rotate === 270) {
1082
- focusPointData.xPercent = 100 - focusPointData.yPercent
1083
- focusPointData.yPercent = focusPointData.xPercent
1084
- } else {
1085
- focusPointData.yPercent = 100 - yPercent
1086
- }
1087
- }
1088
- }
1089
-
1090
- return {
1091
- x: focusPointData.xPercent / 100,
1092
- y: focusPointData.yPercent / 100,
1093
- xPercent: focusPointData.xPercent,
1094
- yPercent: focusPointData.yPercent,
1095
- }
1096
- }
1097
-
1098
- // returns normalized x/y percentages which factor in rotation.
1099
- // typically used for the realtime/client positioning.
1100
- const getFocusCoordinatesRelative = (cropRatio) => {
1101
- let focusPointData = {}
1102
-
1103
- const focusPointCoords = this.getFocusCoords(cropRatio)
1104
-
1105
- // no focus point is set; exit early.
1106
- if (
1107
- focusPointCoords.xPercent === null &&
1108
- focusPointCoords.yPercent === null
1109
- ) {
1110
- if (this.getImplicitFocus()) {
1111
- const implicitFocus = this.getImplicitFocus().split(',')
1112
- focusPointCoords.xPercent = implicitFocus[0] * 100
1113
- focusPointCoords.yPercent = implicitFocus[1] * 100
1114
- } else {
1115
- return null
1116
- }
1117
- }
1118
-
1119
- const { rotate, scaleX, scaleY } = this._croptool.getImageData()
1120
- const { width, height } = this._croptool.getImageData()
1121
- const { width: nWidth, height: nHeight } = this._croptool.getCanvasData()
1122
- const x = (width * focusPointCoords.xPercent) / 100
1123
- const y = (height * focusPointCoords.yPercent) / 100
1124
-
1125
- focusPointData = { ...focusPointCoords }
1126
-
1127
- const rotateCoords = (r, x, y, width, height, focusPointData) => {
1128
- let newX
1129
- let newY
1130
-
1131
- if (r === 90 || r === -270) {
1132
- newX = height - y
1133
- newY = x
1134
- } else if (r === 180 || r === -180) {
1135
- newX = width - x
1136
- newY = height - y
1137
- } else if (r === -90 || r === 270) {
1138
- newX = y
1139
- newY = width - x
1140
- } else {
1141
- newX = x
1142
- newY = y
1143
- }
1144
- focusPointData.xPercent = (newX / nWidth) * 100
1145
- focusPointData.yPercent = (newY / nHeight) * 100
1146
- }
1147
-
1148
- rotateCoords(rotate, x, y, width, height, focusPointData)
1149
-
1150
- // Translate point for flip:
1151
- if (scaleX === -1 && scaleY === -1) {
1152
- // if both have flipped, just calculate the new rotation from that.
1153
- let newRotate = rotate - 180
1154
- rotateCoords(newRotate, x, y, width, height, focusPointData)
1155
- } else {
1156
- // image flipped horizontally?
1157
- if (scaleX === -1) {
1158
- if (rotate === 90 || rotate === -270) {
1159
- focusPointData.yPercent = 100 - focusPointCoords.xPercent
1160
- } else if (rotate === 180 || rotate === -180) {
1161
- focusPointData.xPercent = focusPointCoords.xPercent
1162
- } else if (rotate === -90 || rotate === 270) {
1163
- focusPointData.yPercent = focusPointCoords.xPercent
1164
- } else {
1165
- focusPointData.xPercent = 100 - focusPointCoords.xPercent
1166
- }
1167
- }
1168
-
1169
- // image flipped vertically?
1170
- if (scaleY === -1) {
1171
- if (rotate === 90 || rotate === -270) {
1172
- focusPointData.xPercent = focusPointCoords.yPercent
1173
- } else if (rotate === 180 || rotate === -180) {
1174
- focusPointData.xPercent = 100 - focusPointCoords.xPercent
1175
- focusPointData.yPercent = focusPointCoords.yPercent
1176
- } else if (rotate === 270) {
1177
- focusPointData.xPercent = 100 - focusPointData.xPercent
1178
- } else {
1179
- focusPointData.yPercent = 100 - focusPointCoords.yPercent
1180
- }
1181
- }
1182
- }
1183
-
1184
- return {
1185
- x: focusPointData.xPercent / 100,
1186
- y: focusPointData.yPercent / 100,
1187
- xPercent: focusPointData.xPercent,
1188
- yPercent: focusPointData.yPercent,
1189
- }
1190
- }
1191
-
1192
- const focusContainer = create('div', {
1193
- class: 'ImageEditor-focus-container',
1194
- dblclick: (event) => event.preventDefault(),
1195
- onclick: (event) => {
1196
- event.preventDefault()
1197
- const focusPoint = find(preview, '.ImageEditor-focus-point')
1198
- const implicitMessage = find(preview, '.ImageEditor-implicitMessage')
1199
- if (focusPoint) {
1200
- focusPoint.classList.remove('is-implicit')
1201
- }
1202
- if (implicitMessage) {
1203
- implicitMessage.classList.add('is-hidden')
1204
- }
1205
- const message = find(preview, '.ImageEditor-focusMessage')
1206
- message.classList.add('is-hidden')
1207
- const clickCoords = this.getClickCoordsLocal(event.target, event)
1208
- clickCoords.xPercent *= 100
1209
- clickCoords.yPercent *= 100
1210
- const { xPercent, yPercent } = clickCoords
1211
- setFocusInputValues({ xPercent, yPercent })
1212
- moveTo({ xPercent, yPercent })
1213
- this.showPreview()
1214
- syncRenditions()
1215
- },
1216
- })
1217
-
1218
- const imageContainer = this.getPreviewContainer()
1219
- imageContainer.appendChild(focusContainer)
1220
-
1221
- if (initialFocusX !== '' && initialFocusY !== '') {
1222
- const xPercent = initialFocusX * 100
1223
- const yPercent = initialFocusY * 100
1224
- moveTo({ xPercent, yPercent })
1225
- }
1226
-
1227
- return {
1228
- disable,
1229
- enable,
1230
- moveTo,
1231
- getFocusCoordinatesRelative,
1232
- }
1233
- }
1234
-
1235
- applyAllEdits(data) {
1236
- const {
1237
- brightness,
1238
- contrast,
1239
- invert,
1240
- grayscale,
1241
- sepia,
1242
- flipH,
1243
- flipV,
1244
- rotate,
1245
- } = { ...data }
1246
-
1247
- this.applyFilter('brightness', `${this.convertValueToPercent(brightness)}%`)
1248
- this.applyFilter('contrast', `${this.convertValueToPercent(contrast)}%`)
1249
- this.applyFilter('sepia', sepia ? '100%' : null)
1250
- this.applyFilter('invert', invert ? '100%' : null)
1251
- this.applyFilter('grayscale', grayscale ? '100%' : null)
1252
-
1253
- flipH
1254
- ? this._croptool.scale(-1, flipV ? -1 : 1)
1255
- : this._croptool.scale(1, flipV ? -1 : 1)
1256
-
1257
- flipV
1258
- ? this._croptool.scale(flipH ? -1 : 1, -1)
1259
- : this._croptool.scale(flipH ? -1 : 1, 1)
1260
-
1261
- if (rotate) {
1262
- this._croptool.rotateTo(rotate)
1263
- this.moveFocusCoords()
1264
- this.updateAllRenditionPreviews()
1265
- this.repaint(document.body.offsetHeight)
1266
- }
1267
- }
1268
-
1269
- createEditUI(target, ui, croptool, focusUI, syncRenditions, repaintHandler) {
1270
- const defaults = this.getDefaults()
1271
- const tools = target.querySelector('.imageEditor-tools')
1272
- const adjustments = target.querySelector('.imageEditor-edit')
1273
-
1274
- const editableArea = create('div', {
1275
- class: 'ImageEditor-editableArea',
1276
- onclick: (event) => event.preventDefault(),
1277
- dblclick: (event) => event.preventDefault(),
1278
- })
1279
-
1280
- this.getElement()
1281
- .querySelector('.ImageEditor-main')
1282
- .appendChild(editableArea)
1283
-
1284
- // Server values range from -1 to 1 and must be converted to percentage.
1285
- const convertValueToPercent = (val) => {
1286
- let amount = parseFloat(val)
1287
- amount = amount * 100 + 100
1288
- return amount
1289
- }
1290
-
1291
- const e = this.getElement()
1292
- const brightnessInput = e.querySelector('input[name$=".brightness"]')
1293
- const contrastInput = e.querySelector('input[name$=".contrast"]')
1294
- const sepiaToggle = e.querySelector('input[name$=".sepia"]')
1295
- const invertToggle = e.querySelector('input[name$=".invert"]')
1296
- const grayscaleToggle = e.querySelector('input[name$=".grayscale"]')
1297
- const noFilterToggle = e.querySelector('input[name$=".no-filter"]')
1298
-
1299
- const updateAllInputs = (data) => {
1300
- const context = this.getElement()
1301
- Object.entries(data).forEach(([key, val]) => {
1302
- const input = context.querySelector(
1303
- `.ImageEditor-edit input[name$='.${key}']`,
1304
- )
1305
- input.type === 'checkbox' ? (input.checked = val) : (input.value = val)
1306
- })
1307
- }
1308
-
1309
- brightnessInput.onchange = (event) => {
1310
- this.applyFilter(
1311
- 'brightness',
1312
- `${convertValueToPercent(event.target.value)}%`,
1313
- )
1314
- }
1315
-
1316
- contrastInput.onchange = (event) => {
1317
- this.applyFilter(
1318
- 'contrast',
1319
- `${convertValueToPercent(event.target.value)}%`,
1320
- )
1321
- }
1322
-
1323
- const filters = {
1324
- grayscale: grayscaleToggle,
1325
- invert: invertToggle,
1326
- sepia: sepiaToggle,
1327
- ['no-filter']: noFilterToggle,
1328
- }
1329
-
1330
- /* The checkbox inputs function as radio buttons, meaning when an input is clicked, it should not be uncheckable if it was already checked */
1331
- const toggleCheckedFilter = (filterName) => {
1332
- if (!filters[filterName].checked) {
1333
- filters[filterName].checked = true
1334
- } else if (filterName !== 'no-filter') {
1335
- this.applyFilter(
1336
- filterName,
1337
- filters[filterName].checked ? '100%' : null,
1338
- )
1339
- }
1340
- for (const filter in filters) {
1341
- if (filter !== filterName) {
1342
- this.applyFilter(filter, null)
1343
- filters[filter].checked = false
1344
- }
1345
- }
1346
- }
1347
-
1348
- sepiaToggle.onchange = () => {
1349
- toggleCheckedFilter('sepia')
1350
- }
1351
-
1352
- invertToggle.onchange = () => {
1353
- toggleCheckedFilter('invert')
1354
- }
1355
-
1356
- grayscaleToggle.onchange = () => {
1357
- toggleCheckedFilter('grayscale')
1358
- }
1359
-
1360
- noFilterToggle.onchange = () => {
1361
- toggleCheckedFilter('no-filter')
1362
- }
1363
-
1364
- const rotateLeftButton = create(
1365
- 'button',
1366
- {
1367
- class: 'ImageEditor-rotate-left',
1368
- 'aria-label': tooltips.imageEditor.rotateLeft,
1369
- title: tooltips.imageEditor.rotateLeft,
1370
- type: 'button',
1371
- onclick: (event) => {
1372
- croptool.rotate(-90)
1373
- this.moveFocusCoords()
1374
- syncRenditions()
1375
- repaintHandler()
1376
- setRotationInputValue(croptool.getData().rotate)
1377
- },
1378
- },
1379
- tooltips.imageEditor.rotateLeft,
1380
- )
1381
-
1382
- const rotateRightButton = create(
1383
- 'button',
1384
- {
1385
- class: 'ImageEditor-rotate-right',
1386
- 'aria-label': tooltips.imageEditor.rotateRight,
1387
- title: tooltips.imageEditor.rotateRight,
1388
- type: 'button',
1389
- onclick: (event) => {
1390
- croptool.rotate(90)
1391
- this.moveFocusCoords()
1392
- syncRenditions()
1393
- repaintHandler()
1394
- setRotationInputValue(croptool.getData().rotate)
1395
- },
1396
- },
1397
- tooltips.imageEditor.rotateRight,
1398
- )
1399
-
1400
- const flipHorizontalButton = create(
1401
- 'button',
1402
- {
1403
- class: 'ImageEditor-flip-h',
1404
- 'aria-label': tooltips.imageEditor.flipH,
1405
- title: tooltips.imageEditor.flipH,
1406
- type: 'button',
1407
- onclick: (event) => {
1408
- const rotation = croptool.getData().rotate
1409
-
1410
- // When the image is rotated onto it's side, the flip behavior has to change since it uses the source
1411
- // image (non-rotated) to base the transformation off of. So, if the image is rotated on it's side, flip vertically.
1412
- rotation === 0 || rotation === 180 || rotation === -180
1413
- ? flipH()
1414
- : flipV()
1415
-
1416
- syncRenditions()
1417
- repaintHandler()
1418
- },
1419
- },
1420
- tooltips.imageEditor.flipH,
1421
- )
1422
-
1423
- const flipVerticalButton = create(
1424
- 'button',
1425
- {
1426
- class: 'ImageEditor-flip-v',
1427
- 'aria-label': tooltips.imageEditor.flipV,
1428
- title: tooltips.imageEditor.flipV,
1429
- type: 'button',
1430
- onclick: (event) => {
1431
- const rotation = croptool.getData().rotate
1432
-
1433
- // When the image is rotated onto it's side, the flip behavior has to change since it uses the source
1434
- // image (non-rotated) to base the transformation off of. So, if the image is rotated on it's side, flip horizontally.
1435
- rotation === 0 || rotation === 180 || rotation === -180
1436
- ? flipV()
1437
- : flipH()
1438
-
1439
- syncRenditions()
1440
- repaintHandler()
1441
- },
1442
- },
1443
- tooltips.imageEditor.flipV,
1444
- )
1445
-
1446
- const resetButton = create(
1447
- 'button',
1448
- {
1449
- class: 'ImageEditor-resetButton',
1450
- 'aria-label': tooltips.reset,
1451
- type: 'button',
1452
- onclick: (event) => {
1453
- event.preventDefault()
1454
- event.stopPropagation()
1455
- this.applyAllEdits(defaults)
1456
- updateAllInputs(defaults)
1457
- this.resetAllRenditionPreviews()
1458
- repaintHandler()
1459
- },
1460
- onkeydown: (event) => {
1461
- if (event.key === 'Tab' && !event.shiftKey) {
1462
- event.preventDefault()
1463
- this._element.querySelector('.TabBar-item.is-selected').focus()
1464
- }
1465
- },
1466
- },
1467
- tooltips.reset,
1468
- )
1469
-
1470
- const actionBar = find(this.getElement(), '.ActionBar')
1471
- actionBar.appendChild(resetButton)
1472
-
1473
- const flipH = () => {
1474
- const isFlippedH = getFlipHInputValue()
1475
- const isFlippedV = getFlipVInputValue()
1476
- croptool.scale(isFlippedH ? 1 : -1, isFlippedV ? -1 : 1)
1477
- this.moveFocusCoords()
1478
- setFlipHInputValue()
1479
- }
1480
-
1481
- const flipV = () => {
1482
- const isFlippedV = getFlipVInputValue()
1483
- const isFlippedH = getFlipHInputValue()
1484
- croptool.scale(isFlippedH ? -1 : 1, isFlippedV ? 1 : -1)
1485
- this.moveFocusCoords()
1486
- setFlipVInputValue()
1487
- }
1488
-
1489
- const getFlipHInputValue = () => {
1490
- const name = 'flipH'
1491
- const input = this.getElement().querySelector(
1492
- `.ImageEditor-edit input[name$='.${name}']`,
1493
- )
1494
-
1495
- return input ? input.checked : false
1496
- }
1497
-
1498
- const setFlipHInputValue = () => {
1499
- const name = 'flipH'
1500
- const input = this.getElement().querySelector(
1501
- `.ImageEditor-edit input[name$='.${name}']`,
1502
- )
1503
- if (input) {
1504
- input.checked = !input.checked
1505
- input.dispatchEvent(new Event('change', { bubbles: true }))
1506
- }
1507
- }
1508
-
1509
- const getFlipVInputValue = () => {
1510
- const name = 'flipV'
1511
- const input = this.getElement().querySelector(
1512
- `.ImageEditor-edit input[name$='.${name}']`,
1513
- )
1514
-
1515
- return input ? input.checked : false
1516
- }
1517
-
1518
- const setFlipVInputValue = () => {
1519
- const name = 'flipV'
1520
- const input = this.getElement().querySelector(
1521
- `.ImageEditor-edit input[name$='.${name}']`,
1522
- )
1523
- if (input) {
1524
- input.checked = !input.checked
1525
- input.dispatchEvent(new Event('change', { bubbles: true }))
1526
- }
1527
- }
1528
-
1529
- const setRotationInputValue = (val) => {
1530
- const name = 'rotate'
1531
- const input = this.getElement().querySelector(
1532
- `.ImageEditor-edit input[name$='.${name}']`,
1533
- )
1534
-
1535
- let adjustedVal = val
1536
-
1537
- // convert values to stay within backend range of -90 -> 180
1538
- if (val === -180) {
1539
- adjustedVal = 180
1540
- } else if (val === -270) {
1541
- adjustedVal = 90
1542
- } else if (val === 270) {
1543
- adjustedVal = -90
1544
- }
1545
-
1546
- if (input) {
1547
- input.value = adjustedVal
1548
- input.dispatchEvent(new Event('change', { bubbles: true }))
1549
- }
1550
- }
1551
-
1552
- const brightnessUI = create(
1553
- 'div',
1554
- {
1555
- class: 'CIG-row',
1556
- },
1557
- create(
1558
- 'label',
1559
- { class: 'CIG-label' },
1560
- tooltips.imageEditor.brightness,
1561
- brightnessInput,
1562
- ),
1563
- )
1564
-
1565
- const contrastUI = create(
1566
- 'div',
1567
- {
1568
- class: 'CIG-row',
1569
- },
1570
- create(
1571
- 'label',
1572
- { class: 'CIG-label' },
1573
- tooltips.imageEditor.contrast,
1574
- contrastInput,
1575
- ),
1576
- )
1577
-
1578
- const filtersUI = create(
1579
- 'div',
1580
- {
1581
- class: 'CIG-row',
1582
- role: 'radiogroup',
1583
- },
1584
- create('div', { class: 'CIG-label' }, 'Filter'),
1585
- create(
1586
- 'div',
1587
- {
1588
- class: 'CIG-small',
1589
- role: 'radio',
1590
- tabIndex: 0,
1591
- },
1592
- create('label', noFilterToggle, tooltips.imageEditor.filters),
1593
- ),
1594
- create(
1595
- 'div',
1596
- {
1597
- class: 'CIG-small',
1598
- role: 'radio',
1599
- tabIndex: 0,
1600
- },
1601
- create('label', sepiaToggle, tooltips.imageEditor.sepia),
1602
- ),
1603
- create(
1604
- 'div',
1605
- {
1606
- class: 'CIG-small',
1607
- role: 'radio',
1608
- tabIndex: 0,
1609
- },
1610
- create('label', invertToggle, tooltips.imageEditor.invert),
1611
- ),
1612
- create(
1613
- 'div',
1614
- {
1615
- class: 'CIG-small',
1616
- role: 'radio',
1617
- tabIndex: 0,
1618
- },
1619
- create('label', grayscaleToggle, tooltips.imageEditor.grayscale),
1620
- ),
1621
- )
1622
-
1623
- const rotationUI = create(
1624
- 'div',
1625
- {
1626
- class: 'CIG-row',
1627
- },
1628
- create(
1629
- 'label',
1630
- { class: 'CIG-label' },
1631
- tooltips.imageEditor.rotate + '/' + tooltips.imageEditor.flip,
1632
- ),
1633
- create('div', { class: 'CIG-small' }, [
1634
- rotateLeftButton,
1635
- rotateRightButton,
1636
- flipHorizontalButton,
1637
- flipVerticalButton,
1638
- ]),
1639
- )
1640
-
1641
- adjustments.append(rotationUI, brightnessUI, contrastUI, filtersUI)
1642
-
1643
- ui.appendChild(tools)
1644
- ui.appendChild(adjustments)
1645
-
1646
- const initialEditValues = this.getEditValues()
1647
- this.applyAllEdits(initialEditValues)
1648
- }
1649
-
1650
- filterRenditionPreviews() {
1651
- const index = this._cropSelect.selectedIndex
1652
- const selectedCrop = this._cropSelect.options[index]
1653
- this.hideCropUI()
1654
- this._renditions.forEach((rendition) => {
1655
- if (rendition.current) {
1656
- this.toggleRendition(rendition)
1657
- }
1658
- const renditionElm = this.getRenditionElFromRendition(rendition)
1659
- renditionElm.classList.toggle(
1660
- 'is-visible',
1661
- selectedCrop.value === ''
1662
- ? true
1663
- : rendition.cropRatio === selectedCrop.value,
1664
- )
1665
- })
1666
- }
1667
-
1668
- moveFocusCoords() {
1669
- if (!this._focusUI) {
1670
- return
1671
- }
1672
- const coords = this._focusUI.getFocusCoordinatesRelative()
1673
- if (coords) {
1674
- this._focusUI.moveTo(coords)
1675
- }
1676
- }
1677
-
1678
- showPreview() {
1679
- const index = this._cropSelect.selectedIndex
1680
- const selectedCrop = this._cropSelect.options[index]
1681
- const group = selectedCrop.value
1682
- const focusData = this._focusUI.getFocusCoordinatesRelative(group)
1683
-
1684
- const { width, height } = this.getPreviewContainer().getBoundingClientRect()
1685
- const inputAspectRatio = Math.floor((width / height) * 100) / 100
1686
- let outputWidth, outputHeight
1687
- let left = 0
1688
- let right = 0
1689
- let top = 0
1690
- let bottom = 0
1691
-
1692
- if (group !== '' && focusData) {
1693
- const aspectRatio = selectedCrop.getAttribute('data-preview-ratio')
1694
- if (inputAspectRatio > aspectRatio) {
1695
- outputWidth = height * aspectRatio
1696
- left = width * focusData.x - outputWidth / 2
1697
- if (left <= 0) {
1698
- left = 0
1699
- }
1700
- right = width - outputWidth - left
1701
- if (right <= 0) {
1702
- right = 0
1703
- left = width - outputWidth
1704
- }
1705
- } else if (inputAspectRatio < aspectRatio) {
1706
- outputHeight = width / aspectRatio
1707
- top = height * focusData.y - outputHeight / 2
1708
- if (top <= 0) {
1709
- top = 0
1710
- }
1711
- bottom = height - outputHeight - top
1712
- if (bottom <= 0) {
1713
- bottom = 0
1714
- top = height - outputHeight
1715
- }
1716
- }
1717
- }
1718
- this.imageMask.style.borderLeftWidth = `${left}px`
1719
- this.imageMask.style.borderRightWidth = `${right}px`
1720
- this.imageMask.style.borderTopWidth = `${top}px`
1721
- this.imageMask.style.borderBottomWidth = `${bottom}px`
1722
- }
1723
-
1724
- getClickCoordsLocal(element, clickEvent) {
1725
- const { width, height, top, left } = element.getBoundingClientRect()
1726
- const offset = {
1727
- top: top + document.body.scrollTop,
1728
- left: left + document.body.scrollLeft,
1729
- }
1730
- let x = Math.ceil(clickEvent.pageX - offset.left) || 1
1731
- let y = Math.ceil(clickEvent.pageY - offset.top) || 1
1732
-
1733
- // bound values
1734
- if (x <= 0) {
1735
- x = 1
1736
- }
1737
- if (y <= 0) {
1738
- y = 1
1739
- }
1740
-
1741
- return {
1742
- x,
1743
- y,
1744
- xPercent: x / width,
1745
- yPercent: y / height,
1746
- width,
1747
- height,
1748
- }
1749
- }
1750
-
1751
- getCropTool() {
1752
- return this._croptool
1753
- }
1754
-
1755
- getElement() {
1756
- return this._element
1757
- }
1758
-
1759
- getForm() {
1760
- return this._form
1761
- }
1762
-
1763
- getImplicitFocus() {
1764
- return this._implicitFocus
1765
- }
1766
-
1767
- getPreviewCanvas() {
1768
- return this.getPreviewContainer().querySelector('canvas')
1769
- }
1770
-
1771
- getPreviewContainer() {
1772
- return this.getElement().parentNode.querySelector('.ImageEditor-preview')
1773
- }
1774
-
1775
- getRenditions() {
1776
- const sizeInfos = {}
1777
- const INPUT_NAMES = [
1778
- 'x',
1779
- 'y',
1780
- 'width',
1781
- 'height',
1782
- 'texts',
1783
- 'textSizes',
1784
- 'textXs',
1785
- 'textYs',
1786
- 'textWidths',
1787
- ]
1788
-
1789
- const imageSizes = this.getElement().querySelector(
1790
- '.imageEditor-sizes table',
1791
- )
1792
-
1793
- if (imageSizes) {
1794
- imageSizes.querySelectorAll('th').forEach((heading) => {
1795
- const row = heading.closest('tr')
1796
- const group = row.getAttribute('data-size-group')
1797
- const name = row.getAttribute('data-size-name')
1798
- const displayName = row.getAttribute('data-display-name')
1799
- const description = row.getAttribute('data-description')
1800
- const width = parseFloat(row.getAttribute('data-size-width'))
1801
- const height = parseFloat(row.getAttribute('data-size-height'))
1802
- let independent = row.getAttribute('data-size-independent') === 'true'
1803
- let aspectRatio = width / height
1804
-
1805
- // If the width or height is zero, then this is a special case
1806
- // and there is no fixed aspect ratio.
1807
- // Instead the other dimension specifies the size, and the
1808
- // width or height should be adjusted automatically.
1809
- if (width === 0 || height === 0) {
1810
- aspectRatio = 0
1811
- independent = true
1812
- }
1813
-
1814
- // Create pointers to each of the hidden inputs that this size uses.
1815
- const inputs = {}
1816
- INPUT_NAMES.forEach((name) => {
1817
- inputs[name] = row.querySelector(`input[name$='.${name}']`)
1818
- })
1819
-
1820
- const focusCrop = this.getFocusCoords()
1821
-
1822
- // Save the size information so we can use it later
1823
- sizeInfos[name] = {
1824
- group,
1825
- name,
1826
- description,
1827
- displayName,
1828
- inputs,
1829
- independent,
1830
- width,
1831
- height,
1832
- aspectRatio,
1833
- focusCrop,
1834
- }
1835
- })
1836
- }
1837
-
1838
- return sizeInfos
1839
- }
1840
-
1841
- flattenAndSortRenditions(renditions) {
1842
- const renditionsMap = new Map()
1843
-
1844
- const createRenditionEntry = (rendition) => {
1845
- return {
1846
- name: rendition.name,
1847
- cropRatio: rendition.group,
1848
- sizeInfos: { [rendition.name]: { ...renditions[rendition.name] } },
1849
- }
1850
- }
1851
-
1852
- const sort = (map) => {
1853
- return new Map(
1854
- Array.from(map).sort((a, b) => {
1855
- return a[0] - b[0]
1856
- }),
1857
- )
1858
- }
1859
-
1860
- for (const key in renditions) {
1861
- const rendition = renditions[key]
1862
- renditionsMap.set(rendition.name, createRenditionEntry(rendition))
1863
- }
1864
-
1865
- return sort(renditionsMap)
1866
- }
1867
-
1868
- hideCropUI() {
1869
- this._croptool.clear()
1870
- // this._croptool.disable()
1871
- }
1872
-
1873
- showCropUI() {
1874
- this._croptool.enable()
1875
- const currRendition = this.getCurrentRendition()
1876
- if (currRendition) {
1877
- currRendition.current = false
1878
- this.setCurrentRendition(currRendition)
1879
- }
1880
- }
1881
-
1882
- hideEditUI() {
1883
- this.getElement()
1884
- .querySelector('.ImageEditor-editableArea')
1885
- .classList.add('is-hidden')
1886
- }
1887
-
1888
- showEditUI() {
1889
- this.getElement()
1890
- .querySelector('.ImageEditor-editableArea')
1891
- .classList.remove('is-hidden')
1892
- }
1893
-
1894
- hideHotspotUI() {
1895
- this.getElement()
1896
- .querySelector('.ImageEditor-hotspot-container')
1897
- .classList.add('is-hidden')
1898
- }
1899
-
1900
- showHotspotUI() {
1901
- this.getElement()
1902
- .querySelector('.ImageEditor-hotspot-container')
1903
- .classList.remove('is-hidden')
1904
- this._hotspotUI.reflow()
1905
- }
1906
-
1907
- renderPreview() {
1908
- this.getPreviewContainer().classList.add('is-hidden')
1909
- const lastCropData = this._croptool.getCropBoxData()
1910
- this._croptool.clear()
1911
- const uncroppedCanvas = this._croptool.getCroppedCanvas()
1912
- this._croptool.setCropBoxData(lastCropData)
1913
- const existingPreview = this.getPreviewCanvas()
1914
- existingPreview
1915
- ? this.getPreviewContainer().replaceChild(
1916
- uncroppedCanvas,
1917
- existingPreview,
1918
- )
1919
- : this.getPreviewContainer().appendChild(uncroppedCanvas)
1920
- }
1921
-
1922
- renderRenditionPreviews(renditions) {
1923
- const renditionList = create('ol', {
1924
- class: 'ImageEditor-sizeSelectors',
1925
- })
1926
-
1927
- const createLabel = (rendition) => {
1928
- return (rendition.sizeInfos[rendition.name].displayName ??= '')
1929
- }
1930
-
1931
- const createDescription = (rendition) => {
1932
- const elements = new Map()
1933
- for (const key in rendition.sizeInfos) {
1934
- const size = rendition.sizeInfos[key]
1935
- let description = size.description
1936
- if (description) {
1937
- elements.set(description, create('span', description))
1938
- }
1939
- }
1940
- return [...elements.values()]
1941
- }
1942
-
1943
- const createRenditionPreview = (rendition) => {
1944
- const croptool = this.getCropTool()
1945
-
1946
- const preview = create('div', {
1947
- class: 'imageEditor-sizePreview',
1948
- })
1949
-
1950
- this.positionCrop(rendition)
1951
-
1952
- const canvas = croptool.getCroppedCanvas()
1953
- preview.appendChild(canvas)
1954
- return preview
1955
- }
1956
-
1957
- renditions.forEach((val, key) => {
1958
- const cropGroupElement = create('li', {
1959
- class: `imageEditor-sizeButton ${
1960
- val.current ? 'imageEditor-sizeSelected' : ''
1961
- }`,
1962
- onclick: (event) => {
1963
- event.preventDefault()
1964
- event.stopPropagation()
1965
- this.setCurrentRendition(val)
1966
- },
1967
- })
1968
-
1969
- cropGroupElement.setAttribute('data-group-name', val.sizeInfos[key].group)
1970
- cropGroupElement.setAttribute('data-size-name', key)
1971
-
1972
- if (val.sizeInfos[key] && val.sizeInfos[key].independent) {
1973
- cropGroupElement.setAttribute('data-independent', 'true')
1974
- }
1975
-
1976
- const sizeLabel = create(
1977
- 'div',
1978
- { className: 'imageEditor-sizeLabel' },
1979
- createLabel(val),
1980
- )
1981
-
1982
- const groupLabel = create('div', {
1983
- className: 'ImageEditor-sizeContainer',
1984
- })
1985
-
1986
- groupLabel.append(sizeLabel)
1987
-
1988
- const groupDescription = create('div', {
1989
- className: 'ImageEditor-sizeDescription',
1990
- })
1991
-
1992
- groupDescription.append(...createDescription(val))
1993
-
1994
- const inheritLabel = this.getElement().getAttribute('data-inherit')
1995
- const overrideLabel = this.getElement().getAttribute('data-override')
1996
-
1997
- if (val.cropRatio && val.cropRatio !== 'independent') {
1998
- groupDescription.append(
1999
- create(
2000
- 'span',
2001
- { className: 'is-inherit' },
2002
- inheritLabel + ' ' + val.cropRatio,
2003
- ),
2004
- )
2005
- groupDescription.append(
2006
- create(
2007
- 'span',
2008
- { className: 'is-override' },
2009
- overrideLabel + ' ' + val.cropRatio,
2010
- ),
2011
- )
2012
- }
2013
-
2014
- const resetButton = create(
2015
- 'a',
2016
- {
2017
- class: 'ImageEditor-renditionResetButton',
2018
- onclick: (event) => {
2019
- event.preventDefault()
2020
- event.stopPropagation()
2021
- if (!val.current) {
2022
- this.setCurrentRendition(val)
2023
- }
2024
- this.resetCropInputValues(this.getCurrentRendition())
2025
- this.resetRendition(this.getCurrentRendition())
2026
- },
2027
- },
2028
- 'Reset',
2029
- )
2030
-
2031
- cropGroupElement.append(
2032
- groupLabel,
2033
- groupDescription,
2034
- createRenditionPreview(val),
2035
- )
2036
- groupLabel.append(resetButton)
2037
- renditionList.appendChild(cropGroupElement)
2038
- })
2039
-
2040
- const imageSizes = this.getElement().querySelector('.ImageEditor-sizes')
2041
- if (imageSizes) {
2042
- imageSizes.appendChild(renditionList)
2043
- }
2044
- }
2045
-
2046
- renderTabs() {
2047
- const target = this.getElement().querySelector('.imageEditor-aside')
2048
- const previewImage = this.getPreviewContainer().querySelector('img')
2049
-
2050
- const editTab = create('div', {
2051
- class: 'ImageEditor-editGroup',
2052
- dataset: { tab: 'Edit' },
2053
- })
2054
- if (this.getElement().getAttribute('editor') === 'true') {
2055
- target.appendChild(editTab)
2056
- }
2057
-
2058
- const sizesTab = this.getElement().querySelector('.imageEditor-sizes')
2059
- if (sizesTab) {
2060
- sizesTab.dataset.tab = 'Sizes'
2061
- target.appendChild(sizesTab)
2062
- }
2063
-
2064
- if (this.getElement().getAttribute('editor') === 'true') {
2065
- this._editUI = this.createEditUI(
2066
- target,
2067
- editTab,
2068
- this._croptool,
2069
- this._focusUI,
2070
- () => {
2071
- this.updateAllRenditionPreviews()
2072
- },
2073
- () => {
2074
- this.repaint(document.body.offsetHeight)
2075
- },
2076
- )
2077
- } else {
2078
- this.applyAllEdits(this.getEditValues())
2079
- }
2080
-
2081
- const hotspottingEnabled = !!this.getForm().querySelector(
2082
- '.inputContainer[data-name$=".hotspots"]',
2083
- )
2084
- let hotspotTab
2085
-
2086
- if (hotspottingEnabled) {
2087
- hotspotTab = create('div', {
2088
- class: 'ImageEditor-hotspots',
2089
- dataset: { tab: 'Hotspots' },
2090
- })
2091
- target.appendChild(hotspotTab)
2092
- }
2093
-
2094
- this._hotspotUI = hotspottingEnabled
2095
- ? this.createHotspotUI(
2096
- hotspotTab,
2097
- previewImage.getAttribute('data-width'),
2098
- previewImage.getAttribute('data-height'),
2099
- () => {},
2100
- )
2101
- : null
2102
-
2103
- const mutations = new MutationObserver((mutations) => {
2104
- for (const mutation of mutations) {
2105
- if (mutation.attributeName === 'class') {
2106
- const element = mutation.target
2107
- const tab = element.dataset.tab
2108
- if (element.classList.contains('TC-unselected')) {
2109
- // the previous tab
2110
- if (tab === 'Sizes') {
2111
- this.hideCropUI()
2112
- } else if (tab === 'Edit') {
2113
- this.hideEditUI()
2114
- } else if (tab === 'Hotspots') {
2115
- this.hideHotspotUI()
2116
- }
2117
- } else {
2118
- if (tab === 'Sizes') {
2119
- this.showCropUI()
2120
- } else if (tab === 'Edit') {
2121
- this.showEditUI()
2122
- } else if (tab === 'Hotspots') {
2123
- this.showHotspotUI()
2124
- }
2125
- }
2126
- }
2127
- }
2128
- })
2129
-
2130
- mutations.observe(editTab, {
2131
- attributes: true,
2132
- })
2133
- if (sizesTab) {
2134
- mutations.observe(sizesTab, {
2135
- attributes: true,
2136
- })
2137
- }
2138
- if (hotspotTab) {
2139
- mutations.observe(hotspotTab, {
2140
- attributes: true,
2141
- })
2142
- }
2143
-
2144
- new TabContainer(target) // eslint-disable-line no-new
2145
- }
2146
-
2147
- updateRenditionPreview(rendition, canvas) {
2148
- const renditionEl = this.getRenditionElFromRendition(rendition)
2149
- const oldPreview = renditionEl.querySelector(
2150
- '.imageEditor-sizePreview canvas',
2151
- )
2152
- const parent = renditionEl.querySelector('.imageEditor-sizePreview')
2153
- parent.replaceChild(canvas, oldPreview)
2154
- }
2155
-
2156
- // @focusPointData should be decimal values for {x, y}
2157
- updateAllRenditionPreviews() {
2158
- this._renditions.forEach((rendition) => {
2159
- this.positionCrop(rendition)
2160
- const canvas = this._croptool.getCroppedCanvas()
2161
- this._croptool.clear()
2162
- this.updateRenditionPreview(rendition, canvas)
2163
- })
2164
- }
2165
-
2166
- // Converts pixel values to percentages before updating the input fields.
2167
- setCropInputValues(rendition, data = null) {
2168
- const canvasData = this._croptool.getCanvasData()
2169
- let cropData = data || this._croptool.getCropBoxData()
2170
- let { left, top, width, height } = cropData
2171
-
2172
- left = left !== 0 ? (left - canvasData.left) / canvasData.width : 0
2173
- top = top !== 0 ? (top - canvasData.top) / canvasData.height : 0
2174
- width = width / canvasData.width
2175
- height = height / canvasData.height
2176
-
2177
- cropData = { x: left, y: top, width, height }
2178
-
2179
- for (const key in rendition.sizeInfos) {
2180
- for (const prop in cropData) {
2181
- const input = rendition.sizeInfos[key].inputs[prop]
2182
- input.value = cropData[prop]
2183
- input.removeAttribute('disabled')
2184
- }
2185
- }
2186
- }
2187
-
2188
- getCropInputValues(rendition) {
2189
- const props = ['x', 'y', 'width', 'height']
2190
- let data = {}
2191
-
2192
- for (const key in rendition.sizeInfos) {
2193
- props.forEach((prop) => {
2194
- Object.assign(data, {
2195
- [prop]: rendition.sizeInfos[key].inputs[prop].value,
2196
- })
2197
- })
2198
- }
2199
- return data
2200
- }
2201
-
2202
- resetCropInputValues(rendition) {
2203
- this.setCropInputValues(rendition, {
2204
- left: 0.0,
2205
- top: 0.0,
2206
- width: 0.0,
2207
- height: 0.0,
2208
- })
2209
- }
2210
-
2211
- resetRendition(rendition) {
2212
- delete rendition.modifiedCropData
2213
- const renditionEl = this.getRenditionElFromRendition(rendition)
2214
- renditionEl.classList.remove('ImageEditor-sizeModified')
2215
- this.hideRenditionResetButton(rendition)
2216
- this.applyRenditionCrop(rendition)
2217
- }
2218
-
2219
- resetAllRenditionPreviews() {
2220
- if (this.getCurrentRendition()) {
2221
- this.unsetCurrentRendition(this.getCurrentRendition())
2222
- }
2223
-
2224
- this._renditions.forEach((rendition) => {
2225
- this.resetCropInputValues(rendition)
2226
- delete rendition.modifiedCropData
2227
- const renditionEl = this.getRenditionElFromRendition(rendition)
2228
- renditionEl.classList.remove('ImageEditor-sizeModified')
2229
- })
2230
- }
2231
-
2232
- showRenditionResetButton(rendition) {
2233
- this.getRenditionElFromRendition(rendition)
2234
- ?.querySelector(':scope .ImageEditor-renditionResetButton')
2235
- ?.classList.add('is-visible')
2236
- }
2237
-
2238
- hideRenditionResetButton(rendition) {
2239
- this.getRenditionElFromRendition(rendition)
2240
- ?.querySelector(':scope .ImageEditor-renditionResetButton')
2241
- ?.classList.remove('is-visible')
2242
- }
2243
-
2244
- getRenditionElFromRendition(rendition) {
2245
- const name = rendition.name
2246
- return this.getElement().querySelector(
2247
- `.imageEditor-sizeButton[data-size-name='${name}']`,
2248
- )
2249
- }
2250
-
2251
- getIsCropCentered() {
2252
- return (
2253
- this.getElement()
2254
- .querySelector('.imageEditor-sizes table')
2255
- .getAttribute('data-crop-center') === 'true'
2256
- )
2257
- }
2258
-
2259
- getCurrentRendition() {
2260
- let current = null
2261
- this._renditions.forEach((rendition) => {
2262
- if (rendition.current) {
2263
- current = rendition
2264
- }
2265
- })
2266
- return current
2267
- }
2268
-
2269
- toggleRendition(rendition) {
2270
- this.unsetCurrentRendition(rendition)
2271
- this._focusUI && this._focusUI.enable()
2272
-
2273
- this.positionCrop(rendition)
2274
-
2275
- const canvas = this._croptool.getCroppedCanvas()
2276
- this._croptool.clear()
2277
- this.updateRenditionPreview(rendition, canvas)
2278
- }
2279
-
2280
- unsetCurrentRendition(rendition) {
2281
- const renditionEl = this.getRenditionElFromRendition(rendition)
2282
- renditionEl.classList.remove('imageEditor-sizeSelected')
2283
- rendition.current = false
2284
- }
2285
-
2286
- setCurrentRendition(rendition) {
2287
- // is this rendition already the current?
2288
- if (rendition.current) {
2289
- this.toggleRendition(rendition)
2290
- return
2291
- }
2292
-
2293
- // deselect current
2294
- this._renditions.forEach((rendition) => {
2295
- if (rendition.current) {
2296
- this.unsetCurrentRendition(rendition)
2297
- this.toggleRendition(rendition)
2298
- }
2299
- })
2300
-
2301
- rendition.current = true
2302
- this.applyRenditionCrop(rendition)
2303
- }
2304
-
2305
- applyRenditionCrop(rendition) {
2306
- const renditionEl = this.getRenditionElFromRendition(rendition)
2307
-
2308
- if (renditionEl) {
2309
- renditionEl.classList.add('imageEditor-sizeSelected')
2310
-
2311
- this.positionCrop(rendition)
2312
- }
2313
- }
2314
-
2315
- positionCrop(rendition) {
2316
- let ratio
2317
- let focusPointData
2318
- let cropData = this.getCropInputValues(rendition)
2319
- const imageData = this._croptool.getCanvasData()
2320
-
2321
- if (this._focusUI) {
2322
- focusPointData = this._focusUI.getFocusCoordinatesRelative(
2323
- rendition.cropRatio,
2324
- )
2325
- }
2326
-
2327
- for (const key in rendition.sizeInfos) {
2328
- ratio = rendition.sizeInfos[key].aspectRatio
2329
- }
2330
-
2331
- this._croptool.crop()
2332
- this._croptool.setAspectRatio(ratio)
2333
-
2334
- // Crop is positioned in upper-left by default.
2335
- if (!this.getIsCropCentered()) {
2336
- this.getCropTool().setCropBoxData({
2337
- left: 0,
2338
- top: 0,
2339
- })
2340
- }
2341
-
2342
- // Was the crop box adjusted by the User?
2343
- if (parseFloat(cropData.width) && parseFloat(cropData.height)) {
2344
- // display as a modified crop w/ reset functionality.
2345
- const renditionEl = this.getRenditionElFromRendition(rendition)
2346
- if (renditionEl) {
2347
- this.showRenditionResetButton(rendition)
2348
- }
2349
-
2350
- const left = imageData.width * cropData.x + imageData.left
2351
- const top = imageData.height * cropData.y + imageData.top
2352
- const width = imageData.width * cropData.width
2353
- const height = imageData.height * cropData.height
2354
- this._croptool.setCropBoxData({
2355
- left,
2356
- top,
2357
- width,
2358
- height,
2359
- })
2360
- } else {
2361
- // if a focus point has been set, we need to adjust the crop accordingly
2362
- if (focusPointData) {
2363
- const canvasData = this._croptool.getCanvasData()
2364
- const cropboxData = this._croptool.getCropBoxData()
2365
- const cropboxDataRelativeToFocus = {
2366
- left:
2367
- canvasData.width * focusPointData.x -
2368
- cropboxData.width / 2 +
2369
- canvasData.left,
2370
- top:
2371
- canvasData.height * focusPointData.y -
2372
- cropboxData.height / 2 +
2373
- canvasData.top,
2374
- width: cropboxData.width,
2375
- height: cropboxData.height,
2376
- }
2377
-
2378
- this._croptool.setCropBoxData(cropboxDataRelativeToFocus)
2379
-
2380
- for (const key in rendition.sizeInfos) {
2381
- rendition.sizeInfos[key].focusCrop = cropboxDataRelativeToFocus
2382
- }
2383
- }
2384
- }
2385
- }
2386
- }
2387
-
2388
- onFind('.imageEditor', (target) => {
2389
- new ImageEditor(target)
2390
- })
2391
-
2392
- // Keeps focus within image editor when tabbing
2393
- onFind('.ImageEditor .TabBar-item', (tab) => {
2394
- tab.addEventListener('keydown', (event) => {
2395
- if (event.key === 'Tab' && event.shiftKey) {
2396
- event.preventDefault()
2397
- tab
2398
- .closest('.ImageEditor')
2399
- ?.querySelector('.ImageEditor-resetButton')
2400
- ?.focus()
2401
- }
2402
- })
2403
- })