@brightspot/ui 3.0.1 → 5.0.1

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 (589) hide show
  1. package/README.md +56 -37
  2. package/dist/storybook/assets/{ActionBar.stories-BTkFDNZF.js → ActionBar.stories-IDc5Jeit.js} +1 -1
  3. package/dist/storybook/assets/{ActionItem.stories-BY1vOUMp.js → ActionItem.stories-FKbziuZR.js} +1 -1
  4. package/dist/storybook/assets/{Avatar.stories-BViF7cJM.js → Avatar.stories-BsAnn4F7.js} +1 -1
  5. package/dist/storybook/assets/{AvatarGroup.stories-DBLtdzWj.js → AvatarGroup.stories-BmNNGA4K.js} +1 -1
  6. package/dist/storybook/assets/{Badge.stories-BJzAykeL.js → Badge.stories-BMb_-0Io.js} +1 -1
  7. package/dist/storybook/assets/{Button-BS_DVZrR.js → Button-D3vjfsjz.js} +1 -1
  8. package/dist/storybook/assets/{Button.stories-CMa8tsGq.js → Button.stories-VndkwLDv.js} +1 -1
  9. package/dist/storybook/assets/{ButtonGroup.stories-Dorf10Q3.js → ButtonGroup.stories-BvbNykTx.js} +1 -1
  10. package/dist/storybook/assets/{Celebrate.stories-C0RndH03.js → Celebrate.stories-DrrL3txs.js} +1 -1
  11. package/dist/storybook/assets/{Checkbox.stories-DuM_lJ8J.js → Checkbox.stories-BS4H3-cO.js} +1 -1
  12. package/dist/storybook/assets/{CircularProgress.stories-Bv2S_R-k.js → CircularProgress.stories-YQfbjx-O.js} +1 -1
  13. package/dist/storybook/assets/{ClipboardMixin.stories-Bd1tO-g7.js → ClipboardMixin.stories-DjsApoGs.js} +1 -1
  14. package/dist/storybook/assets/{Color-6BZIO3FS-CKpudxYg.js → Color-6BZIO3FS-D23VaL29.js} +1 -1
  15. package/dist/storybook/assets/{Colors.stories-CLQ--V38.js → Colors.stories-B0Eo-AgP.js} +1 -1
  16. package/dist/storybook/assets/{CombinedEffects.stories-DnT8sQ2Z.js → CombinedEffects.stories-CJv4PhDZ.js} +1 -1
  17. package/dist/storybook/assets/{ComponentStatesMixin-Rv-1Sy61.js → ComponentStatesMixin-Bn8b1Qpq.js} +1 -1
  18. package/dist/storybook/assets/{ComponentStatesMixin.stories-DNs2OQFK.js → ComponentStatesMixin.stories-CM9Nn1Vr.js} +1 -1
  19. package/dist/storybook/assets/{CopyToClipboard.stories-Bz6tBGbk.js → CopyToClipboard.stories-DdwTxinQ.js} +1 -1
  20. package/dist/storybook/assets/{Debounce.stories-B2zRBNPi.js → Debounce.stories-bdqs8xGz.js} +1 -1
  21. package/dist/storybook/assets/{DocsRenderer-LL677BLK-DW8rd1PO.js → DocsRenderer-LL677BLK-CdjX43FI.js} +3 -3
  22. package/dist/storybook/assets/{Dropdown.stories-hnwE4FQe.js → Dropdown.stories-wY7jzy1b.js} +1 -1
  23. package/dist/storybook/assets/{EmptyState.stories-D8NwBW4d.js → EmptyState.stories-FpHAx68-.js} +1 -1
  24. package/dist/storybook/assets/{Events.stories-uC-17Dli.js → Events.stories-gJAakVdu.js} +1 -1
  25. package/dist/storybook/assets/{Heading.stories-gU4dU4SQ.js → Heading.stories-BqFtaYkf.js} +1 -1
  26. package/dist/storybook/assets/{HueRipple.stories-C9mL5X65.js → HueRipple.stories-Bk1g8tT8.js} +1 -1
  27. package/dist/storybook/assets/{Icon.stories-CODM5nby.js → Icon.stories-sxRLpE9W.js} +1 -1
  28. package/dist/storybook/assets/{IconButton.stories-B2ua_wfB.js → IconButton.stories-DjmbahEP.js} +1 -1
  29. package/dist/storybook/assets/{LinearProgress.stories-orcCd9Ym.js → LinearProgress.stories-DwJQwdLl.js} +1 -1
  30. package/dist/storybook/assets/{Pagination.stories-C4ygdgfN.js → Pagination.stories-Cp-E5mN-.js} +1 -1
  31. package/dist/storybook/assets/{Popover.stories-BCIIWmTA.js → Popover.stories-BRW3tYFh.js} +1 -1
  32. package/dist/storybook/assets/{ReadyMixin-Cxdzrrfi.js → ReadyMixin-Btxswj_k.js} +1 -1
  33. package/dist/storybook/assets/{RovingTabindexMixin.stories-DOwFdYhD.js → RovingTabindexMixin.stories-CU7heX4w.js} +1 -1
  34. package/dist/storybook/assets/{Rtc.stories-CHCfV1TF.js → Rtc.stories-CGkrXg7-.js} +1 -1
  35. package/dist/storybook/assets/{ScrollShadow.stories-BVa9OI5n.js → ScrollShadow.stories-HPqnieYi.js} +1 -1
  36. package/dist/storybook/assets/{Switch.stories-iU97zahz.js → Switch.stories-SIix50Dz.js} +1 -1
  37. package/dist/storybook/assets/{Tab.stories-Bibe-F62.js → Tab.stories-BJ0Dl--1.js} +1 -1
  38. package/dist/storybook/assets/{Tabs.stories-CF4_aHnK.js → Tabs.stories-DEV7dhqr.js} +1 -1
  39. package/dist/storybook/assets/{Throttle.stories-DIuUxjiJ.js → Throttle.stories-5l5dcEMx.js} +1 -1
  40. package/dist/storybook/assets/{Tooltip.stories-vob7VYrU.js → Tooltip.stories-CJrvBRM9.js} +1 -1
  41. package/dist/storybook/assets/{Upload.stories-2Tow92Sk.js → Upload.stories-HAndUW3D.js} +1 -1
  42. package/dist/storybook/assets/{UploadItem.stories-BDmgA2i-.js → UploadItem.stories-B-8IM6rL.js} +1 -1
  43. package/dist/storybook/assets/{Welcome.stories-CxDC5l_l.js → Welcome.stories-Cy7QZEXf.js} +1 -1
  44. package/dist/storybook/assets/{Widget.stories-Dj_P7LE4.js → Widget.stories-DyTOXrSZ.js} +1 -1
  45. package/dist/storybook/assets/{WithTooltip-65CFNBJE-C1mqF-R9.js → WithTooltip-65CFNBJE-BF-px3XD.js} +1 -1
  46. package/dist/storybook/assets/{blocks-CDIBgsPi.js → blocks-B-r6Fucc.js} +5 -5
  47. package/dist/storybook/assets/{formatter-EIJCOSYU-DlWS8Eh5.js → formatter-EIJCOSYU-BhTCWHea.js} +1 -1
  48. package/dist/storybook/assets/if-defined-CxLwFifW.js +1 -0
  49. package/dist/storybook/assets/{iframe-BgFj0b5u.css → iframe-D0roG0J-.css} +1 -1
  50. package/dist/storybook/assets/{iframe-B3sUtE0N.js → iframe-XJ17FJi7.js} +3 -3
  51. package/dist/storybook/assets/{index-CP948hPJ.js → index-CQ_bLab5.js} +1 -1
  52. package/dist/storybook/assets/{onFind-DtBCGkmC.js → onFind-RCD4BqgZ.js} +1 -1
  53. package/dist/storybook/assets/{onFind.stories-CdMaO-FL.js → onFind.stories-CD7vXuRn.js} +1 -1
  54. package/dist/storybook/assets/{onRemove.stories-CnNXun2Y.js → onRemove.stories-Dm8HrW-w.js} +1 -1
  55. package/dist/storybook/assets/{onVisible.stories-ZnFebS7S.js → onVisible.stories-CigtHPTK.js} +1 -1
  56. package/dist/storybook/assets/{style-map-Imekj22o.js → style-map-C3PJ2niZ.js} +1 -1
  57. package/dist/storybook/assets/{syntaxhighlighter-ED5Y7EFY-CqsAEB33.js → syntaxhighlighter-ED5Y7EFY-DIlzdeob.js} +1 -1
  58. package/dist/storybook/iframe.html +3 -3
  59. package/dist/storybook/project.json +1 -1
  60. package/dist/tailwind.config.d.ts +4 -1
  61. package/dist/tailwind.config.d.ts.map +1 -1
  62. package/dist/tailwind.config.js +0 -2
  63. package/dist/tailwind.config.js.map +1 -1
  64. package/dist/tailwind.config.ts +0 -2
  65. package/package.json +20 -4
  66. package/src/legacy/tool-ui/src/AIInline.css +123 -0
  67. package/src/legacy/tool-ui/src/ActionBar.css +41 -0
  68. package/src/legacy/tool-ui/src/Admin.css +258 -0
  69. package/src/legacy/tool-ui/src/AdobeStock.css +3 -0
  70. package/src/legacy/tool-ui/src/AnalyticsWidget.css +34 -0
  71. package/src/legacy/tool-ui/src/AnalyticsWidget.ts +158 -0
  72. package/src/legacy/tool-ui/src/Apis.css +31 -0
  73. package/src/legacy/tool-ui/src/AppetizeioEmbedded.css +58 -0
  74. package/src/legacy/tool-ui/src/AssociatedContentWidget.css +68 -0
  75. package/src/legacy/tool-ui/src/AutoExpand.css +33 -0
  76. package/src/legacy/tool-ui/src/Avatar.css +31 -0
  77. package/src/legacy/tool-ui/src/BackgroundTasks.css +29 -0
  78. package/src/legacy/tool-ui/src/Base.css +80 -0
  79. package/src/legacy/tool-ui/src/Board.css +160 -0
  80. package/src/legacy/tool-ui/src/Board.ts +515 -0
  81. package/src/legacy/tool-ui/src/BroadcastAlertBanner.ts +93 -0
  82. package/src/legacy/tool-ui/src/BulkUpload.css +51 -0
  83. package/src/legacy/tool-ui/src/BulkWorkflow.css +47 -0
  84. package/src/legacy/tool-ui/src/BulkWorkflow.ts +110 -0
  85. package/src/legacy/tool-ui/src/BulletedList.css +11 -0
  86. package/src/legacy/tool-ui/src/CIGCluster/README.md +32 -0
  87. package/src/legacy/tool-ui/src/CIGCluster/index.ts +144 -0
  88. package/src/legacy/tool-ui/src/CIGCluster.css +60 -0
  89. package/src/legacy/tool-ui/src/Calendar.css +110 -0
  90. package/src/legacy/tool-ui/src/Card.css +28 -0
  91. package/src/legacy/tool-ui/src/Celebrate.ts +87 -0
  92. package/src/legacy/tool-ui/src/Chart.ts +53 -0
  93. package/src/legacy/tool-ui/src/ChartCompat.ts +413 -0
  94. package/src/legacy/tool-ui/src/ChartTable.ts +22 -0
  95. package/src/legacy/tool-ui/src/Checkbox.css +22 -0
  96. package/src/legacy/tool-ui/src/CodeMirror.css +173 -0
  97. package/src/legacy/tool-ui/src/Collections.css +74 -0
  98. package/src/legacy/tool-ui/src/ColorInputSpectrum.css +140 -0
  99. package/src/legacy/tool-ui/src/ComboInput.css +253 -0
  100. package/src/legacy/tool-ui/src/Compat.css +383 -0
  101. package/src/legacy/tool-ui/src/ComponentStatesMixin.ts +27 -0
  102. package/src/legacy/tool-ui/src/ContentColors.css +19 -0
  103. package/src/legacy/tool-ui/src/ContentEdit.css +1065 -0
  104. package/src/legacy/tool-ui/src/ContentEditDrawer.css +274 -0
  105. package/src/legacy/tool-ui/src/ContentEditDrawer.ts +217 -0
  106. package/src/legacy/tool-ui/src/ContentEditPanel.ts +188 -0
  107. package/src/legacy/tool-ui/src/ContentEditSites.ts +101 -0
  108. package/src/legacy/tool-ui/src/ContentForm.css +87 -0
  109. package/src/legacy/tool-ui/src/ContentForm.ts +104 -0
  110. package/src/legacy/tool-ui/src/ContentInputGroup.css +678 -0
  111. package/src/legacy/tool-ui/src/ContentInputGroup.ts +86 -0
  112. package/src/legacy/tool-ui/src/ContentReporting.css +54 -0
  113. package/src/legacy/tool-ui/src/ContentSelector.css +215 -0
  114. package/src/legacy/tool-ui/src/ContentSelectorActions.ts +428 -0
  115. package/src/legacy/tool-ui/src/ContentSummary.css +176 -0
  116. package/src/legacy/tool-ui/src/ContentSummary.ts +223 -0
  117. package/src/legacy/tool-ui/src/ContentTemplatesWidget.css +54 -0
  118. package/src/legacy/tool-ui/src/ContentTools.css +109 -0
  119. package/src/legacy/tool-ui/src/ContentType.css +27 -0
  120. package/src/legacy/tool-ui/src/Conversation.css +325 -0
  121. package/src/legacy/tool-ui/src/Conversation.ts +836 -0
  122. package/src/legacy/tool-ui/src/CopySiteWidget.css +8 -0
  123. package/src/legacy/tool-ui/src/CopyToClipboard/README.md +23 -0
  124. package/src/legacy/tool-ui/src/CopyToClipboard/index.ts +54 -0
  125. package/src/legacy/tool-ui/src/CopyToClipboard.css +10 -0
  126. package/src/legacy/tool-ui/src/Crosslinker.css +486 -0
  127. package/src/legacy/tool-ui/src/Crosslinker.ts +46 -0
  128. package/src/legacy/tool-ui/src/CustomKeyboard.css +25 -0
  129. package/src/legacy/tool-ui/src/DashboardRow.css +54 -0
  130. package/src/legacy/tool-ui/src/DashboardWidget.css +444 -0
  131. package/src/legacy/tool-ui/src/DateStringField.css +82 -0
  132. package/src/legacy/tool-ui/src/DateTimeInput.css +11 -0
  133. package/src/legacy/tool-ui/src/Dialog.css +441 -0
  134. package/src/legacy/tool-ui/src/Diff.css +280 -0
  135. package/src/legacy/tool-ui/src/Diff.ts +89 -0
  136. package/src/legacy/tool-ui/src/Downloads.css +7 -0
  137. package/src/legacy/tool-ui/src/Dropdown.css +63 -0
  138. package/src/legacy/tool-ui/src/EditFieldUpdateCache.ts +129 -0
  139. package/src/legacy/tool-ui/src/EmbeddedInputGroup/README.md +3 -0
  140. package/src/legacy/tool-ui/src/EmbeddedInputGroup/index.ts +189 -0
  141. package/src/legacy/tool-ui/src/EmbeddedInputGroup.css +75 -0
  142. package/src/legacy/tool-ui/src/Enhancement.css +135 -0
  143. package/src/legacy/tool-ui/src/Entry.ts +893 -0
  144. package/src/legacy/tool-ui/src/EventEmitterMixin.ts +71 -0
  145. package/src/legacy/tool-ui/src/ExternalCalendars.ts +34 -0
  146. package/src/legacy/tool-ui/src/ExternalItemObjects.ts +36 -0
  147. package/src/legacy/tool-ui/src/FileInput.css +150 -0
  148. package/src/legacy/tool-ui/src/FocusRegions.ts +530 -0
  149. package/src/legacy/tool-ui/src/FormFilter.css +93 -0
  150. package/src/legacy/tool-ui/src/FullscreenView.css +152 -0
  151. package/src/legacy/tool-ui/src/GCA.css +9 -0
  152. package/src/legacy/tool-ui/src/Global.d.ts +219 -0
  153. package/src/legacy/tool-ui/src/GraphQL.css +74 -0
  154. package/src/legacy/tool-ui/src/GraphQLApis.css +4 -0
  155. package/src/legacy/tool-ui/src/Guide.css +193 -0
  156. package/src/legacy/tool-ui/src/GuideField.css +25 -0
  157. package/src/legacy/tool-ui/src/Hierarchy.css +68 -0
  158. package/src/legacy/tool-ui/src/Icon/index.css +11 -0
  159. package/src/legacy/tool-ui/src/Icon/index.ts +69 -0
  160. package/src/legacy/tool-ui/src/ImageEditor.css +522 -0
  161. package/src/legacy/tool-ui/src/ImageRecognition.css +11 -0
  162. package/src/legacy/tool-ui/src/Incompatible.css +42 -0
  163. package/src/legacy/tool-ui/src/InputRow.css +26 -0
  164. package/src/legacy/tool-ui/src/Label.css +33 -0
  165. package/src/legacy/tool-ui/src/LabeledCheckbox.css +13 -0
  166. package/src/legacy/tool-ui/src/Link.css +4 -0
  167. package/src/legacy/tool-ui/src/LinkCarousel.css +77 -0
  168. package/src/legacy/tool-ui/src/LinkList.css +53 -0
  169. package/src/legacy/tool-ui/src/LinkTable.css +105 -0
  170. package/src/legacy/tool-ui/src/ListManager.css +27 -0
  171. package/src/legacy/tool-ui/src/LiveBlog.css +290 -0
  172. package/src/legacy/tool-ui/src/LiveBlog.ts +47 -0
  173. package/src/legacy/tool-ui/src/Loader.svg +16 -0
  174. package/src/legacy/tool-ui/src/LocationMap.css +25 -0
  175. package/src/legacy/tool-ui/src/LookingGlass.css +31 -0
  176. package/src/legacy/tool-ui/src/LucideDynamicLoader.ts +31 -0
  177. package/src/legacy/tool-ui/src/MailPublishing.css +32 -0
  178. package/src/legacy/tool-ui/src/Mention.css +34 -0
  179. package/src/legacy/tool-ui/src/MenuView.css +659 -0
  180. package/src/legacy/tool-ui/src/Message.css +239 -0
  181. package/src/legacy/tool-ui/src/Month.css +65 -0
  182. package/src/legacy/tool-ui/src/NavRail/index.css +47 -0
  183. package/src/legacy/tool-ui/src/NavRail/index.ts +40 -0
  184. package/src/legacy/tool-ui/src/Notification.css +197 -0
  185. package/src/legacy/tool-ui/src/Notifications.ts +139 -0
  186. package/src/legacy/tool-ui/src/Page.css +884 -0
  187. package/src/legacy/tool-ui/src/PageNav.ts +108 -0
  188. package/src/legacy/tool-ui/src/PaginatedResult.css +20 -0
  189. package/src/legacy/tool-ui/src/Pagination.css +119 -0
  190. package/src/legacy/tool-ui/src/PastePopup.css +11 -0
  191. package/src/legacy/tool-ui/src/PlaceholderEditableMixin.ts +86 -0
  192. package/src/legacy/tool-ui/src/Popup.css +413 -0
  193. package/src/legacy/tool-ui/src/PostPublish.ts +12 -0
  194. package/src/legacy/tool-ui/src/PrePublish.ts +63 -0
  195. package/src/legacy/tool-ui/src/Preview.css +134 -0
  196. package/src/legacy/tool-ui/src/PrivilegeAccessWidget.css +27 -0
  197. package/src/legacy/tool-ui/src/ProfileDropdown.css +95 -0
  198. package/src/legacy/tool-ui/src/ProseMirror-table.css +179 -0
  199. package/src/legacy/tool-ui/src/ProseMirror.css +180 -0
  200. package/src/legacy/tool-ui/src/ProseMirrorContainer.css +40 -0
  201. package/src/legacy/tool-ui/src/ProseMirrorEnhancementMenu.css +68 -0
  202. package/src/legacy/tool-ui/src/ProseMirrorFindReplace.css +132 -0
  203. package/src/legacy/tool-ui/src/QueryField.css +77 -0
  204. package/src/legacy/tool-ui/src/README.md +235 -0
  205. package/src/legacy/tool-ui/src/RadialProgressBar.css +11 -0
  206. package/src/legacy/tool-ui/src/RadialProgressBar.ts +73 -0
  207. package/src/legacy/tool-ui/src/Radio.css +22 -0
  208. package/src/legacy/tool-ui/src/RepeatableContentInputGroup.css +470 -0
  209. package/src/legacy/tool-ui/src/RepeatableContentSelector.css +177 -0
  210. package/src/legacy/tool-ui/src/RepeatableTextInput.css +71 -0
  211. package/src/legacy/tool-ui/src/Revision.css +70 -0
  212. package/src/legacy/tool-ui/src/Revisions.css +292 -0
  213. package/src/legacy/tool-ui/src/RichText.css +15 -0
  214. package/src/legacy/tool-ui/src/SearchControlsToggle.ts +90 -0
  215. package/src/legacy/tool-ui/src/SearchFields.css +109 -0
  216. package/src/legacy/tool-ui/src/SearchInput.css +27 -0
  217. package/src/legacy/tool-ui/src/SearchResult.css +523 -0
  218. package/src/legacy/tool-ui/src/SearchWidget.css +441 -0
  219. package/src/legacy/tool-ui/src/SearchWidget.ts +154 -0
  220. package/src/legacy/tool-ui/src/SearchWidgetAdvanced.css +109 -0
  221. package/src/legacy/tool-ui/src/SharePreview.css +71 -0
  222. package/src/legacy/tool-ui/src/SkipLinks.css +23 -0
  223. package/src/legacy/tool-ui/src/SkipLinks.ts +112 -0
  224. package/src/legacy/tool-ui/src/SlackAuthentication.css +9 -0
  225. package/src/legacy/tool-ui/src/Sortable.css +50 -0
  226. package/src/legacy/tool-ui/src/Spellcheck.css +34 -0
  227. package/src/legacy/tool-ui/src/StyleEmbeddedContent.css +62 -0
  228. package/src/legacy/tool-ui/src/Suggestions.css +53 -0
  229. package/src/legacy/tool-ui/src/Suggestions.ts +209 -0
  230. package/src/legacy/tool-ui/src/TabBar.css +174 -0
  231. package/src/legacy/tool-ui/src/TabContainer.css +40 -0
  232. package/src/legacy/tool-ui/src/Table.css +107 -0
  233. package/src/legacy/tool-ui/src/TableSizerPopup.css +42 -0
  234. package/src/legacy/tool-ui/src/Taxonomy.css +60 -0
  235. package/src/legacy/tool-ui/src/TextInput.css +40 -0
  236. package/src/legacy/tool-ui/src/Theme.css +64 -0
  237. package/src/legacy/tool-ui/src/ThemeBundleEditor.css +93 -0
  238. package/src/legacy/tool-ui/src/TimedContent.css +74 -0
  239. package/src/legacy/tool-ui/src/Toggleable/README.md +90 -0
  240. package/src/legacy/tool-ui/src/Toggleable/index.ts +230 -0
  241. package/src/legacy/tool-ui/src/Translation.css +115 -0
  242. package/src/legacy/tool-ui/src/Translation.ts +20 -0
  243. package/src/legacy/tool-ui/src/TreeList.css +45 -0
  244. package/src/legacy/tool-ui/src/Types.ts +44 -0
  245. package/src/legacy/tool-ui/src/Utilities.css +7 -0
  246. package/src/legacy/tool-ui/src/ViewMirror.css +7 -0
  247. package/src/legacy/tool-ui/src/ViewPreview.css +59 -0
  248. package/src/legacy/tool-ui/src/ViewWatchers.css +69 -0
  249. package/src/legacy/tool-ui/src/Viewers.css +21 -0
  250. package/src/legacy/tool-ui/src/Week.css +23 -0
  251. package/src/legacy/tool-ui/src/Widget.css +622 -0
  252. package/src/legacy/tool-ui/src/WorkStreams.css +97 -0
  253. package/src/legacy/tool-ui/src/Workflow.css +100 -0
  254. package/src/legacy/tool-ui/src/Workstreams.ts +52 -0
  255. package/src/legacy/tool-ui/src/WrappingMixin.ts +77 -0
  256. package/src/legacy/tool-ui/src/additional-tailwind-classes.txt +205 -0
  257. package/src/legacy/tool-ui/src/bsp-tracking.d.ts +27 -0
  258. package/src/legacy/tool-ui/src/dialog/README.md +83 -0
  259. package/src/legacy/tool-ui/src/dialog/index.ts +465 -0
  260. package/src/legacy/tool-ui/src/dom/README.md +63 -0
  261. package/src/legacy/tool-ui/src/dom/Tether.ts +135 -0
  262. package/src/legacy/tool-ui/src/dom/TetherLayout.ts +149 -0
  263. package/src/legacy/tool-ui/src/dom/aria.ts +123 -0
  264. package/src/legacy/tool-ui/src/dom/closest.ts +5 -0
  265. package/src/legacy/tool-ui/src/dom/create.ts +46 -0
  266. package/src/legacy/tool-ui/src/dom/find.ts +5 -0
  267. package/src/legacy/tool-ui/src/dom/findAll.ts +5 -0
  268. package/src/legacy/tool-ui/src/dom/focusable.ts +29 -0
  269. package/src/legacy/tool-ui/src/dom/ifClick.ts +18 -0
  270. package/src/legacy/tool-ui/src/dom/ifMatches.ts +17 -0
  271. package/src/legacy/tool-ui/src/dom/ifUnmodified.ts +16 -0
  272. package/src/legacy/tool-ui/src/dom/insertBefore.ts +4 -0
  273. package/src/legacy/tool-ui/src/dom/insertFirst.ts +4 -0
  274. package/src/legacy/tool-ui/src/dom/insertLast.ts +4 -0
  275. package/src/legacy/tool-ui/src/dom/keyboard.ts +176 -0
  276. package/src/legacy/tool-ui/src/dom/onFind.ts +277 -0
  277. package/src/legacy/tool-ui/src/dom/onFindOnce.ts +31 -0
  278. package/src/legacy/tool-ui/src/dom/onRTEReady.ts +105 -0
  279. package/src/legacy/tool-ui/src/dom/onRemove.ts +27 -0
  280. package/src/legacy/tool-ui/src/dom/onVisible.ts +29 -0
  281. package/src/legacy/tool-ui/src/dom/popupMenu.d.ts +6 -0
  282. package/src/legacy/tool-ui/src/dom/popupMenu.js +282 -0
  283. package/src/legacy/tool-ui/src/dom/previousUntil.ts +22 -0
  284. package/src/legacy/tool-ui/src/dropdown/README.md +9 -0
  285. package/src/legacy/tool-ui/src/dropdown/index.ts +105 -0
  286. package/src/legacy/tool-ui/src/form/CodeInput/CodeMirror.less +168 -0
  287. package/src/legacy/tool-ui/src/form/CodeInput/CodeMirror.ts +15 -0
  288. package/src/legacy/tool-ui/src/form/CodeInput/index.ts +87 -0
  289. package/src/legacy/tool-ui/src/form/FormFilter/README.md +31 -0
  290. package/src/legacy/tool-ui/src/form/FormFilter/index.ts +340 -0
  291. package/src/legacy/tool-ui/src/form/Input/index.less +43 -0
  292. package/src/legacy/tool-ui/src/form/Input/index.ts +92 -0
  293. package/src/legacy/tool-ui/src/form/SearchInput/README.md +23 -0
  294. package/src/legacy/tool-ui/src/form/SearchInput/index.ts +89 -0
  295. package/src/legacy/tool-ui/src/form/TextAreaInput/index.less +11 -0
  296. package/src/legacy/tool-ui/src/form/TextAreaInput/index.ts +36 -0
  297. package/src/legacy/tool-ui/src/graphql/GraphQL.ts +49 -0
  298. package/src/legacy/tool-ui/src/graphql/GraphQLPreview.ts +23 -0
  299. package/src/legacy/tool-ui/src/main/webapp/dist/011a678e3efe41981754.png +1 -0
  300. package/src/legacy/tool-ui/src/main/webapp/dist/1173.080a90c5923854fc48e6.js +2 -0
  301. package/src/legacy/tool-ui/src/main/webapp/dist/1173.080a90c5923854fc48e6.js.LICENSE.txt +10 -0
  302. package/src/legacy/tool-ui/src/main/webapp/dist/194137260531ee29a5f2.svg +1 -0
  303. package/src/legacy/tool-ui/src/main/webapp/dist/1946.156c6821383df2638492.js +1 -0
  304. package/src/legacy/tool-ui/src/main/webapp/dist/1950.3594aaa4f20df28f39d9.js +1 -0
  305. package/src/legacy/tool-ui/src/main/webapp/dist/1e8408af1a34bdf61457.png +1 -0
  306. package/src/legacy/tool-ui/src/main/webapp/dist/2111.3956b5196a514b2c0131.js +1 -0
  307. package/src/legacy/tool-ui/src/main/webapp/dist/2111.a937b0cafa8cf7c039d2.css +1 -0
  308. package/src/legacy/tool-ui/src/main/webapp/dist/2129.6b044e5564dd3dbb2f94.js +1 -0
  309. package/src/legacy/tool-ui/src/main/webapp/dist/2314.420883b6638f35f40e70.js +1 -0
  310. package/src/legacy/tool-ui/src/main/webapp/dist/2456.5bcfb782c2ec27de32ed.js +1 -0
  311. package/src/legacy/tool-ui/src/main/webapp/dist/2537.e8f1359bedd328bf57f4.js +1 -0
  312. package/src/legacy/tool-ui/src/main/webapp/dist/2565.4ca31a6a46de85cd3b31.js +1 -0
  313. package/src/legacy/tool-ui/src/main/webapp/dist/270.3011484ff80f3093083a.js +1 -0
  314. package/src/legacy/tool-ui/src/main/webapp/dist/2825.c1342ac2662d11fd4125.js +1 -0
  315. package/src/legacy/tool-ui/src/main/webapp/dist/2947.f96d21c44c05980974ef.js +1 -0
  316. package/src/legacy/tool-ui/src/main/webapp/dist/3532.c358dfbb2cde0f802c7f.js +1 -0
  317. package/src/legacy/tool-ui/src/main/webapp/dist/3543.2d704cf9e170322468e7.js +2 -0
  318. package/src/legacy/tool-ui/src/main/webapp/dist/3543.2d704cf9e170322468e7.js.LICENSE.txt +1 -0
  319. package/src/legacy/tool-ui/src/main/webapp/dist/3611.61837ffa933f74502094.js +1 -0
  320. package/src/legacy/tool-ui/src/main/webapp/dist/3790.74b7037d430ed0a47d0c.js +1 -0
  321. package/src/legacy/tool-ui/src/main/webapp/dist/3995.afbec58662cafb46ec2c.js +1 -0
  322. package/src/legacy/tool-ui/src/main/webapp/dist/4017.c3ed1ca3be18b5c080c8.js +1 -0
  323. package/src/legacy/tool-ui/src/main/webapp/dist/4093.152620c022c3013502b7.js +1 -0
  324. package/src/legacy/tool-ui/src/main/webapp/dist/4114.57fe159e416e716f8cbc.js +1 -0
  325. package/src/legacy/tool-ui/src/main/webapp/dist/4168.a224af7c5c5f6f2ddd7b.js +1 -0
  326. package/src/legacy/tool-ui/src/main/webapp/dist/4374.37147747a254da8f5f8a.js +1 -0
  327. package/src/legacy/tool-ui/src/main/webapp/dist/4450.16dd5684292fd0a7ea7f.js +1 -0
  328. package/src/legacy/tool-ui/src/main/webapp/dist/4478.93f64e6a6ccb35a6c6d4.js +1 -0
  329. package/src/legacy/tool-ui/src/main/webapp/dist/44b38a404dec4643c46b.png +1 -0
  330. package/src/legacy/tool-ui/src/main/webapp/dist/4582.243dbaa3d8a8648f6f40.js +1 -0
  331. package/src/legacy/tool-ui/src/main/webapp/dist/4709.4f6b707e225e481a928e.js +1 -0
  332. package/src/legacy/tool-ui/src/main/webapp/dist/4790.10d75f7c7f8e54ce83a1.js +1 -0
  333. package/src/legacy/tool-ui/src/main/webapp/dist/4795.034ebc93a67b7a3d6536.js +1 -0
  334. package/src/legacy/tool-ui/src/main/webapp/dist/4850.6b944738520f69c98070.js +1 -0
  335. package/src/legacy/tool-ui/src/main/webapp/dist/4a4ee777ddc0d4cedee4.png +1 -0
  336. package/src/legacy/tool-ui/src/main/webapp/dist/5001.a61ae5608398d5d98ab1.js +1 -0
  337. package/src/legacy/tool-ui/src/main/webapp/dist/5030.1e8b82e1444067d492b1.js +1 -0
  338. package/src/legacy/tool-ui/src/main/webapp/dist/5055.620ccb3717b4a6427f09.js +1 -0
  339. package/src/legacy/tool-ui/src/main/webapp/dist/5098.33613009827cfc80aafb.js +1 -0
  340. package/src/legacy/tool-ui/src/main/webapp/dist/5385.6656501b67424994820b.js +1 -0
  341. package/src/legacy/tool-ui/src/main/webapp/dist/5598.d84773ca53126661bdca.js +2 -0
  342. package/src/legacy/tool-ui/src/main/webapp/dist/5598.d84773ca53126661bdca.js.LICENSE.txt +11 -0
  343. package/src/legacy/tool-ui/src/main/webapp/dist/5617.c81ecb679371ca302f1f.js +1 -0
  344. package/src/legacy/tool-ui/src/main/webapp/dist/5718.449113ae2463a3bb8a83.js +2 -0
  345. package/src/legacy/tool-ui/src/main/webapp/dist/5718.449113ae2463a3bb8a83.js.LICENSE.txt +8 -0
  346. package/src/legacy/tool-ui/src/main/webapp/dist/6047.291dd2d4b3f79be2c64d.js +1 -0
  347. package/src/legacy/tool-ui/src/main/webapp/dist/6086.7ca02b827506f29db590.js +1 -0
  348. package/src/legacy/tool-ui/src/main/webapp/dist/6216.cf8adc1990ee1111f065.js +1 -0
  349. package/src/legacy/tool-ui/src/main/webapp/dist/6497.b7d8dcbf0e7c8d8d538d.js +1 -0
  350. package/src/legacy/tool-ui/src/main/webapp/dist/6565.0e5abc3bd344c53ae154.js +1 -0
  351. package/src/legacy/tool-ui/src/main/webapp/dist/7033.4cd6efbe12e7aaa35745.js +1 -0
  352. package/src/legacy/tool-ui/src/main/webapp/dist/7202.1e5b8f21215ea6eae20a.js +1 -0
  353. package/src/legacy/tool-ui/src/main/webapp/dist/7361.793163ca71dfac69d9eb.js +1 -0
  354. package/src/legacy/tool-ui/src/main/webapp/dist/75.e45c9aec6e7409bfe21d.js +1 -0
  355. package/src/legacy/tool-ui/src/main/webapp/dist/7524.2990be869cf0a2988d17.js +1 -0
  356. package/src/legacy/tool-ui/src/main/webapp/dist/7554.bb243c1fb06a125f6353.js +1 -0
  357. package/src/legacy/tool-ui/src/main/webapp/dist/7718.a937b0cafa8cf7c039d2.css +1 -0
  358. package/src/legacy/tool-ui/src/main/webapp/dist/7718.f5996ddd99f2353c7785.js +1 -0
  359. package/src/legacy/tool-ui/src/main/webapp/dist/7741.0b2789c4e783af835763.js +1 -0
  360. package/src/legacy/tool-ui/src/main/webapp/dist/7769.d47f40cc83035fc2392a.js +1 -0
  361. package/src/legacy/tool-ui/src/main/webapp/dist/7863.2a1d1fd229778a47e10b.js +1 -0
  362. package/src/legacy/tool-ui/src/main/webapp/dist/7923.8a92f55de8387f7a8a35.js +1 -0
  363. package/src/legacy/tool-ui/src/main/webapp/dist/8684.7171c731d38a9873d6df.js +1 -0
  364. package/src/legacy/tool-ui/src/main/webapp/dist/8752.3d02a0f42ec39786fdf0.js +1 -0
  365. package/src/legacy/tool-ui/src/main/webapp/dist/8897.bfeed19f2a4acbb6d67c.js +1 -0
  366. package/src/legacy/tool-ui/src/main/webapp/dist/8934.e90ceca8169fc25e4e1b.js +1 -0
  367. package/src/legacy/tool-ui/src/main/webapp/dist/9277.88b2d89b3ba1c24e7f93.js +2 -0
  368. package/src/legacy/tool-ui/src/main/webapp/dist/9277.88b2d89b3ba1c24e7f93.js.LICENSE.txt +9 -0
  369. package/src/legacy/tool-ui/src/main/webapp/dist/9360.bb0352313d610cae11de.js +2 -0
  370. package/src/legacy/tool-ui/src/main/webapp/dist/9360.bb0352313d610cae11de.js.LICENSE.txt +33 -0
  371. package/src/legacy/tool-ui/src/main/webapp/dist/9561.64eab18c8c1bbc4ad8ec.js +1 -0
  372. package/src/legacy/tool-ui/src/main/webapp/dist/9576.174d6a99544d17e7416d.js +1 -0
  373. package/src/legacy/tool-ui/src/main/webapp/dist/983.eedba1cd484aa5bee0c4.js +1 -0
  374. package/src/legacy/tool-ui/src/main/webapp/dist/Appetizeio.185641211f7c0e42a455.js +1 -0
  375. package/src/legacy/tool-ui/src/main/webapp/dist/CISpectrum.74db7dc80b3a6797a167.js +1 -0
  376. package/src/legacy/tool-ui/src/main/webapp/dist/Chart.223ea4ee9e3e32b67a26.js +1 -0
  377. package/src/legacy/tool-ui/src/main/webapp/dist/ChartCompat.4dd096dd030942227ec6.js +1 -0
  378. package/src/legacy/tool-ui/src/main/webapp/dist/CodeMirror.5ddffe054374f883d6f8.js +1 -0
  379. package/src/legacy/tool-ui/src/main/webapp/dist/ImageEditor.14e3b3035666436f53a5.js +1 -0
  380. package/src/legacy/tool-ui/src/main/webapp/dist/LocationMap.72a510c0f5ff236f596e.js +1 -0
  381. package/src/legacy/tool-ui/src/main/webapp/dist/RTEProseMirror.e8521581e28e90ef6f30.js +500 -0
  382. package/src/legacy/tool-ui/src/main/webapp/dist/RTEProseMirror.e8521581e28e90ef6f30.js.LICENSE.txt +8 -0
  383. package/src/legacy/tool-ui/src/main/webapp/dist/RegionMap.f6f963eda2cb616b9661.js +1 -0
  384. package/src/legacy/tool-ui/src/main/webapp/dist/TimedContent.4bb4dc83ffbe4019dd7b.js +1 -0
  385. package/src/legacy/tool-ui/src/main/webapp/dist/VideoEditor.11c89dda5cf125738a33.js +2 -0
  386. package/src/legacy/tool-ui/src/main/webapp/dist/VideoEditor.11c89dda5cf125738a33.js.LICENSE.txt +23 -0
  387. package/src/legacy/tool-ui/src/main/webapp/dist/c6e9c007f41bd4d26c20.png +1 -0
  388. package/src/legacy/tool-ui/src/main/webapp/dist/iframeResizer.contentWindow.map +1 -0
  389. package/src/legacy/tool-ui/src/main/webapp/dist/iframeResizer.contentWindow.min.js +2 -0
  390. package/src/legacy/tool-ui/src/main/webapp/dist/iframeResizer.contentWindow.min.js.LICENSE.txt +7 -0
  391. package/src/legacy/tool-ui/src/main/webapp/dist/lucide.woff2 +0 -0
  392. package/src/legacy/tool-ui/src/main/webapp/dist/preview.d46d98e5ca9dba0a0f5a.js +1 -0
  393. package/src/legacy/tool-ui/src/main/webapp/dist/v4.a6f53058dbb04a69aa5c.css +3 -0
  394. package/src/legacy/tool-ui/src/main/webapp/dist/v4.de442a0b430071a7803d.js +92 -0
  395. package/src/legacy/tool-ui/src/main/webapp/dist/v4.de442a0b430071a7803d.js.LICENSE.txt +71 -0
  396. package/src/legacy/tool-ui/src/main/webapp/dist/v5.5ce74760f3fb482bec5e.js +92 -0
  397. package/src/legacy/tool-ui/src/main/webapp/dist/v5.5ce74760f3fb482bec5e.js.LICENSE.txt +78 -0
  398. package/src/legacy/tool-ui/src/main/webapp/dist/v5.68dab4082aba302779ed.css +5 -0
  399. package/src/legacy/tool-ui/src/main/webapp/style/v3/ExternalPreviewFrame.less +6 -0
  400. package/src/legacy/tool-ui/src/main/webapp/style/v3/WidgetSearchAdvancedQuery.less +24 -0
  401. package/src/legacy/tool-ui/src/main/webapp/style/v3/element.less +11 -0
  402. package/src/legacy/tool-ui/src/main/webapp/style/v3/icon/ajax-loader.gif +0 -0
  403. package/src/legacy/tool-ui/src/main/webapp/style/v3/index.less +98 -0
  404. package/src/legacy/tool-ui/src/main/webapp/style/v3/repeatable.less +89 -0
  405. package/src/legacy/tool-ui/src/main/webapp/style/v3/variables.less +53 -0
  406. package/src/legacy/tool-ui/src/main/webapp/v4/ActionBar.less +44 -0
  407. package/src/legacy/tool-ui/src/main/webapp/v4/Admin.less +253 -0
  408. package/src/legacy/tool-ui/src/main/webapp/v4/AdobeStock.less +20 -0
  409. package/src/legacy/tool-ui/src/main/webapp/v4/Apis.less +43 -0
  410. package/src/legacy/tool-ui/src/main/webapp/v4/AutoExpand.less +36 -0
  411. package/src/legacy/tool-ui/src/main/webapp/v4/Avatar.less +30 -0
  412. package/src/legacy/tool-ui/src/main/webapp/v4/BackgroundTasks.less +52 -0
  413. package/src/legacy/tool-ui/src/main/webapp/v4/Badge.less +10 -0
  414. package/src/legacy/tool-ui/src/main/webapp/v4/Board.less +311 -0
  415. package/src/legacy/tool-ui/src/main/webapp/v4/BulletedList.less +17 -0
  416. package/src/legacy/tool-ui/src/main/webapp/v4/Button.less +105 -0
  417. package/src/legacy/tool-ui/src/main/webapp/v4/CIGCluster.less +55 -0
  418. package/src/legacy/tool-ui/src/main/webapp/v4/Card.less +53 -0
  419. package/src/legacy/tool-ui/src/main/webapp/v4/Checkbox.less +99 -0
  420. package/src/legacy/tool-ui/src/main/webapp/v4/Collections.less +49 -0
  421. package/src/legacy/tool-ui/src/main/webapp/v4/ColorInputSpectrum.less +319 -0
  422. package/src/legacy/tool-ui/src/main/webapp/v4/ComboInput.less +403 -0
  423. package/src/legacy/tool-ui/src/main/webapp/v4/CommunityWidget.less +73 -0
  424. package/src/legacy/tool-ui/src/main/webapp/v4/Compat.less +7 -0
  425. package/src/legacy/tool-ui/src/main/webapp/v4/ContentEdit.less +1512 -0
  426. package/src/legacy/tool-ui/src/main/webapp/v4/ContentEditDrawer.less +329 -0
  427. package/src/legacy/tool-ui/src/main/webapp/v4/ContentEditSites.less +5 -0
  428. package/src/legacy/tool-ui/src/main/webapp/v4/ContentForm.less +174 -0
  429. package/src/legacy/tool-ui/src/main/webapp/v4/ContentInputGroup.less +669 -0
  430. package/src/legacy/tool-ui/src/main/webapp/v4/ContentReporting.less +39 -0
  431. package/src/legacy/tool-ui/src/main/webapp/v4/ContentSelector.less +268 -0
  432. package/src/legacy/tool-ui/src/main/webapp/v4/ContentSummary.less +238 -0
  433. package/src/legacy/tool-ui/src/main/webapp/v4/ContentTools.less +73 -0
  434. package/src/legacy/tool-ui/src/main/webapp/v4/ContentType.less +24 -0
  435. package/src/legacy/tool-ui/src/main/webapp/v4/CopyToClipboard.less +20 -0
  436. package/src/legacy/tool-ui/src/main/webapp/v4/DashboardRow.less +40 -0
  437. package/src/legacy/tool-ui/src/main/webapp/v4/DashboardWidget.less +193 -0
  438. package/src/legacy/tool-ui/src/main/webapp/v4/DataTable.less +52 -0
  439. package/src/legacy/tool-ui/src/main/webapp/v4/DateStringField.less +148 -0
  440. package/src/legacy/tool-ui/src/main/webapp/v4/DateTimeInput.less +58 -0
  441. package/src/legacy/tool-ui/src/main/webapp/v4/Diff.less +322 -0
  442. package/src/legacy/tool-ui/src/main/webapp/v4/Downloads.css +11 -0
  443. package/src/legacy/tool-ui/src/main/webapp/v4/Draggable.less +42 -0
  444. package/src/legacy/tool-ui/src/main/webapp/v4/Dropdown.less +76 -0
  445. package/src/legacy/tool-ui/src/main/webapp/v4/EmbeddedInputGroup.less +141 -0
  446. package/src/legacy/tool-ui/src/main/webapp/v4/Entry.less +157 -0
  447. package/src/legacy/tool-ui/src/main/webapp/v4/ExternalItemImport.less +3 -0
  448. package/src/legacy/tool-ui/src/main/webapp/v4/FileInput.less +194 -0
  449. package/src/legacy/tool-ui/src/main/webapp/v4/FormFilter.less +189 -0
  450. package/src/legacy/tool-ui/src/main/webapp/v4/Frame.less +26 -0
  451. package/src/legacy/tool-ui/src/main/webapp/v4/GraphQLApis.less +4 -0
  452. package/src/legacy/tool-ui/src/main/webapp/v4/Grid.less +48 -0
  453. package/src/legacy/tool-ui/src/main/webapp/v4/Guide.less +209 -0
  454. package/src/legacy/tool-ui/src/main/webapp/v4/GuideField.less +108 -0
  455. package/src/legacy/tool-ui/src/main/webapp/v4/Hierarchy.less +89 -0
  456. package/src/legacy/tool-ui/src/main/webapp/v4/Highlight.less +5 -0
  457. package/src/legacy/tool-ui/src/main/webapp/v4/Icon.less +53 -0
  458. package/src/legacy/tool-ui/src/main/webapp/v4/IconButton.less +111 -0
  459. package/src/legacy/tool-ui/src/main/webapp/v4/ImageEditor.less +922 -0
  460. package/src/legacy/tool-ui/src/main/webapp/v4/Input.less +15 -0
  461. package/src/legacy/tool-ui/src/main/webapp/v4/InputRow.less +35 -0
  462. package/src/legacy/tool-ui/src/main/webapp/v4/Label.less +12 -0
  463. package/src/legacy/tool-ui/src/main/webapp/v4/LabeledCheckbox.less +35 -0
  464. package/src/legacy/tool-ui/src/main/webapp/v4/Link.less +93 -0
  465. package/src/legacy/tool-ui/src/main/webapp/v4/LinkCarousel.less +143 -0
  466. package/src/legacy/tool-ui/src/main/webapp/v4/LinkList.less +150 -0
  467. package/src/legacy/tool-ui/src/main/webapp/v4/LinkTable.less +112 -0
  468. package/src/legacy/tool-ui/src/main/webapp/v4/LocationMap.less +62 -0
  469. package/src/legacy/tool-ui/src/main/webapp/v4/LookingGlass.less +30 -0
  470. package/src/legacy/tool-ui/src/main/webapp/v4/Message.less +189 -0
  471. package/src/legacy/tool-ui/src/main/webapp/v4/Month.less +102 -0
  472. package/src/legacy/tool-ui/src/main/webapp/v4/Notification.less +278 -0
  473. package/src/legacy/tool-ui/src/main/webapp/v4/NumberBar.less +21 -0
  474. package/src/legacy/tool-ui/src/main/webapp/v4/NumberedList.less +17 -0
  475. package/src/legacy/tool-ui/src/main/webapp/v4/Page.less +922 -0
  476. package/src/legacy/tool-ui/src/main/webapp/v4/PaginatedResult.less +89 -0
  477. package/src/legacy/tool-ui/src/main/webapp/v4/Pagination.less +80 -0
  478. package/src/legacy/tool-ui/src/main/webapp/v4/Popup.less +415 -0
  479. package/src/legacy/tool-ui/src/main/webapp/v4/PrePublish.less +5 -0
  480. package/src/legacy/tool-ui/src/main/webapp/v4/Preview.less +495 -0
  481. package/src/legacy/tool-ui/src/main/webapp/v4/ProfileDropdown.less +66 -0
  482. package/src/legacy/tool-ui/src/main/webapp/v4/QueryField.less +147 -0
  483. package/src/legacy/tool-ui/src/main/webapp/v4/RadialProgressBar.less +20 -0
  484. package/src/legacy/tool-ui/src/main/webapp/v4/Repeatable.less +102 -0
  485. package/src/legacy/tool-ui/src/main/webapp/v4/RepeatableContentInputGroup.less +731 -0
  486. package/src/legacy/tool-ui/src/main/webapp/v4/RepeatableContentSelector.less +296 -0
  487. package/src/legacy/tool-ui/src/main/webapp/v4/RepeatableTextInput.less +156 -0
  488. package/src/legacy/tool-ui/src/main/webapp/v4/Reset.less +373 -0
  489. package/src/legacy/tool-ui/src/main/webapp/v4/Revision.less +50 -0
  490. package/src/legacy/tool-ui/src/main/webapp/v4/RichText.less +32 -0
  491. package/src/legacy/tool-ui/src/main/webapp/v4/RichTextEditor.less +197 -0
  492. package/src/legacy/tool-ui/src/main/webapp/v4/SearchFields.less +403 -0
  493. package/src/legacy/tool-ui/src/main/webapp/v4/SearchInput.less +65 -0
  494. package/src/legacy/tool-ui/src/main/webapp/v4/SearchResult.less +479 -0
  495. package/src/legacy/tool-ui/src/main/webapp/v4/SearchWidget.less +697 -0
  496. package/src/legacy/tool-ui/src/main/webapp/v4/SearchWidgetAdvanced.less +115 -0
  497. package/src/legacy/tool-ui/src/main/webapp/v4/SharePreview.less +95 -0
  498. package/src/legacy/tool-ui/src/main/webapp/v4/Sortable.less +242 -0
  499. package/src/legacy/tool-ui/src/main/webapp/v4/StandardForm.less +12 -0
  500. package/src/legacy/tool-ui/src/main/webapp/v4/StyleEmbeddedContent.less +131 -0
  501. package/src/legacy/tool-ui/src/main/webapp/v4/Suggestions.less +121 -0
  502. package/src/legacy/tool-ui/src/main/webapp/v4/TabBar.less +258 -0
  503. package/src/legacy/tool-ui/src/main/webapp/v4/TabContainer.less +26 -0
  504. package/src/legacy/tool-ui/src/main/webapp/v4/Taxonomy.less +84 -0
  505. package/src/legacy/tool-ui/src/main/webapp/v4/Text.less +41 -0
  506. package/src/legacy/tool-ui/src/main/webapp/v4/TextInput.less +32 -0
  507. package/src/legacy/tool-ui/src/main/webapp/v4/ThemeBundleEditor.less +106 -0
  508. package/src/legacy/tool-ui/src/main/webapp/v4/TimedContent.less +202 -0
  509. package/src/legacy/tool-ui/src/main/webapp/v4/TreeList.less +93 -0
  510. package/src/legacy/tool-ui/src/main/webapp/v4/Utils.css +14 -0
  511. package/src/legacy/tool-ui/src/main/webapp/v4/VideoEditor.less +737 -0
  512. package/src/legacy/tool-ui/src/main/webapp/v4/ViewMirror.less +42 -0
  513. package/src/legacy/tool-ui/src/main/webapp/v4/ViewPreview.less +147 -0
  514. package/src/legacy/tool-ui/src/main/webapp/v4/Viewers.less +70 -0
  515. package/src/legacy/tool-ui/src/main/webapp/v4/Week.less +32 -0
  516. package/src/legacy/tool-ui/src/main/webapp/v4/Widget.less +224 -0
  517. package/src/legacy/tool-ui/src/main/webapp/v4/Workflow.less +153 -0
  518. package/src/legacy/tool-ui/src/main/webapp/v4/appetizeio/AppetizeioEmbedded.less +77 -0
  519. package/src/legacy/tool-ui/src/main/webapp/v4/bsp-rings-bg.svg +9 -0
  520. package/src/legacy/tool-ui/src/main/webapp/v4/graphql/GraphQL.less +171 -0
  521. package/src/legacy/tool-ui/src/main/webapp/v4/mail-publishing/MailPublishing.less +38 -0
  522. package/src/legacy/tool-ui/src/main/webapp/v4/rte/Mention.less +35 -0
  523. package/src/legacy/tool-ui/src/main/webapp/v4/rte/ProseMirror.less +759 -0
  524. package/src/legacy/tool-ui/src/main/webapp/v4/rte/ProseMirrorContainer.less +29 -0
  525. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/ai_inline_manager/views/AIInline.less +244 -0
  526. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/collab_manager/views/AvatarView.less +11 -0
  527. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/collab_manager/views/CollabEditing.less +18 -0
  528. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/custom_keyboard/CustomKeyboard.less +21 -0
  529. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/views/Enhancement.less +67 -0
  530. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/views/PreviewView.less +45 -0
  531. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/views/ProsemirrorEnhancementMenu.less +113 -0
  532. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/find_replace_manager/views/ProseMirrorFindReplace.less +174 -0
  533. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/fullscreen_manager/views/FullscreenView.less +383 -0
  534. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/list_manager/ListManager.less +42 -0
  535. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/menubar/views/MenuView.less +821 -0
  536. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/paste_manager/views/PastePopup.less +5 -0
  537. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/spellcheck/spellcheck.less +73 -0
  538. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/table_manager/views/ProseMirror-table.less +137 -0
  539. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/table_manager/views/TableSizerPopup.less +19 -0
  540. package/src/legacy/tool-ui/src/main/webapp/v4/theme/Theme.less +224 -0
  541. package/src/legacy/tool-ui/src/main/webapp/v4/translation/Translation.less +114 -0
  542. package/src/legacy/tool-ui/src/main/webapp/v4/widget/AnalyticsWidget.less +18 -0
  543. package/src/legacy/tool-ui/src/main/webapp/v4/widget/AssignmentContent.less +19 -0
  544. package/src/legacy/tool-ui/src/main/webapp/v4/widget/AssignmentDeskDashboard.less +410 -0
  545. package/src/legacy/tool-ui/src/main/webapp/v4/widget/AssignmentDeskRelatedWidget.less +3 -0
  546. package/src/legacy/tool-ui/src/main/webapp/v4/widget/AssignmentFilters.less +23 -0
  547. package/src/legacy/tool-ui/src/main/webapp/v4/widget/AssociatedContentWidget.less +92 -0
  548. package/src/legacy/tool-ui/src/main/webapp/v4/widget/BulkUpload.less +65 -0
  549. package/src/legacy/tool-ui/src/main/webapp/v4/widget/BulkWorkflow.less +103 -0
  550. package/src/legacy/tool-ui/src/main/webapp/v4/widget/Calendar.less +109 -0
  551. package/src/legacy/tool-ui/src/main/webapp/v4/widget/CalendarAccessOverview.less +26 -0
  552. package/src/legacy/tool-ui/src/main/webapp/v4/widget/CalendarEventSummary.less +15 -0
  553. package/src/legacy/tool-ui/src/main/webapp/v4/widget/ClosableWindow.less +3 -0
  554. package/src/legacy/tool-ui/src/main/webapp/v4/widget/ContentColors.less +17 -0
  555. package/src/legacy/tool-ui/src/main/webapp/v4/widget/ContentTemplatesWidget.less +123 -0
  556. package/src/legacy/tool-ui/src/main/webapp/v4/widget/Conversation.less +537 -0
  557. package/src/legacy/tool-ui/src/main/webapp/v4/widget/CopySiteWidget.less +13 -0
  558. package/src/legacy/tool-ui/src/main/webapp/v4/widget/FormSubmission.less +3 -0
  559. package/src/legacy/tool-ui/src/main/webapp/v4/widget/LiveBlog.less +158 -0
  560. package/src/legacy/tool-ui/src/main/webapp/v4/widget/PitchAssignments.less +16 -0
  561. package/src/legacy/tool-ui/src/main/webapp/v4/widget/PitchContent.less +10 -0
  562. package/src/legacy/tool-ui/src/main/webapp/v4/widget/Revisions.less +280 -0
  563. package/src/legacy/tool-ui/src/main/webapp/v4/widget/SavedSearch.less +13 -0
  564. package/src/legacy/tool-ui/src/main/webapp/v4/widget/SemRushDashboardWidget.less +6 -0
  565. package/src/legacy/tool-ui/src/main/webapp/v4/widget/SlackAuthentication.less +13 -0
  566. package/src/legacy/tool-ui/src/main/webapp/v4/widget/WorkStreams.less +39 -0
  567. package/src/legacy/tool-ui/src/platform.ts +25 -0
  568. package/src/legacy/tool-ui/src/rtc/Socket.ts +205 -0
  569. package/src/legacy/tool-ui/src/rtc/index.ts +276 -0
  570. package/src/legacy/tool-ui/src/table/README.md +3 -0
  571. package/src/legacy/tool-ui/src/table/index.ts +107 -0
  572. package/src/legacy/tool-ui/src/theme/ColorRotator.ts +32 -0
  573. package/src/legacy/tool-ui/src/theme/Theme.ts +516 -0
  574. package/src/legacy/tool-ui/src/theme/index.ts +7 -0
  575. package/src/legacy/tool-ui/src/util/README.md +42 -0
  576. package/src/legacy/tool-ui/src/util/debounce.ts +22 -0
  577. package/src/legacy/tool-ui/src/util/getComponentKey.ts +25 -0
  578. package/src/legacy/tool-ui/src/util/noise.ts +51 -0
  579. package/src/legacy/tool-ui/src/util/repaint.ts +17 -0
  580. package/src/legacy/tool-ui/src/util/storage.ts +21 -0
  581. package/src/legacy/tool-ui/src/util/string.ts +15 -0
  582. package/src/legacy/tool-ui/src/util/svg.ts +46 -0
  583. package/src/legacy/tool-ui/src/util/throttle.ts +37 -0
  584. package/src/legacy/tool-ui/src/util/transition.ts +5 -0
  585. package/src/legacy/tool-ui/src/v5.css +149 -0
  586. package/src/legacy/tool-ui/src/v5.ts +2081 -0
  587. package/src/legacy/tool-ui/src/widget/README.md +27 -0
  588. package/src/legacy/tool-ui/src/widget/index.ts +160 -0
  589. package/dist/storybook/assets/if-defined-DxmT9CH8.js +0 -1
@@ -0,0 +1,2081 @@
1
+ import './main/webapp/v4/Entry.js'
2
+
3
+ import './v5.css'
4
+ import {
5
+ getToggle as getPageNavToggle,
6
+ toggle as togglePageNav,
7
+ } from './PageNav'
8
+
9
+ import onFind from './dom/onFind'
10
+ import create from './dom/create'
11
+ import { announce } from './dom/aria'
12
+ import storage from './util/storage'
13
+ import './Celebrate'
14
+ import './ContentEditPanel'
15
+ import './Crosslinker'
16
+ import Dialog from './dialog/index'
17
+ import { getIcon } from './LucideDynamicLoader'
18
+ import './SearchControlsToggle'
19
+ import { onTransitionsEnded } from './util/transition'
20
+ import './Workstreams'
21
+ import onRemove from './dom/onRemove'
22
+ import './NavRail/index'
23
+ import Icon from './Icon/index'
24
+ import throttle from './util/throttle'
25
+ import { v1 as uuid } from 'uuid'
26
+ import {
27
+ focusRegionManager,
28
+ RegionId,
29
+ FOCUS_REGION_SELECTOR,
30
+ } from './FocusRegions'
31
+ import './SkipLinks'
32
+
33
+ /*
34
+ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
35
+ ┃ ┌───────────────────────────────────────────────────────┐ ┃
36
+ ┃ │ +-+-+-+-+-+-+-+-+-+ │ ┃░░
37
+ ┃ │ |A|T|T|E|N|T|I|O|N| │ ┃░░
38
+ ┃ │ +-+-+-+-+-+-+-+-+-+ │ ┃░░
39
+ ┃ └───────────────────────────────────────────────────────┘ ┃░░
40
+ ┃ ┃░░
41
+ ┃ The following is bridge code to maintain backwards ┃░░
42
+ ┃ compatibility with the v4 UI. The DOM structure, which v4 ┃░░
43
+ ┃ relies on, has been preserved and is mostly unchanged. If ┃░░
44
+ ┃ anything, the DOM changes are additive. A few elements ┃░░
45
+ ┃ and attributes may be new, but those should be rare. ┃░░
46
+ ┃ ┃░░
47
+ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛░░
48
+ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
49
+ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
50
+ */
51
+
52
+ // Remove any custom theme color properties from v4.
53
+ onFind('html', (html: HTMLElement) => {
54
+ for (let i = html.style.length - 1; i >= 0; i--) {
55
+ const nameString = html.style[i]
56
+ if (nameString?.startsWith('--color') || nameString?.startsWith('--rgb')) {
57
+ html.style.removeProperty(nameString)
58
+ }
59
+ }
60
+ })
61
+
62
+ // Modify the global page nav.
63
+ onFind('.Page-nav', (nav: HTMLElement) => {
64
+ // Nav is always interactive in v5.
65
+ nav.removeAttribute('inert')
66
+
67
+ // Find the original toggle button and move it to the header.
68
+ // Note that in the future this should be done on the server-side
69
+ const toggle = document.querySelector<HTMLElement>('.Page-navToggle')
70
+ if (!toggle) return
71
+ document
72
+ .querySelector('.Page-header')
73
+ ?.insertAdjacentElement('afterbegin', toggle)
74
+ })
75
+
76
+ onFind('.Page-navItemToggle', (toggle: HTMLElement) => {
77
+ const icon = new Icon()
78
+ icon.symbol = 'chevron-down'
79
+ toggle?.prepend(icon)
80
+ })
81
+
82
+ // Reimplement the nav toggle behavior.
83
+ onFind('.Page-navToggle', (toggle: HTMLElement) => {
84
+ function updateToggleLabel(expanded: boolean): void {
85
+ const label = expanded
86
+ ? window.BRIGHTSPOT?.ui.tooltips.menu.close
87
+ : window.BRIGHTSPOT?.ui.tooltips.menu.open
88
+ toggle.title = label
89
+ toggle.ariaLabel = label
90
+ }
91
+
92
+ toggle.onclick = () => {
93
+ const isExpanding = toggle.ariaExpanded === 'false'
94
+ toggle.ariaExpanded = isExpanding ? 'true' : 'false'
95
+ updateToggleLabel(isExpanding)
96
+ togglePageNav(isExpanding ? 'ON' : 'OFF')
97
+
98
+ const navLinks = document.querySelectorAll<HTMLElement>(
99
+ '.Page-main > btu-nav-rail a',
100
+ )
101
+
102
+ const page = toggle.closest<HTMLElement>('.Page')
103
+ const rail = document.querySelector<HTMLElement>(
104
+ '.Page-main > btu-nav-rail',
105
+ )
106
+
107
+ page?.classList.add('is-nav-animating')
108
+ if (rail) {
109
+ onTransitionsEnded(rail).then(() =>
110
+ page?.classList.remove('is-nav-animating'),
111
+ )
112
+ } else {
113
+ page?.classList.remove('is-nav-animating')
114
+ }
115
+
116
+ // When page nav is toggled close, update the tooltips to the item name
117
+ if (!isExpanding) {
118
+ navLinks.forEach((item: HTMLElement) => {
119
+ item.title = item.querySelector(':scope > span')?.textContent ?? ''
120
+ })
121
+ } else {
122
+ // When page nav is toggled open, update the tooltips on
123
+ // expandable items to expand/collapse
124
+ navLinks.forEach((item: HTMLElement) => {
125
+ if (item.parentElement?.hasAttribute('aria-expanded')) {
126
+ item.title = item.parentElement?.classList.contains('is-expanded')
127
+ ? window.BRIGHTSPOT?.ui.tooltips.collapse
128
+ : window.BRIGHTSPOT?.ui.tooltips.expand
129
+ } else {
130
+ item.title = ''
131
+ }
132
+ })
133
+ }
134
+ }
135
+
136
+ const lastToggleState = getPageNavToggle()
137
+ if (window.matchMedia('not all and (min-width: 768px)').matches) {
138
+ toggle.setAttribute('aria-expanded', 'false')
139
+ } else if (lastToggleState !== null) {
140
+ toggle.setAttribute(
141
+ 'aria-expanded',
142
+ lastToggleState === 'ON' ? 'true' : 'false',
143
+ )
144
+ }
145
+
146
+ updateToggleLabel(toggle.ariaExpanded === 'true')
147
+
148
+ // If page nav is collapsed, update the tooltips to the item name
149
+ if (toggle.getAttribute('aria-expanded') === 'false') {
150
+ document
151
+ .querySelectorAll<HTMLElement>('.Page-main > btu-nav-rail a')
152
+ .forEach((item: HTMLElement) => {
153
+ item.title = item.querySelector(':scope > span')?.textContent ?? ''
154
+ })
155
+ }
156
+ })
157
+
158
+ onFind('.toolUserDisplay', (userDisplay) => {
159
+ // Move site switcher out of `.toolUserDisplay` and into the tool title.
160
+ const sitename = userDisplay.querySelector('.toolUserSite')
161
+ if (sitename) {
162
+ const toolTitle = document.querySelector('.toolTitle')
163
+ toolTitle?.append(sitename)
164
+ }
165
+ })
166
+
167
+ onFind('.ContentEdit-top', (top) => {
168
+ const contentEdit = top.closest<HTMLElement>('.ContentEdit')
169
+
170
+ const wips = top.querySelector('.WorkInProgressSaveStatus')
171
+ if (wips) top.classList.add('has-wips')
172
+
173
+ /*
174
+ Moves (Banner) Messages in content edit
175
+ into the sibling to Content edit left called "top"
176
+ so that the message can span across the grid as designed.
177
+ ie:
178
+
179
+ From:
180
+ -- ContentEdit-left ------------------
181
+ | --- ContentEdit-main ------------
182
+ | | --- Message ---------------
183
+ ...
184
+
185
+ To:
186
+ -- ContentEdit-left ------------------
187
+ |_____________________________________
188
+ .
189
+ -- ContentEdit-top -------------------
190
+ | --- Message ---------------------
191
+ ...
192
+ */
193
+ const message = contentEdit?.querySelector(
194
+ ':scope .ContentEdit-main > .Message',
195
+ )
196
+ if (message) {
197
+ top.insertAdjacentElement('afterend', message)
198
+
199
+ if (!message.closest('.LiveBlogGrid')) {
200
+ const content = message.closest('.Popup-content, .Page-content')
201
+ content?.classList.add('has-message')
202
+ }
203
+ }
204
+
205
+ /*
206
+ Moves the title element into the top element.
207
+ This is so the "ContentEdit-overlay" element will be a child of the toolbar.
208
+ */
209
+ const title = contentEdit?.querySelector<HTMLElement>('.ContentEdit-title')
210
+ if (!title) return
211
+
212
+ const overlayDropdown = title.querySelector<HTMLElement>(
213
+ 'button.ContentEdit-overlay:not(empty)',
214
+ )
215
+ if (!overlayDropdown) return
216
+ top.prepend(overlayDropdown)
217
+ top.classList.add('has-overlay')
218
+ })
219
+
220
+ /*
221
+ Hide the search input in favor of the icon button when on mobile.
222
+ Note that this is primarily for keyboard/screen reader a11y.
223
+ */
224
+ onFind('.Page-header.mobile', (title) => {
225
+ const header = title.closest('.Page-header')
226
+ const search = header?.querySelector('.Page-search')
227
+ if (!search) return
228
+
229
+ search.querySelector(':scope > button')?.removeAttribute('hidden')
230
+ search.querySelector('btu-search-input')?.setAttribute('hidden', 'true')
231
+ })
232
+
233
+ /*
234
+ On mobile screens this adds the ability to click/toggle the page search icon.
235
+ So the search input can show/hide like the design.
236
+ */
237
+ onFind('.Page .Page-search', (search: HTMLElement) => {
238
+ search.querySelector(':scope > button')?.addEventListener('click', () => {
239
+ search.setAttribute(
240
+ 'aria-expanded',
241
+ search.getAttribute('aria-expanded') === 'true' ? 'false' : 'true',
242
+ )
243
+
244
+ if (search.getAttribute('aria-expanded') === 'true') {
245
+ // Show the search input, and hide the icon button (a11y).
246
+ search.querySelector('btu-search-input')?.removeAttribute('hidden')
247
+ search.querySelector(':scope > button')?.setAttribute('hidden', 'true')
248
+
249
+ search
250
+ .querySelector<HTMLInputElement>('btu-search-input > btu-input > input')
251
+ ?.click()
252
+ }
253
+ })
254
+ })
255
+
256
+ /*
257
+ When global search is opening, display a loading spinner in the input field.
258
+ */
259
+ onFind(
260
+ '.Page .Page-search > btu-search-input > btu-input > input',
261
+ (searchInput: HTMLElement) => {
262
+ searchInput.addEventListener('click', () => {
263
+ if (searchInput.closest('.Page:has( .Popup[name="miscSearch"])')) {
264
+ return
265
+ }
266
+
267
+ searchInput.closest('.Page')?.classList.add('is-searchOpening')
268
+
269
+ onFind(
270
+ '.Page dialog:not(:modal)[open] .Popup.loaded.is-ready.is-visible .SearchWidget.is-transitioned',
271
+ (widget: HTMLElement) => {
272
+ widget.closest('.Page')?.classList.remove('is-searchOpening')
273
+ },
274
+ )
275
+ })
276
+ },
277
+ )
278
+
279
+ /*
280
+ When global search is closed, the search input should also collapse.
281
+ Listen on the dialog element so this fires for all close paths (close button, Escape, backdrop).
282
+ */
283
+ onFind('btu-dialog:has(.SearchWidget.is-global)', (dialog: HTMLElement) => {
284
+ dialog.addEventListener('btu-dialog-closed', () => {
285
+ document
286
+ .querySelector('.Page .Page-search')
287
+ ?.setAttribute('aria-expanded', 'false')
288
+
289
+ // Reset the search input visibility when on mobile.
290
+ const search = document.querySelector('.Page-header.mobile .Page-search')
291
+ search?.querySelector('btu-search-input')?.setAttribute('hidden', 'true')
292
+ search?.querySelector(':scope > button')?.removeAttribute('hidden')
293
+ })
294
+ })
295
+
296
+ /*
297
+ When Page-content contains its own query/search field, the global search input should collapse
298
+ by default.
299
+ */
300
+ onFind('.Page-content:has(.SearchWidget-query)', (page: HTMLElement) => {
301
+ page.previousElementSibling
302
+ ?.querySelector('.Page-search')
303
+ ?.setAttribute('aria-expanded', 'false')
304
+ })
305
+
306
+ /*
307
+ Clones a site logo (if exists) into the site switcher logo area.
308
+ This could be done on the backend, but it's easier to do it here for now.
309
+
310
+ From:
311
+ -----------------
312
+ <div class="siteSwitch-logo"></div>
313
+ -----------------
314
+
315
+ To:
316
+ -----------------
317
+ <div class="siteSwitch-logo"> <img ...> </div>
318
+ -----------------
319
+ */
320
+ onFind('.siteSwitch-logo', (target) => {
321
+ const logo = document.querySelector('.Page-title > a > img')
322
+ if (logo) {
323
+ target.append(logo.cloneNode())
324
+ }
325
+ })
326
+
327
+ /** Creates a new label to show the tab item selected in a menu tab bar
328
+ * From:
329
+ -----------------
330
+ <div class="TabBar is-menuOnly">
331
+ <div class="TabBar-menu">
332
+ <button class="TabBar-menuToggle">...</button>
333
+ <div class="TabBar-menuList">...</div>
334
+ </div>
335
+ </div>
336
+ -----------------
337
+
338
+ To:
339
+ -----------------
340
+ <div class="TabBar is-menuOnly">
341
+ <div class="TabBar-menu">
342
+ <div class="TabBar-selected-text">...</div>
343
+ <button class="TabBar-menuToggle">...</button>
344
+ <div class="TabBar-menuList">...</div>
345
+ </div>
346
+ </div>
347
+ -----------------
348
+ */
349
+ onFind('.TabBar.is-menuOnly', (container: HTMLElement) => {
350
+ const menu = container.querySelector<HTMLElement>('.TabBar-menu')
351
+ if (!menu) return
352
+
353
+ const selectedItem = menu.querySelector<HTMLElement>(
354
+ ':scope .TabBar-menuList > .TabBar-menuItem.is-selected',
355
+ )
356
+ const tabBarSelected = create('div', {
357
+ className: 'TabBar-selected-text',
358
+ innerText: selectedItem?.dataset.tab,
359
+ })
360
+ menu.insertBefore(tabBarSelected, menu.firstElementChild)
361
+ menu
362
+ .querySelectorAll<HTMLElement>('.TabBar-menuItem')
363
+ .forEach((item: HTMLElement) => {
364
+ item.addEventListener('click', (event) => {
365
+ event?.preventDefault()
366
+ tabBarSelected.innerText = item.dataset.tab ?? ''
367
+ })
368
+ })
369
+ })
370
+
371
+ /** Adds a new data-label attribute to search result tab items
372
+ * for icon styling the tab buttons based on their text content
373
+ */
374
+ onFind('.SearchResult-views > ul', (tabs: HTMLElement) => {
375
+ tabs
376
+ .querySelectorAll<HTMLElement>(':scope > li > a')
377
+ .forEach((tabItem: HTMLElement) => {
378
+ const label = tabItem.textContent?.toLowerCase() ?? ''
379
+ tabItem.dataset.label = label
380
+ tabItem.ariaLabel = label
381
+ tabItem.title = label
382
+ })
383
+ })
384
+
385
+ /** Adds a disabled edit button above empty embedded guides,
386
+ * matching style and placement of existing edit buttons.
387
+ */
388
+ onFind('.Guide-field > .Guide-embedded:empty', (guide) => {
389
+ const editButton = create('a', {
390
+ className: 'Guide-edit',
391
+ href: '#',
392
+ disabled: true,
393
+ 'aria-disabled': true,
394
+ })
395
+
396
+ guide.append(editButton)
397
+ })
398
+
399
+ /**
400
+ * Adds a styling hook to indicate that the Guide edit field
401
+ * has dropdown actions.
402
+ */
403
+ onFind(
404
+ '.Popup[name="edit-guide"] .RCIG-item > btu-dropdown:not(:empty)',
405
+ (dropdown: HTMLElement) => {
406
+ dropdown?.closest('.RCIG-item')?.classList.add('has-dropdown-actions')
407
+ },
408
+ )
409
+
410
+ /** Applies the following updates to table structure to fit v5 design:
411
+ * Adds a LinkList-wrapper to LinkList tables in DashboardWidgets for horizontal scrolling
412
+ * For LinkTable & LinkList tables, re-orders LinkTable-main or LinkList-main column so that:
413
+ * 1). They are the first column if no checkbox column is present.
414
+ * 2). They are the second column when checkbox column exists in the table.
415
+ */
416
+ onFind('table', (table: HTMLElement) => {
417
+ if (
418
+ ['LinkList', 'LinkTable', 'term-results'].some((cls) =>
419
+ table.classList.contains(cls),
420
+ )
421
+ ) {
422
+ const headerRow = table.querySelector<HTMLElement>(':scope > thead > tr')
423
+ const rows = table.querySelectorAll<HTMLElement>('tbody > tr')
424
+ const main = table.querySelector<HTMLElement>(
425
+ '.LinkTable-main, .LinkList-main, .term-stats-head',
426
+ )
427
+ const row = main?.closest('tr')
428
+ if (!row || !main) return
429
+ const hasCheckbox = table.querySelector<HTMLElement>(
430
+ 'th > input[type="checkbox"], td > input[type="checkbox"]',
431
+ )
432
+ if (headerRow) {
433
+ const idx = Array.from(row.children).indexOf(main)
434
+ let headerIdx = idx
435
+ for (let i = 0; i < idx; i++) {
436
+ const hItem = headerRow.children[i]
437
+ if (hItem instanceof HTMLTableCellElement && hItem.colSpan > 1) {
438
+ headerIdx = headerIdx - hItem.colSpan + 1
439
+ }
440
+ }
441
+
442
+ const thead = headerRow.children[headerIdx]
443
+ if (hasCheckbox) {
444
+ headerRow.insertBefore(thead, headerRow.children[1])
445
+ } else {
446
+ headerRow.insertBefore(thead, headerRow.firstElementChild)
447
+ }
448
+ }
449
+ rows.forEach((row) => {
450
+ const main = row.querySelector<HTMLElement>(
451
+ ':scope > td.LinkTable-main, :scope > td.LinkList-main',
452
+ )
453
+ if (!main) return
454
+ if (hasCheckbox) {
455
+ row.insertBefore(main, row.children[1])
456
+ } else {
457
+ row.insertBefore(main, row.firstElementChild)
458
+ }
459
+ })
460
+ }
461
+ if (
462
+ table.closest(
463
+ '.DashboardWidget:not(.widget-scheduledEvents), .SearchWidget-result, #term-finder-result',
464
+ )
465
+ ) {
466
+ const cellCount = table.querySelectorAll<HTMLElement>(
467
+ 'tbody tr:first-child > td',
468
+ ).length
469
+ /* do not add scrolling to table if:
470
+ * it is a single column table or
471
+ * if it has two columns and one of those is viewers (e.g.: My Activity Widget)
472
+ */
473
+ if (
474
+ cellCount === 1 ||
475
+ (cellCount === 2 && table.querySelector('tr td.LinkList-viewers'))
476
+ ) {
477
+ return
478
+ }
479
+
480
+ const parentNode = table.parentElement
481
+ if (!parentNode) return
482
+ if (parentNode.childElementCount > 1) {
483
+ const tableContainer = create('div', {
484
+ className: 'LinkList-wrapper',
485
+ })
486
+ parentNode.insertBefore(tableContainer, table)
487
+ tableContainer.appendChild(table)
488
+ } else {
489
+ parentNode.classList.add('LinkList-wrapper')
490
+ }
491
+ }
492
+ })
493
+
494
+ /* Move the pagination to bottom of Notification container. */
495
+ onFind('.Notification-pagination', (pagination) => {
496
+ pagination
497
+ .closest('.ToolUserNotifications')
498
+ ?.querySelector('.Notification')
499
+ ?.insertAdjacentElement('beforeend', pagination)
500
+ })
501
+
502
+ /* Move the PaginatedResult (PR) pagination after table. */
503
+ onFind('.PR-pagination', (pagination) => {
504
+ pagination
505
+ .closest('.PR')
506
+ ?.querySelector('.LinkList-wrapper, .LinkTable')
507
+ ?.insertAdjacentElement('afterend', pagination)
508
+ })
509
+
510
+ /* Move the Dashboard Widget Pagination. */
511
+ onFind('.DashboardWidget-pagination', (pagination) => {
512
+ // Move to bottom of workstream widget
513
+ if (pagination.closest('.p-workStreams')) {
514
+ pagination
515
+ .closest('.p-workStreams')
516
+ ?.querySelector('.block:not(:has(+ .block))')
517
+ ?.insertAdjacentElement('afterend', pagination)
518
+ } else {
519
+ /* moves pagination after tables for non-calendar dashboard widgets */
520
+ pagination
521
+ .closest('.DashboardWidget:not(.widget-scheduledEvents)')
522
+ ?.querySelector('.LinkList-wrapper, .LinkTable, .LinkList')
523
+ ?.insertAdjacentElement('afterend', pagination)
524
+ }
525
+ })
526
+
527
+ /* Upgrade the v4 copy button to a btu-copy-to-clipboard element. */
528
+ onFind(
529
+ '.LookingGlass-actions[data-action="copy"]',
530
+ (legacyElement: HTMLElement) => {
531
+ const copyToClipboard = document.createElement('btu-copy-to-clipboard')
532
+ copyToClipboard.classList.add(...legacyElement.classList)
533
+ const key = legacyElement.querySelector(':scope > a')?.getAttribute('href')
534
+ const title = legacyElement.querySelector(':scope > a')?.textContent ?? ''
535
+ const copySuccessMessage =
536
+ legacyElement.querySelector(':scope > span')?.textContent ?? ''
537
+ if (!key) return
538
+
539
+ copyToClipboard.copydata = key
540
+ copyToClipboard.title = title
541
+ copyToClipboard.label = title
542
+
543
+ legacyElement.parentNode?.insertBefore(copyToClipboard, legacyElement)
544
+ legacyElement.remove()
545
+
546
+ copyToClipboard.addEventListener(
547
+ 'btu-copy-to-clipboard-copied',
548
+ (event) => {
549
+ if (event.target instanceof HTMLElement) {
550
+ const button = event.target.querySelector('button')
551
+ if (button) button.dataset.successLabel = copySuccessMessage
552
+ }
553
+
554
+ if (copySuccessMessage) {
555
+ announce(copySuccessMessage)
556
+ }
557
+ },
558
+ )
559
+ },
560
+ )
561
+
562
+ /*
563
+ This checks for when the mobile breakpoint is reached
564
+ and adds a classname of 'mobile' to the page header.
565
+ Normally we wouldn't do this, but it's easier to share styles
566
+ when you can stack query selectors like:
567
+ ---------
568
+ .Page-header.mobile,
569
+ .Page-header:has(.ContentEdit-title--v5) {
570
+ <shared styles>
571
+ }
572
+ ---------
573
+ also, @media and @apply cannot be used in conjunction.
574
+ */
575
+ onFind('.Page-header', (header) => {
576
+ const tabletQuery = window.matchMedia('not all and (min-width: 768px)')
577
+ function handleBreakpoint(matches: boolean) {
578
+ const search = header.querySelector('.Page-search')
579
+ if (matches) {
580
+ header.classList.add('mobile')
581
+ document.body.classList.remove('has-navrail-expanded')
582
+ // Restore mobile search visibility: show icon button, hide input.
583
+ // Reset aria-expanded so CSS driven by it (which hides the button) stays in sync.
584
+ if (search) {
585
+ search.setAttribute('aria-expanded', 'false')
586
+ search.querySelector(':scope > button')?.removeAttribute('hidden')
587
+ search.querySelector('btu-search-input')?.setAttribute('hidden', 'true')
588
+ }
589
+ } else {
590
+ header.classList.remove('mobile')
591
+ // Restore desktop search visibility: show input, hide icon button.
592
+ document.body.classList.toggle(
593
+ 'has-navrail-expanded',
594
+ getPageNavToggle() === 'ON',
595
+ )
596
+
597
+ // Restore search visibility when switching from mobile to desktop.
598
+ if (search) {
599
+ search.querySelector('btu-search-input')?.removeAttribute('hidden')
600
+ search
601
+ .querySelector<HTMLElement>(':scope > button')
602
+ ?.setAttribute('hidden', 'true')
603
+ }
604
+ }
605
+ }
606
+ handleBreakpoint(tabletQuery.matches)
607
+ tabletQuery.addEventListener('change', (ev) => handleBreakpoint(ev.matches))
608
+ })
609
+
610
+ /* Formats day of week labels in date picker to show only first two letters */
611
+ onFind(
612
+ ['.DateStringCalendar .Month-dowLabel', '.Week-day > .Week-dow'],
613
+ (dateLabelEl: HTMLElement) => {
614
+ const label = dateLabelEl.innerText.substring(0, 2)
615
+ dateLabelEl.innerText = label
616
+ },
617
+ )
618
+
619
+ /*
620
+ Change the custom property used in v5 for status color so that
621
+ it won't conflict and be overridden by the workflow color in v4.
622
+
623
+ Note the variety of markup used for status labels in the v4 UI.
624
+ */
625
+ onFind(
626
+ '.Revisions-statusLabel, .Message.is-revision, .ContentSummary-link > .Label, .ComboInput-selected .Label, .ContentSelector-search > .Label, .LinkTable-label > .Label',
627
+ (statusLabel: HTMLElement) => {
628
+ const workflowColorFromDataset = statusLabel.dataset.statusColor
629
+ const style = statusLabel.style
630
+ const workflowColor =
631
+ workflowColorFromDataset ?? style.getPropertyValue('background')
632
+
633
+ if (workflowColor) {
634
+ style.setProperty('--status-color', workflowColor)
635
+ statusLabel.dataset.statusColor = workflowColor
636
+ style.removeProperty('background')
637
+ style.removeProperty('color')
638
+ } else {
639
+ style.removeProperty('--workflow-color')
640
+ }
641
+ },
642
+ )
643
+
644
+ /*
645
+ Gives reply button inner text "Reply" or "Reply (1)" based on the number of replies.
646
+ Also moves the reply button to the end of the post.
647
+ */
648
+ onFind('.Conversation-post .Conversation-reply', (reply: HTMLElement) => {
649
+ if (reply.innerText) {
650
+ reply.innerText = reply.title + ' (' + reply.innerText + ')'
651
+ } else {
652
+ reply.innerText = reply.title
653
+ }
654
+
655
+ const post = reply.closest('.Conversation-post')
656
+ post?.append(reply)
657
+ })
658
+
659
+ /* adds title attributes for ul.LinkTable list items to show the entire text on hover */
660
+ onFind('ul.LinkTable', (list: HTMLElement) => {
661
+ const liItems = list.querySelectorAll<HTMLElement>(':scope > li')
662
+ liItems.forEach((item: HTMLElement) => {
663
+ item = item.querySelector('.ViewWatchers-item-name') || item
664
+ item.title = item.textContent || ''
665
+ })
666
+ })
667
+
668
+ onFind('.DashboardWidget-maximize', (maximize: HTMLElement) => {
669
+ if (maximize.closest('.DashboardRow-cell')?.matches('.is-maximized')) {
670
+ maximize.setAttribute('aria-expanded', 'true')
671
+ }
672
+
673
+ maximize.addEventListener('click', async (event) => {
674
+ const target = event.target
675
+ if (!(target instanceof HTMLElement)) return
676
+
677
+ const toggleMaximize = (widget: HTMLElement) => {
678
+ if (widget instanceof HTMLElement) {
679
+ if (maximize.toggleAttribute('aria-expanded')) {
680
+ widget.classList.add('is-maximized')
681
+ document.body.classList.add('has-dashboardWidgetMaximized')
682
+ } else {
683
+ widget.classList.remove('is-maximized')
684
+ document.body.classList.remove('has-dashboardWidgetMaximized')
685
+ }
686
+ }
687
+ }
688
+
689
+ const dc = target.closest<HTMLElement>('.DashboardRow-cell')
690
+ if (!dc) return
691
+
692
+ // Transition the backdrop separately to avoid unwanted transitions.
693
+ if (!dc.classList.contains('is-maximized')) {
694
+ document.body.classList.add('transition-backdrop')
695
+ dc.classList.add('transition-dashboard-widget')
696
+ const transition = document.startViewTransition(() => toggleMaximize(dc))
697
+ await transition.finished
698
+ // De-reference the unique view transition id when the transition is complete.
699
+ dc.classList.remove('transition-dashboard-widget')
700
+ } else {
701
+ document.body.classList.remove('transition-backdrop')
702
+ toggleMaximize(dc)
703
+ }
704
+
705
+ maximize.focus()
706
+ })
707
+ })
708
+
709
+ /* Adds focus point toggle to enable/disable focus ui on image preview */
710
+ onFind('.ImageEditor-groups', (imageSizeGroups: HTMLElement) => {
711
+ const focusToggle = create('button', {
712
+ className: 'ImageEditor-focusToggle',
713
+ type: 'button',
714
+ })
715
+ let groupsDropdown: HTMLElement | null = null
716
+ imageSizeGroups.prepend(focusToggle)
717
+ focusToggle.addEventListener('click', (event) => {
718
+ const isActive = imageSizeGroups.classList.toggle('is-focus-active')
719
+ if (!groupsDropdown) {
720
+ groupsDropdown = imageSizeGroups.querySelector<HTMLElement>(
721
+ ':scope > .ComboInput',
722
+ )
723
+ }
724
+ if (!groupsDropdown) return
725
+ groupsDropdown.tabIndex = isActive ? 0 : -1
726
+ })
727
+ })
728
+
729
+ /* Copies search reset button tooltip to inner text */
730
+ onFind('.SearchWidget-reset', (reset: HTMLElement) => {
731
+ reset.innerText = reset.title?.split(' ')[0]
732
+ })
733
+
734
+ /*
735
+ Pulls out the status label from image search result caption so that it can be styled separately.
736
+ */
737
+ onFind(
738
+ '.SearchResult-images a[data-label-html] > figcaption > .contentTitle--v5 > .Label',
739
+ (status: HTMLElement) => {
740
+ status.classList.add('contentStatus--v5')
741
+ status.parentElement?.insertBefore(status, status.parentElement.firstChild)
742
+ },
743
+ )
744
+
745
+ /*
746
+ Copies image height style from the SearchResult-images dataset so that the CSS can access it.
747
+ */
748
+ onFind('.SearchResult-images[data-height]', (images: HTMLElement) => {
749
+ images.style.setProperty('--image-height', images.dataset.height + 'dvh')
750
+
751
+ const observer = new MutationObserver((mutationList) => {
752
+ for (const mutation of mutationList) {
753
+ if (
754
+ mutation.type === 'attributes' &&
755
+ mutation.attributeName === 'data-height' &&
756
+ images.dataset.height
757
+ ) {
758
+ images.style.setProperty(
759
+ '--image-height',
760
+ images.dataset.height + 'dvh',
761
+ )
762
+ }
763
+ }
764
+ })
765
+
766
+ observer.observe(images, {
767
+ attributes: true,
768
+ attributeFilter: ['data-height'],
769
+ })
770
+ })
771
+
772
+ /*
773
+ Adds a toggle button to show/hide the image height input field in the search result.
774
+ */
775
+ onFind('.SearchResult-imageHeightInput', (input: HTMLInputElement) => {
776
+ const button = create('button', {
777
+ className: 'SearchResult-imageHeightToggle v5-only',
778
+ type: 'button',
779
+ ariaExpanded: 'false',
780
+ title: window.BRIGHTSPOT?.ui.tooltips.imageHeightOpen,
781
+ ariaLabel: window.BRIGHTSPOT?.ui.tooltips.imageHeightOpen,
782
+ })
783
+
784
+ const container = create('div', {
785
+ className: 'SearchResult-imageHeightContainer v5-only',
786
+ })
787
+
788
+ // Wrap the input in the container and insert toggle button before input.
789
+ input.insertAdjacentElement('beforebegin', container)
790
+ container.append(input)
791
+ container.prepend(button)
792
+
793
+ button?.addEventListener('click', () => {
794
+ if (button.ariaExpanded === 'false') {
795
+ button.ariaExpanded = 'true'
796
+ button.title = window.BRIGHTSPOT?.ui.tooltips.imageHeightClose
797
+ button.ariaLabel = window.BRIGHTSPOT?.ui.tooltips.imageHeightClose
798
+ input?.classList.add('is-open')
799
+ } else {
800
+ button.ariaExpanded = 'false'
801
+ button.title = window.BRIGHTSPOT?.ui.tooltips.imageHeightOpen
802
+ button.ariaLabel = window.BRIGHTSPOT?.ui.tooltips.imageHeightOpen
803
+ input?.classList.remove('is-open')
804
+ }
805
+ })
806
+ })
807
+
808
+ /* Move the ask-ai wrapper in a level so it can participate in the grid layout. */
809
+ onFind('.Page-askAi', (askAi) => {
810
+ document
811
+ .querySelector('.Page-container')
812
+ ?.insertAdjacentElement('beforebegin', askAi)
813
+ document.body.classList.add('has-ask-ai')
814
+ })
815
+
816
+ /*
817
+ Move the notifications panel into the nav rail. In the future this should be
818
+ done on the server-side
819
+ */
820
+ onFind('.Page-notifications', (notifications: HTMLElement) => {
821
+ const link = notifications.querySelector('a')
822
+ const icon = new Icon()
823
+ icon.symbol = 'bell'
824
+ icon.fill = 'gradient'
825
+ const label = document.createElement('span')
826
+ label.textContent = window.BRIGHTSPOT?.ui.tooltips.notifications
827
+ link?.appendChild(icon)
828
+ link?.appendChild(label)
829
+
830
+ const navRail = document.querySelector('btu-nav-rail')
831
+ if (!navRail) return
832
+ const avatar = navRail.querySelector('.Page-avatar')
833
+ const avatarLi = avatar?.closest('li')
834
+ if (avatarLi) {
835
+ const notificationsLi = document.createElement('li')
836
+ notificationsLi.appendChild(notifications)
837
+ navRail.querySelector('ul')?.insertBefore(notificationsLi, avatarLi)
838
+ } else {
839
+ navRail.addItem(notifications)
840
+ }
841
+ })
842
+
843
+ /*
844
+ Add wrapper to the frames in Create with AI popup.
845
+ Needed to keep the scroll position at the bottom when the frames are updating on response.
846
+ */
847
+ onFind('.Widget-title + .AIChat-frame', (frame: HTMLElement) => {
848
+ const wrapper = document.createElement('div')
849
+ wrapper.classList.add('AIChat-wrapper')
850
+ wrapper.classList.add('!px-0')
851
+ frame.parentElement?.insertBefore(wrapper, frame)
852
+ wrapper.appendChild(frame)
853
+ })
854
+
855
+ /*
856
+ In board view there's an additional ComboInput and new parent element for
857
+ holding the "show fields" button. To display these in a way
858
+ that makes sense, we make the board fields parent adopt the default sorts.
859
+
860
+ From:
861
+ -----------------
862
+ [Board Fields]
863
+ [Default Sorts]
864
+ -----------------
865
+
866
+ To:
867
+ -----------------
868
+ [Board Fields]
869
+ |- [Default Sorts]
870
+ -----------------
871
+ */
872
+ onFind('.SearchResult-board-fields', (bfContainer) => {
873
+ const ctx = bfContainer.closest('.SearchResult')
874
+ if (!ctx) return
875
+ const defaultSorts = ctx.querySelector('.SearchResult-sorts')
876
+ if (!defaultSorts) return
877
+ bfContainer.prepend(defaultSorts)
878
+ })
879
+
880
+ /* Unhides the progressBar inside Content Edit workstreams banner
881
+ From:
882
+ -----------------
883
+ <div class="progressBar" style="width:" hidden>
884
+ -----------------
885
+
886
+ To:
887
+ -----------------
888
+ <div class="progressBar" style="--progressPct:">
889
+ |- ::before
890
+ </div
891
+ -----------------
892
+ */
893
+ onFind('.ContentEdit-workstreamProgress .progressBar', (bar: HTMLElement) => {
894
+ bar.classList.remove('hidden')
895
+ bar.removeAttribute('hidden')
896
+ const progressPct = bar.style.width
897
+ bar.style.removeProperty('width')
898
+ bar.style.setProperty('--progressPct', progressPct)
899
+ })
900
+
901
+ /* Adds toggle functionality to the filters in The Shelf when it is in the right rail */
902
+ onFind('.ContentEditDrawer-widget-filters', (filters) => {
903
+ const isExpanded = storage.get('BSP.ContentEdit.shelfFilters') === 'EXPANDED'
904
+ const mainDrawer = filters.closest('.ContentEditDrawer')
905
+ const drawerWidgetFrame = filters.closest('.ContentEditDrawer-widget')
906
+ if (!mainDrawer || !drawerWidgetFrame) return
907
+ const label = isExpanded
908
+ ? window.BRIGHTSPOT?.ui.tooltips.filtersHide
909
+ : window.BRIGHTSPOT?.ui.tooltips.filtersShow
910
+ const button = create('button', {
911
+ className: 'ContentEditDrawer-filtersToggle v5-only',
912
+ type: 'button',
913
+ title: label,
914
+ ariaLabel: label,
915
+ ariaExpanded: isExpanded ? 'true' : 'false',
916
+ })
917
+
918
+ filters?.classList.toggle('is-visible', isExpanded)
919
+
920
+ // The frame gets reloaded after selecting an option in the filters.
921
+ // Replace the toggle with a new one each time this happens so that
922
+ // there are not multiple and it has the right event listener.
923
+ const closeWidget = mainDrawer?.querySelector('.closeButton')
924
+ const existingToggle = mainDrawer?.querySelector(
925
+ '.ContentEditDrawer-filtersToggle',
926
+ )
927
+ existingToggle?.remove()
928
+ closeWidget?.parentNode?.insertBefore(button, closeWidget)
929
+ if (drawerWidgetFrame.classList.contains('is-loading')) {
930
+ drawerWidgetFrame.classList.remove('is-loading')
931
+ }
932
+
933
+ button.addEventListener('click', () => {
934
+ filters?.classList.toggle('is-visible')
935
+
936
+ const isExpanded = filters?.classList.contains('is-visible')
937
+ const label = isExpanded
938
+ ? window.BRIGHTSPOT?.ui.tooltips.filtersHide
939
+ : window.BRIGHTSPOT?.ui.tooltips.filtersShow
940
+
941
+ storage.set(
942
+ 'BSP.ContentEdit.shelfFilters',
943
+ isExpanded ? 'EXPANDED' : 'COLLAPSED',
944
+ )
945
+ button.title = label
946
+ button.ariaLabel = label
947
+ button.ariaExpanded = isExpanded ? 'true' : 'false'
948
+ })
949
+ filters.addEventListener('change', () => {
950
+ drawerWidgetFrame.classList.add('is-loading')
951
+ })
952
+ })
953
+
954
+ /** Live Blog Post view
955
+ 1. Highlights the selected post in left rail list of live blog posts
956
+ If a new post is being created , New Post button is selected
957
+
958
+ 2. Mobile view styling for live blog posts:
959
+ Creates a new toggle button that expands/collapses the live blog post list
960
+ From:
961
+ -- Page-main ------------------
962
+ | --- Page-header ------------
963
+ | | --- ContentEdit-title--v5 ---------------
964
+ | | | ...
965
+ | --- Page-content ------------
966
+ | | --- Grid (live blog posts grid)
967
+ ...
968
+
969
+ To:
970
+ -- Page-main ------------------
971
+ | --- Page-header ------------
972
+ | | --- LiveBlogPostList-open
973
+ | | --- ContentEdit-title--v5 ---------------
974
+ | | | ...
975
+ | --- Page-content ------------
976
+ | | --- Grid (live blog posts grid)
977
+ ...
978
+ */
979
+ onFind('.LiveBlogPostListNav.loaded', (listContainer: HTMLElement) => {
980
+ const pageContainer = listContainer.closest<HTMLElement>('.Page-main')
981
+ const gridContent = pageContainer?.querySelector<HTMLElement>('.LiveBlogGrid')
982
+
983
+ if (!gridContent) return
984
+
985
+ const newPostButton = listContainer.querySelector<HTMLElement>(
986
+ '.LiveBlogPostList-controls a:not(.LiveBlogPostListNav-refresh)',
987
+ )
988
+ if (!newPostButton) return
989
+
990
+ const selectNavItem = (item: HTMLElement) => {
991
+ const prevSelected = listContainer.querySelector(
992
+ 'a.is-selected, tr.is-selected',
993
+ )
994
+ if (prevSelected) {
995
+ prevSelected.classList.remove('is-selected')
996
+ }
997
+ item.classList.add('is-selected')
998
+ }
999
+
1000
+ onFind('.LiveBlogGrid .ContentEdit', (form: HTMLElement) => {
1001
+ const isNewPost = form?.classList.contains('is-new')
1002
+ const formId = form?.dataset.objectId
1003
+ gridContent.classList.add('is-visible')
1004
+ const contentPanel = form.querySelector<HTMLElement>(
1005
+ '.ContentEdit-right.is-visible',
1006
+ )
1007
+ if (contentPanel) {
1008
+ contentPanel.classList.remove('is-visible')
1009
+ }
1010
+ if (isNewPost) {
1011
+ selectNavItem(newPostButton)
1012
+ } else {
1013
+ if (!formId) return
1014
+ const post = listContainer.querySelector<HTMLElement>(
1015
+ `tr[data-post-id="${formId}"]`,
1016
+ )
1017
+ if (post) {
1018
+ selectNavItem(post)
1019
+ }
1020
+ }
1021
+ })
1022
+
1023
+ /* Back to list view button in mobile */
1024
+ let listOpen = gridContent.querySelector<HTMLButtonElement>(
1025
+ '.LiveBlogPostList-open',
1026
+ )
1027
+ if (!listOpen) {
1028
+ listOpen = create('button', {
1029
+ className: 'LiveBlogPostList-open',
1030
+ ariaLabel: window.BRIGHTSPOT?.ui.tooltips.listOpen,
1031
+ title: window.BRIGHTSPOT?.ui.tooltips.listOpen,
1032
+ type: 'button',
1033
+ onclick: () => {
1034
+ gridContent.classList.remove('is-visible')
1035
+ listContainer.classList.add('is-visible')
1036
+ },
1037
+ })
1038
+ gridContent.insertBefore(listOpen, gridContent.firstElementChild)
1039
+ }
1040
+
1041
+ listContainer.addEventListener('click', (event) => {
1042
+ if (event.target instanceof HTMLAnchorElement) {
1043
+ const item = event.target
1044
+ if (
1045
+ item.target === 'postedit' &&
1046
+ (item.closest('.LiveBlogPostList-controls') ||
1047
+ item.closest('tr[data-post-id]'))
1048
+ ) {
1049
+ gridContent.classList.add('is-visible')
1050
+ listContainer.classList.remove('is-visible')
1051
+ }
1052
+ }
1053
+ })
1054
+ })
1055
+
1056
+ /**
1057
+ * Resolves a Lucide icon by name and applies the corresponding CSS custom
1058
+ * properties (--compat-icon for the font glyph, --compat-icon-via-mask for
1059
+ * the SVG mask) to the given element.
1060
+ */
1061
+ function applyIconProperties(name: string, el: HTMLElement): void {
1062
+ const materialToLucide = window.BRIGHTSPOT?.icons?.materialToLucide ?? {}
1063
+ const mappedName = materialToLucide[name] ?? name
1064
+ const iconData = getIcon(mappedName, el)
1065
+
1066
+ el.style.setProperty('--compat-icon', `'${iconData?.compat ?? ''}'`)
1067
+
1068
+ if (iconData?.encodedSvg) {
1069
+ el.style.setProperty(
1070
+ '--compat-icon-via-mask',
1071
+ `url("data:image/svg+xml,${iconData.encodedSvg}")`,
1072
+ )
1073
+ } else {
1074
+ el.style.removeProperty('--compat-icon-via-mask')
1075
+ }
1076
+ }
1077
+
1078
+ /*
1079
+ Find all elements with icon data attributes and
1080
+ try to associate and render a corresponding Lucide icon.
1081
+ */
1082
+ onFind(
1083
+ ['[data-icon]', '[data-icon-name]', '[data-icon-button-name]'],
1084
+ (el: HTMLElement) => {
1085
+ const name =
1086
+ el.dataset.icon || el.dataset.iconName || el.dataset.iconButtonName
1087
+
1088
+ if (name) {
1089
+ applyIconProperties(name, el)
1090
+ }
1091
+ },
1092
+ )
1093
+
1094
+ // Listen for 'btu-resolve-icon' events from blacklisted areas (toolbar).
1095
+ document.addEventListener('btu-resolve-icon', ((
1096
+ e: CustomEvent<{ name: string; element: HTMLElement }>,
1097
+ ) => {
1098
+ const { name, element } = e.detail
1099
+ if (!name || !element) return
1100
+
1101
+ applyIconProperties(name, element)
1102
+ }) as EventListener)
1103
+
1104
+ /* Renders a v5 content edit title into the page header.
1105
+ A MutationObserver on `.ContentLabel` watches for label text changes and refreshes the headline.
1106
+ */
1107
+ const renderCETitle = (title: HTMLElement) => {
1108
+ const label = title.querySelector<HTMLElement>('.ContentLabel')
1109
+ if (!label) return
1110
+
1111
+ const editPage = title.closest<HTMLElement>('.ContentEdit')
1112
+ if (!editPage) return
1113
+
1114
+ const header = editPage.querySelector<HTMLElement>('.ContentEdit-top')
1115
+ if (!header) return
1116
+
1117
+ const headlineText = () =>
1118
+ (label.textContent ?? '').replace(/(\r\n|\n|\r)/gm, ' ')
1119
+
1120
+ const render = () => {
1121
+ const contentType =
1122
+ title
1123
+ .querySelector<HTMLElement>('.ContentEdit-typeName')
1124
+ ?.textContent?.trim() ||
1125
+ title.querySelector<HTMLSelectElement>('select')?.selectedOptions[0]
1126
+ ?.text ||
1127
+ ''
1128
+ const status = title.querySelector<HTMLSpanElement>('.Label')
1129
+
1130
+ const newTitle = document.createElement('div')
1131
+ newTitle.classList.add('ContentEdit-title--v5')
1132
+
1133
+ const newContentType = document.createElement('span')
1134
+ newContentType.innerText = contentType
1135
+ newContentType.classList.add('ContentEdit-contentType--v5')
1136
+
1137
+ const title2 = document.createElement('span')
1138
+ title2.innerText = headlineText()
1139
+ title2.classList.add('ContentEdit-contentTitle--v5')
1140
+
1141
+ const newStatus = document.createElement('span')
1142
+ newStatus.classList.add('ContentEdit-status--v5')
1143
+ const legacyStatus = title.querySelector<HTMLSpanElement>('span.Label')
1144
+ newStatus.innerText = legacyStatus?.innerText ?? ''
1145
+ // check if the legacyStatus has a class-specified status (e.g. is-scheduled)
1146
+ if (legacyStatus) {
1147
+ const isStatus = Array.from(legacyStatus.classList).find((cls) =>
1148
+ cls.startsWith('is-'),
1149
+ )
1150
+ if (isStatus) {
1151
+ newStatus.classList.add(isStatus)
1152
+ }
1153
+ }
1154
+
1155
+ const customWorkflowColor = status?.style?.getPropertyValue('background')
1156
+ if (customWorkflowColor) {
1157
+ newStatus.dataset.statusColor = customWorkflowColor
1158
+ newStatus.style.setProperty('--status-color', customWorkflowColor)
1159
+ }
1160
+
1161
+ // Try to grab status name from the revision banner, as it is often more descriptive
1162
+ const statusBanner = editPage.querySelector<HTMLElement>(
1163
+ '.Message.is-revision',
1164
+ )
1165
+ const statusText = statusBanner?.querySelector<HTMLSpanElement>(
1166
+ '.ContentEdit-revisionLabel',
1167
+ )
1168
+ if (statusText) {
1169
+ newStatus.innerText = statusText.innerText
1170
+
1171
+ // background: fallback for when the global status-label onFind handler hasn't yet set --status-color from data-status-color.
1172
+ const bannerColor =
1173
+ statusBanner?.dataset.statusColor ||
1174
+ statusBanner?.style.getPropertyValue('--status-color') ||
1175
+ statusBanner?.style.getPropertyValue('background')
1176
+ if (bannerColor) {
1177
+ newStatus.dataset.statusColor = bannerColor
1178
+ newStatus.style.setProperty('--status-color', bannerColor)
1179
+ }
1180
+ }
1181
+
1182
+ // If status name is empty, default to either "New" or "Published"
1183
+ if (!newStatus.innerText || newStatus.innerText == '') {
1184
+ if (editPage.classList.contains('is-notNew')) {
1185
+ newStatus.innerText = window.BRIGHTSPOT?.ui.workflow.published
1186
+ }
1187
+ if (editPage.classList.contains('is-new')) {
1188
+ newStatus.innerText = window.BRIGHTSPOT?.ui.tooltips.new
1189
+ }
1190
+ }
1191
+
1192
+ newTitle.replaceChildren(title2, newContentType, newStatus)
1193
+
1194
+ const existingTitle = header.querySelector('.ContentEdit-title--v5')
1195
+ if (existingTitle) {
1196
+ header.replaceChild(newTitle, existingTitle)
1197
+ } else {
1198
+ header.prepend(newTitle)
1199
+ }
1200
+ }
1201
+
1202
+ const observer = new MutationObserver(() => {
1203
+ // Refresh the headline in-place if already rendered; otherwise do a full render.
1204
+ const existingContentTitle = header.querySelector<HTMLElement>(
1205
+ '.ContentEdit-contentTitle--v5',
1206
+ )
1207
+ if (existingContentTitle) {
1208
+ existingContentTitle.innerText = headlineText()
1209
+ } else {
1210
+ render()
1211
+ }
1212
+ })
1213
+
1214
+ observer.observe(label, {
1215
+ attributes: true,
1216
+ })
1217
+ onRemove(editPage, () => observer.disconnect())
1218
+
1219
+ render()
1220
+
1221
+ // Bridge.js adds the capitalized `Message` class to `.message.is-revision` asynchronously.
1222
+ // onFind fires once when it appears — this covers the case where Bridge.js runs after render().
1223
+ // If Bridge.js has already run, render() applies the color directly via `.Message.is-revision`.
1224
+ onFind(editPage, '.Message.is-revision', (statusBanner: HTMLElement) => {
1225
+ const bannerColor =
1226
+ statusBanner.dataset.statusColor ||
1227
+ statusBanner.style.getPropertyValue('--status-color') ||
1228
+ statusBanner.style.getPropertyValue('background')
1229
+ if (!bannerColor) return
1230
+
1231
+ const badge = header.querySelector<HTMLElement>('.ContentEdit-status--v5')
1232
+ if (!badge) return
1233
+
1234
+ const revisionLabel = statusBanner.querySelector<HTMLElement>(
1235
+ '.ContentEdit-revisionLabel',
1236
+ )
1237
+ if (revisionLabel) badge.innerText = revisionLabel.innerText
1238
+
1239
+ badge.dataset.statusColor = bannerColor
1240
+ badge.style.setProperty('--status-color', bannerColor)
1241
+ })
1242
+ }
1243
+
1244
+ if (document.readyState === 'loading') {
1245
+ window.addEventListener('DOMContentLoaded', () => {
1246
+ const title = document.querySelector<HTMLElement>('.ContentEdit-title')
1247
+ if (title) renderCETitle(title)
1248
+ })
1249
+ } else {
1250
+ const title = document.querySelector<HTMLElement>('.ContentEdit-title')
1251
+ if (title) renderCETitle(title)
1252
+ }
1253
+
1254
+ onFind(
1255
+ ['.Popup.loaded .ContentEdit-title', '.LiveBlogGrid .ContentEdit-title'],
1256
+ (title: HTMLElement) => {
1257
+ renderCETitle(title)
1258
+ },
1259
+ )
1260
+
1261
+ /* Elements which trigger a dropdown need to be popovers so they render in a top-layer.
1262
+ This is to prevent stacking/position issues rendering them inside a modal dialog (like embedded CE). */
1263
+ onFind(
1264
+ '[aria-haspopup][aria-controls]:not([aria-controls=""])',
1265
+ (trigger: HTMLElement) => {
1266
+ const popup = document.getElementById(
1267
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1268
+ trigger.getAttribute('aria-controls')!,
1269
+ )
1270
+ if (popup === null) return
1271
+ popup.setAttribute('popover', '')
1272
+ trigger.addEventListener('click', () => {
1273
+ popup.showPopover()
1274
+ })
1275
+ },
1276
+ )
1277
+
1278
+ /*
1279
+ `is-local` popups which we don't want treated as popovers
1280
+ and instead should be opened as modals
1281
+ */
1282
+ function isPopoverBlacklisted(popup: HTMLElement) {
1283
+ if (!popup.dataset?.popupSourceClass) return false
1284
+
1285
+ const blacklist = ['rte2-toolbar-add-table']
1286
+ return blacklist.includes(popup.dataset.popupSourceClass)
1287
+ }
1288
+
1289
+ const renderDialogHeading = (
1290
+ root: HTMLElement | null,
1291
+ selectors: string,
1292
+ hideOriginal = true,
1293
+ ) => {
1294
+ if (root === null) {
1295
+ console.warn(
1296
+ 'root element provided to `Dialog._renderTitle` is null. Skipping render.',
1297
+ )
1298
+ return
1299
+ }
1300
+
1301
+ const cloned = root.querySelector(`:scope > :where(${selectors})`)
1302
+ const source = root.querySelector(`:scope > * :where(${selectors})`)
1303
+
1304
+ if (source) {
1305
+ if (!cloned) {
1306
+ const clone = source.cloneNode(true) as HTMLElement
1307
+ // If the markup isn't fresh from the server, remove the hidden class.
1308
+ clone.classList.remove('!hidden')
1309
+ root.closest('.Popup-content')?.prepend(clone)
1310
+ if (hideOriginal) source.classList.add('!hidden')
1311
+ }
1312
+ }
1313
+ }
1314
+
1315
+ const copyDialogAriaLabel = (content: HTMLElement | null) => {
1316
+ if (!content) return
1317
+
1318
+ const legacyPopup = content.closest('.Popup')
1319
+ const dialog = content.closest('dialog[open]')
1320
+ if (!legacyPopup || !dialog) return
1321
+
1322
+ const legacyAriaLabel = legacyPopup.ariaLabel
1323
+ let ariaLabel = dialog.ariaLabel
1324
+ if (ariaLabel && (!legacyAriaLabel || ariaLabel === legacyAriaLabel)) {
1325
+ legacyPopup.removeAttribute('aria-label')
1326
+ return
1327
+ }
1328
+
1329
+ if (legacyAriaLabel) {
1330
+ ariaLabel = legacyAriaLabel
1331
+ legacyPopup.removeAttribute('aria-label')
1332
+ }
1333
+
1334
+ // If not already present in v4, aria-label should be set to the heading copied by #renderDialogHeading
1335
+ if (!ariaLabel) {
1336
+ const widgetTitle = legacyPopup.querySelector(
1337
+ ':scope > .Popup-content > .Widget-title',
1338
+ )
1339
+ const widgetText =
1340
+ widgetTitle?.querySelector(':scope > span')?.textContent ||
1341
+ widgetTitle?.textContent ||
1342
+ null
1343
+ ariaLabel = widgetText
1344
+ }
1345
+
1346
+ if (!ariaLabel) return
1347
+ dialog.ariaLabel = ariaLabel
1348
+ }
1349
+
1350
+ /* Legacy popups which do NOT load their contents in a frame. eg: ContentSummary */
1351
+ onFind(
1352
+ 'btu-dialog dialog[open] .Popup-content:not(:has(> .frame))',
1353
+ (content: HTMLElement) => {
1354
+ renderDialogHeading(content, 'h1,h2,h3,.Widget-title')
1355
+ copyDialogAriaLabel(content)
1356
+ },
1357
+ )
1358
+
1359
+ /* Legacy popups which load their contents in a frame */
1360
+ onFind(
1361
+ 'btu-dialog dialog[open] .Popup.loaded .frame.plugin-popup.loaded',
1362
+ async (frame: HTMLElement) => {
1363
+ /* Disabling eslint because the preceeding QS establishes that this is a descendant of a popup. */
1364
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1365
+ await onTransitionsEnded(frame.closest('dialog[open]')!)
1366
+
1367
+ // after transitions are complete, show the content.
1368
+ frame.classList.add('is-transitioned')
1369
+
1370
+ // autofocus a search input if it exists (these don't reliably have an "autofocus" attribute present).
1371
+ frame
1372
+ .querySelector<HTMLInputElement>(
1373
+ '.SearchInput input:not([hidden]), [class^=Search] > btu-input input:not([hidden])',
1374
+ )
1375
+ ?.focus()
1376
+
1377
+ // Clone the title from the frame to the popup.
1378
+ const blacklistedFromCloning = [
1379
+ '.SearchWidget.is-global',
1380
+ '.Widget[name="diff"]',
1381
+ '.Widget[name^="create-new-" i]',
1382
+ '.Widget[name^="objectId-" i]:not(:has(.ContentReporting-status))',
1383
+ '.Widget[name^="_copy" i]',
1384
+ '.Widget[name^="DateStringCalendarFrame" i]',
1385
+ ]
1386
+ if (!blacklistedFromCloning.some((t) => frame.matches(t))) {
1387
+ renderDialogHeading(
1388
+ frame.closest('.Popup-content'),
1389
+ 'h1,h2,h3,.Guide-title,.Widget-title',
1390
+ )
1391
+ copyDialogAriaLabel(frame.closest('.Popup-content'))
1392
+ }
1393
+ },
1394
+ )
1395
+
1396
+ /* Admin edit modals */
1397
+ onFind(
1398
+ 'btu-dialog dialog[open] .Popup.loaded .plugin-popup.loaded.Widget[name^="objectId-" i]:has(.Admin > :where(.Admin-nav, .Admin-main))',
1399
+ (frame: HTMLElement) => {
1400
+ renderDialogHeading(
1401
+ frame.closest('.Popup-content'),
1402
+ '.Admin .Widget-title',
1403
+ false,
1404
+ )
1405
+ copyDialogAriaLabel(frame.closest('.Popup-content'))
1406
+ },
1407
+ )
1408
+
1409
+ /*
1410
+ Popovers and Modal Dialogs:
1411
+ Avoiding the "PrePublish" & "QueryField-frame" popup as they are special cases.
1412
+ The "PrePublishPopup" class is added after page load to trigger the auto-open behavior.
1413
+ */
1414
+ onFind(
1415
+ '.Popup:not(.PrePublish, [name="queryField"]), .PrePublishPopup',
1416
+ (legacyPopup: HTMLElement) => {
1417
+ // Exit early for those which are non-modal popovers.
1418
+ if (
1419
+ legacyPopup.classList.contains('is-local') &&
1420
+ !isPopoverBlacklisted(legacyPopup)
1421
+ ) {
1422
+ legacyPopup.setAttribute('popover', '')
1423
+ legacyPopup.addEventListener('toggle', (event: any) => {
1424
+ if (event.newState === 'closed') {
1425
+ legacyPopup.dispatchEvent(
1426
+ new CustomEvent('legacy-popup-closed', {
1427
+ bubbles: true,
1428
+ composed: true,
1429
+ }),
1430
+ )
1431
+ legacyPopup.remove()
1432
+ }
1433
+ })
1434
+ legacyPopup.showPopover()
1435
+ return
1436
+ }
1437
+
1438
+ // Upgrade jQuery Popups to WebComponent.
1439
+ const dialog = new Dialog()
1440
+ dialog.addEventListener('btu-dialog-ready', () => {
1441
+ onRemove.resume()
1442
+ })
1443
+
1444
+ /* This is to pause any onRemove callbacks specific to popup element while it's being moved */
1445
+ onRemove.pause()
1446
+
1447
+ legacyPopup.parentNode?.insertBefore(dialog, legacyPopup)
1448
+ legacyPopup.removeAttribute('role') // Native dialog element has implicit role="dialog"
1449
+ dialog.append(legacyPopup)
1450
+ },
1451
+ )
1452
+
1453
+ /* -------------- [begin] One-off modals --------------------------- */
1454
+
1455
+ /* Special snowflake popup which is part of the page load and needs to immediately open as a modal. */
1456
+ onFind('.PrePublish:has(.Popup-content)', async (popup: HTMLElement) => {
1457
+ setTimeout(() => popup.classList.add('PrePublishPopup'), 0)
1458
+ })
1459
+
1460
+ /* Content edit workflow popover now opens up as a dialog modal in v5 */
1461
+ onFind('.ContentEdit-workflowToggle', (toggle: HTMLElement) => {
1462
+ const workflowPopup = toggle.parentElement?.querySelector<HTMLElement>(
1463
+ '.ContentEdit-workflow',
1464
+ )
1465
+ if (!workflowPopup) return
1466
+ const dialog = new Dialog()
1467
+ dialog.removeOnClose = false
1468
+ dialog.autoOpen = false
1469
+
1470
+ dialog.addEventListener('btu-dialog-ready', () => {
1471
+ onRemove.resume()
1472
+ })
1473
+
1474
+ /* This is to pause any onRemove callbacks specific to popup element while it's being moved */
1475
+ onRemove.pause()
1476
+
1477
+ workflowPopup.parentNode?.insertBefore(dialog, workflowPopup)
1478
+ dialog.append(workflowPopup)
1479
+
1480
+ toggle.addEventListener('click', (event) => {
1481
+ if (toggle.classList.contains('is-open')) {
1482
+ dialog.show()
1483
+ } else {
1484
+ dialog.close(event)
1485
+ }
1486
+ })
1487
+ })
1488
+
1489
+ /* Query field modal */
1490
+ interface QueryInputHTMLElement extends HTMLElement {
1491
+ frame?: QueryFieldFrameElement
1492
+ }
1493
+
1494
+ interface QueryFieldFrameElement extends HTMLElement {
1495
+ _bspDialogSetup?: boolean
1496
+ }
1497
+
1498
+ onFind('.QueryField-toggle', (toggle: HTMLElement) => {
1499
+ const queryField =
1500
+ toggle.parentElement?.querySelector<QueryInputHTMLElement>('.QueryField')
1501
+ if (!queryField) return
1502
+ const queryFieldFrame = queryField.frame
1503
+ if (!queryFieldFrame) return
1504
+
1505
+ // Multiple onFind registrations (v4/QueryField.js + v5.ts + others) each
1506
+ // have their own data-ofc key, so the same toggle can be processed N times.
1507
+ // Guard on the frame element so only the first registration sets up the Dialog.
1508
+ if (queryFieldFrame._bspDialogSetup) return
1509
+ queryFieldFrame._bspDialogSetup = true
1510
+
1511
+ // Upgrade jQuery Popups to WebComponent.
1512
+ const dialog = new Dialog()
1513
+ dialog.removeOnClose = false
1514
+ dialog.autoOpen = false
1515
+
1516
+ // Defer moving the frame and wiring handlers until the Popup is present.
1517
+ // Uses a brightspot-frame-load listener scoped to this specific frame rather
1518
+ // than a global onFind('.QueryField-frame > .Popup') registration, which
1519
+ // would fire for every QueryField-frame Popup on the page and cause
1520
+ // "insertBefore: new child contains the parent" errors when multiple query
1521
+ // fields exist simultaneously.
1522
+ const onFrameLoaded = () => {
1523
+ const popup = queryFieldFrame.querySelector<HTMLElement>('.Popup')
1524
+ if (!popup) return // early dispatch fired before form response; keep listening
1525
+ queryFieldFrame.removeEventListener('brightspot-frame-load', onFrameLoaded)
1526
+
1527
+ dialog.addEventListener('btu-dialog-ready', () => {
1528
+ onRemove.resume()
1529
+ })
1530
+
1531
+ /* This is to pause any onRemove callbacks specific to popup element while it's being moved */
1532
+ onRemove.pause()
1533
+
1534
+ queryFieldFrame.parentNode?.insertBefore(dialog, queryFieldFrame)
1535
+ dialog.append(queryFieldFrame)
1536
+
1537
+ toggle.addEventListener('click', (event) => {
1538
+ dialog.show()
1539
+ })
1540
+
1541
+ dialog.addEventListener('btu-dialog-closed', (event) => {
1542
+ popup.classList.remove('is-visible')
1543
+ })
1544
+
1545
+ const saveButton =
1546
+ queryFieldFrame.querySelector<HTMLElement>('.QueryField-button')
1547
+ if (!saveButton) return
1548
+ saveButton.addEventListener('click', (event) => {
1549
+ dialog.close(event)
1550
+ })
1551
+ }
1552
+
1553
+ queryFieldFrame.addEventListener('brightspot-frame-load', onFrameLoaded)
1554
+ })
1555
+
1556
+ /* Color picker modals */
1557
+ onFind('dialog btu-input > .sp-replacer', (trigger: HTMLElement) => {
1558
+ const popoverId = trigger.getAttribute('aria-controls')
1559
+ if (!popoverId) return
1560
+ const colorPickerPopup = document.querySelector<HTMLElement>(`#${popoverId}`)
1561
+ if (!colorPickerPopup) return
1562
+ colorPickerPopup.setAttribute('popover', '')
1563
+ trigger.addEventListener('click', () => {
1564
+ colorPickerPopup.showPopover()
1565
+ })
1566
+ })
1567
+
1568
+ /*
1569
+ ProseMirror spellcheck suggestions opens as a popover so that it can
1570
+ appear above fullscreen RTE.
1571
+ */
1572
+ onFind(
1573
+ '.ProseMirror-spellcheck-suggestions.is-opening',
1574
+ (spellcheck: HTMLElement) => {
1575
+ spellcheck.setAttribute('popover', '')
1576
+ spellcheck.showPopover()
1577
+ },
1578
+ )
1579
+
1580
+ /* -------------- [end] One-off modals --------------------------- */
1581
+
1582
+ /*
1583
+ Adds light-dismiss functionality to dialog elements.
1584
+ At the time of this implementation <dialog>'s `closedBy` attribute is experimental.
1585
+ TODO: in the future this could be simplified to use the native `closedby="any"` attribute.
1586
+ See: https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/closedBy
1587
+ */
1588
+ document.addEventListener('click', (e) => {
1589
+ const dialog = e.target
1590
+ if (dialog instanceof HTMLDialogElement && dialog.nodeName === 'DIALOG') {
1591
+ const wrapper = dialog.parentElement
1592
+ if (wrapper && wrapper.tagName === Dialog.tagName) {
1593
+ // We don't want the following dialog classes/variants to close on light-dismiss.
1594
+ const preventLightDismissal = wrapper.querySelector(
1595
+ '.Popup.is-fromTop, .Popup.is-noGap',
1596
+ )
1597
+ if (preventLightDismissal) return
1598
+ const d = wrapper as Dialog
1599
+ d.close(e)
1600
+ }
1601
+ }
1602
+ })
1603
+
1604
+ // Add support for legacy jQuery popup close events. (this can be removed when jQuery is removed).
1605
+ document.addEventListener('legacy.popup.close', (e: Event) => {
1606
+ const popup = e.target
1607
+ if (popup instanceof HTMLElement) {
1608
+ const d = popup.closest('btu-dialog') as Dialog
1609
+ d?.close(e)
1610
+ }
1611
+ })
1612
+
1613
+ document.addEventListener('page-nav-rail-toggled', (e: Event) => {
1614
+ // Close search dialog if it's open.
1615
+ const btuDialog = document.querySelector<Dialog>(
1616
+ 'btu-dialog:has(dialog[open] .Popup[name="miscSearch"])',
1617
+ )
1618
+ btuDialog?.closest('.Page')?.classList.remove('is-searchOpening')
1619
+ btuDialog?.close(e)
1620
+ })
1621
+
1622
+ // In month view on a calendar widget,
1623
+ // clicking anywhere on a day with an event should click the event
1624
+ onFind('.Month-day:has(a.calendarEvents)', (day) => {
1625
+ day.addEventListener('click', () => {
1626
+ day.querySelector<HTMLAnchorElement>('a.calendarEvents')?.click()
1627
+ })
1628
+ })
1629
+
1630
+ // Set aria-expanded="true" only when the Ask AI panel is loaded and visible
1631
+ function setAskAiButtonExpanded(expanded: boolean) {
1632
+ const btn = document.querySelector('.Page-askAiButton')
1633
+ if (btn) btn.setAttribute('aria-expanded', expanded ? 'true' : 'false')
1634
+ }
1635
+
1636
+ // Shared click handler for .AskAi-close
1637
+ function handleAskAiCloseClick(evt: Event) {
1638
+ const target = evt.target
1639
+ if (
1640
+ target instanceof HTMLElement &&
1641
+ target.classList.contains('AskAi-close')
1642
+ ) {
1643
+ // Update the container state to not loaded when it's closed.
1644
+ // This is helpful for other components to know that Ask AI panel is not active.
1645
+ const askAiContainer = document.getElementById('askAi-container')
1646
+ askAiContainer?.classList.remove('is-loaded')
1647
+
1648
+ setAskAiButtonExpanded(false)
1649
+ }
1650
+ }
1651
+
1652
+ // When Ask AI container is loaded
1653
+ onFind('#askAi-container .frame.loaded', (container) => {
1654
+ container.parentElement?.classList.add('is-loaded')
1655
+ setAskAiButtonExpanded(true)
1656
+
1657
+ const askAiPanel = container.closest('.AskAi')
1658
+ const targetElement = askAiPanel || container
1659
+ targetElement.addEventListener('click', handleAskAiCloseClick)
1660
+ })
1661
+
1662
+ // Fallback: If AskAi panel appears separately
1663
+ onFind('.AskAi', (container) => {
1664
+ container.addEventListener('click', handleAskAiCloseClick)
1665
+ })
1666
+
1667
+ onFind('.Page-header .Page-askAiLink', (magicButton) => {
1668
+ /* V4 style collision without removing this class...*/
1669
+ magicButton.classList.remove('IconButton')
1670
+ onFind('.Page-header + btu-nav-rail', (navRail: HTMLElement) => {
1671
+ const magicItem = document.createElement('li')
1672
+ magicItem.appendChild(magicButton)
1673
+ const guideItem = navRail.querySelector('li:has(> .Page-guideLink)')
1674
+ /* If the guide item exists, insert the magic item before it.
1675
+ Otherwise, insert it after the first item in the nav rail. */
1676
+ if (guideItem) {
1677
+ navRail.querySelector('ul')?.insertBefore(magicItem, guideItem)
1678
+ } else {
1679
+ navRail
1680
+ .querySelector('ul > li')
1681
+ ?.insertAdjacentElement('afterend', magicItem)
1682
+ }
1683
+ })
1684
+ })
1685
+
1686
+ // Add scroll listener for content edit mobile to allow header to collapse
1687
+ onFind(
1688
+ '.Page-header.mobile ~ .Page-container:not(:has(.ContentEdit-right.is-visible)) .ContentEdit-left',
1689
+ (contentEdit) => {
1690
+ const mobileHeader = document.querySelector<HTMLElement>(
1691
+ '.Page-header.mobile',
1692
+ )
1693
+ if (!mobileHeader) return
1694
+
1695
+ contentEdit.addEventListener(
1696
+ 'scroll',
1697
+ throttle(100, () => {
1698
+ const scrollTop = contentEdit.scrollTop
1699
+ // Only collapse the header if the total scroll capacity exceeds the header height (plus the remove threshold).
1700
+ const maxScroll = contentEdit.scrollHeight - contentEdit.clientHeight
1701
+ if (
1702
+ scrollTop > 10 &&
1703
+ maxScroll > mobileHeader.offsetHeight + 10 &&
1704
+ !mobileHeader.classList.contains('is-scrolled')
1705
+ ) {
1706
+ mobileHeader.classList.add('is-scrolled')
1707
+ } else if (
1708
+ scrollTop <= 10 &&
1709
+ mobileHeader.classList.contains('is-scrolled')
1710
+ ) {
1711
+ mobileHeader.classList.remove('is-scrolled')
1712
+ }
1713
+ }),
1714
+ )
1715
+ },
1716
+ )
1717
+
1718
+ // On the Reports Page, click on a report and go on the recent reports tab.
1719
+ // The view icon in the content selectors should not open the status popup in fullscreen in this case.
1720
+ // This removes the data for popup mode, where ContentSelector-edit usually has noGap set.
1721
+ onFind(
1722
+ [
1723
+ '.ContentReportingWidget .CIG-row[data-field="getPastReportResults"] .ContentSelector-edit',
1724
+ '.CollectionList-item-edit.ContentSelector-edit',
1725
+ ],
1726
+ (viewButton) => {
1727
+ viewButton.removeAttribute('data-popup-mode')
1728
+ },
1729
+ )
1730
+
1731
+ onFind('.Popup[name="siteSwitch"]', (el) => {
1732
+ document.getElementById('SiteSwitch-link')?.classList.add('is-active')
1733
+ })
1734
+
1735
+ onFind('.LiveBlogPostList', (el) => {
1736
+ document.body.classList.add('has-liveBlog-postList')
1737
+ })
1738
+
1739
+ onFind('.ToolUserAvatar', (avatar) => {
1740
+ avatar.parentElement?.classList.add('ToolUserAvatar-container')
1741
+ })
1742
+
1743
+ document.addEventListener('legacy-popup-closed', (event) => {
1744
+ if (event.target instanceof HTMLElement) {
1745
+ if (event.target.getAttribute('name') === 'siteSwitch') {
1746
+ document.getElementById('SiteSwitch-link')?.classList.remove('is-active')
1747
+ }
1748
+ }
1749
+ })
1750
+
1751
+ /**
1752
+ * Prevents accidental button clicks when workflow is in a dragging state.
1753
+ * Ex. if you start dragging on the "Add transition" button, it shouldn't
1754
+ * also trigger a click.
1755
+ */
1756
+ onFind('.Workflow-state', (state) => {
1757
+ state.addEventListener(
1758
+ 'click',
1759
+ (e) => {
1760
+ if (state.classList.contains('is-dragging')) {
1761
+ e.stopPropagation()
1762
+ }
1763
+ },
1764
+ true,
1765
+ )
1766
+ })
1767
+
1768
+ /**
1769
+ * Auto-discover focus regions from backend-rendered [data-focus-region] elements.
1770
+ *
1771
+ * This allows Java developers to register custom focus regions purely through
1772
+ * HTML attributes, without writing any TypeScript. The element must also have
1773
+ * proper a11y attributes (role="region", aria-label) for skip-link support.
1774
+ *
1775
+ * Intentionally placed before imperative registrations — onFind callbacks fire
1776
+ * in registration order, so auto-discovery runs first and imperative register()
1777
+ * calls below will overwrite it, giving imperative registrations precedence.
1778
+ */
1779
+ onFind(FOCUS_REGION_SELECTOR, (element: HTMLElement) => {
1780
+ focusRegionManager.registerFromAttribute(element)
1781
+ })
1782
+
1783
+ /**
1784
+ * Make various a11y-related adjustments to the Page Guide region.
1785
+ */
1786
+ onFind('.Page-guide', (guide: HTMLElement) => {
1787
+ const page = guide.closest<HTMLElement>('.Page')
1788
+ if (!page) return
1789
+
1790
+ // Move the guide before the Page-container for better reading order
1791
+ const pageContainer = page?.querySelector('.Page-container')
1792
+ if (pageContainer) {
1793
+ pageContainer.parentNode?.insertBefore(guide, pageContainer)
1794
+ }
1795
+
1796
+ focusRegionManager.register({
1797
+ id: RegionId.PageGuide,
1798
+ element: guide,
1799
+ isHidden: () => !page.classList.contains('is-guideOpen'),
1800
+ })
1801
+
1802
+ function assignRegionLabel() {
1803
+ const title = guide.querySelector<HTMLElement>('.Guide-title-text')
1804
+ if (title) {
1805
+ title.id = 'Guide-title'
1806
+ guide.setAttribute('aria-labelledby', 'Guide-title')
1807
+ } else {
1808
+ guide.removeAttribute('aria-labelledby')
1809
+ }
1810
+ }
1811
+
1812
+ const guideVisibilityObserver = new MutationObserver((mutations) => {
1813
+ mutations.forEach((mutation) => {
1814
+ if (mutation.type === 'childList') {
1815
+ assignRegionLabel()
1816
+ }
1817
+ })
1818
+ })
1819
+
1820
+ assignRegionLabel()
1821
+ guideVisibilityObserver.observe(guide, {
1822
+ childList: true,
1823
+ subtree: true,
1824
+ })
1825
+ })
1826
+
1827
+ /**
1828
+ * Register the Page Header region.
1829
+ */
1830
+ onFind('.Page-header', (header: HTMLElement) => {
1831
+ // Note that <header> implicitly has role="banner"
1832
+ focusRegionManager.register({
1833
+ id: RegionId.PageHeader,
1834
+ element: header,
1835
+ label: window.BRIGHTSPOT?.ui.tooltips.skipLinks.pageHeader,
1836
+ })
1837
+ })
1838
+
1839
+ /**
1840
+ * Register the Nav Rail region.
1841
+ */
1842
+ onFind('btu-nav-rail[role="navigation"]', (navRail: HTMLElement) => {
1843
+ focusRegionManager.register({
1844
+ id: RegionId.PageNav,
1845
+ element: navRail,
1846
+ label: window.BRIGHTSPOT?.ui.tooltips.skipLinks.pageNavigation,
1847
+ })
1848
+ })
1849
+
1850
+ /**
1851
+ * Register the main region for non-admin & non-content edit pages.
1852
+ */
1853
+ onFind(
1854
+ '.Page-content:not(:has(> .content-edit), :has(> .Admin))',
1855
+ (content: HTMLElement) => {
1856
+ focusRegionManager.register({
1857
+ id: RegionId.PageMain,
1858
+ element: content,
1859
+ label: window.BRIGHTSPOT?.ui.tooltips.skipLinks.mainContent,
1860
+ })
1861
+ },
1862
+ )
1863
+
1864
+ /**
1865
+ * Register popup/modal header region so that focus can be directed to the close button.
1866
+ */
1867
+ onFind(
1868
+ 'dialog[open] .Popup-content > .Popup-close',
1869
+ (popupContent: HTMLElement) => {
1870
+ focusRegionManager.register({
1871
+ id: RegionId.ModalClose,
1872
+ element: popupContent,
1873
+ })
1874
+ },
1875
+ )
1876
+
1877
+ /**
1878
+ * Register content edit widget regions (ContentEdit-right).
1879
+ * Also set preferred focus to the text input when Conversations widget is active.
1880
+ * Uses MutationObserver to re-register when visibility changes.
1881
+ */
1882
+ onFind('.ContentEdit-right[role="region"]', (rightPanel: HTMLElement) => {
1883
+ const registerPanel = () => {
1884
+ const conversationPreferredFocus =
1885
+ rightPanel.dataset.rightTab === 'chat_bubble'
1886
+
1887
+ focusRegionManager.register({
1888
+ id: RegionId.ContentEditRight,
1889
+ element: rightPanel,
1890
+ preferredFocus: () => {
1891
+ if (!conversationPreferredFocus) return null
1892
+ return rightPanel.querySelector<HTMLElement>(
1893
+ '.Conversation-newPost .ProseMirror',
1894
+ )
1895
+ },
1896
+ isHidden: () => !rightPanel.classList.contains('is-visible'),
1897
+ })
1898
+ }
1899
+
1900
+ // Initial registration
1901
+ if (rightPanel.classList.contains('is-visible')) registerPanel()
1902
+
1903
+ // Watch for visibility changes and re-register when .is-visible changes
1904
+ const observer = new MutationObserver((mutations) => {
1905
+ for (const mutation of mutations) {
1906
+ if (rightPanel.classList.contains('is-visible')) {
1907
+ registerPanel()
1908
+ }
1909
+ }
1910
+ })
1911
+
1912
+ observer.observe(rightPanel, {
1913
+ attributeFilter: ['class'],
1914
+ })
1915
+ })
1916
+
1917
+ /**
1918
+ * Register the Content Edit toolbar region.
1919
+ */
1920
+ onFind('section > .ContentEdit-toolbar', (toolbar: HTMLElement) => {
1921
+ focusRegionManager.register({
1922
+ id: RegionId.ContentEditToolbar,
1923
+ element: toolbar,
1924
+ })
1925
+ })
1926
+
1927
+ /**
1928
+ * Register the Content Edit main (left) region.
1929
+ */
1930
+ onFind('.ContentEdit-left[role="region"]', (mainPanel: HTMLElement) => {
1931
+ focusRegionManager.register({
1932
+ id: RegionId.PageMain,
1933
+ element: mainPanel,
1934
+ })
1935
+ })
1936
+
1937
+ /**
1938
+ * Register the Content Edit title (top) region.
1939
+ */
1940
+ onFind('.ContentEdit-top[role="region"]', (titlePanel: HTMLElement) => {
1941
+ focusRegionManager.register({
1942
+ id: RegionId.ContentEditTop,
1943
+ element: titlePanel,
1944
+ })
1945
+ })
1946
+
1947
+ /**
1948
+ * Register the Content Edit preview region.
1949
+ */
1950
+ onFind(
1951
+ '.ContentEdit-preview[role="region"]:not([inert])',
1952
+ (previewPanel: HTMLElement) => {
1953
+ focusRegionManager.register({
1954
+ id: RegionId.ContentEditPreview,
1955
+ element: previewPanel,
1956
+ isHidden: () => previewPanel.hasAttribute('inert'),
1957
+ })
1958
+ },
1959
+ )
1960
+
1961
+ /**
1962
+ * Register the Content Edit drawer region.
1963
+ */
1964
+ onFind('.ContentEditDrawer[role="region"].is-open', (drawer: HTMLElement) => {
1965
+ focusRegionManager.register({
1966
+ id: RegionId.ContentEditDrawer,
1967
+ element: drawer,
1968
+ isHidden: () => !drawer.classList.contains('is-open'),
1969
+ })
1970
+ })
1971
+
1972
+ /**
1973
+ * Duplicate id management for situations when multiple content edit forms are open at once.
1974
+ */
1975
+ /* Generate unique IDs for ContentEditDrawer titles */
1976
+ onFind('#ContentEditDrawer-title', (title: HTMLElement) => {
1977
+ const drawer = title.closest<HTMLElement>('.ContentEditDrawer[role="region"]')
1978
+ if (!drawer) return
1979
+
1980
+ const uniqueId = `ContentEditDrawer-title-${uuid().replace(/-/g, '')}`
1981
+ title.id = uniqueId
1982
+ drawer.setAttribute('aria-labelledby', uniqueId)
1983
+ })
1984
+
1985
+ /* Generate unique IDs for ContentEdit widget titles */
1986
+ onFind('.ContentEdit-right-title', (title: HTMLElement) => {
1987
+ const panel = title.closest<HTMLElement>('[role="region"][data-right-tab]')
1988
+ if (!panel) return
1989
+
1990
+ const baseId = title.id
1991
+ const uniqueId = `${baseId}-${uuid().replace(/-/g, '')}`
1992
+ title.id = uniqueId
1993
+ panel.setAttribute('aria-labelledby', uniqueId)
1994
+ })
1995
+
1996
+ /**
1997
+ * Register the Admin left & main regions. Also ensures that the main region
1998
+ * has an identifiable label.
1999
+ */
2000
+ onFind('.Admin-nav[role="navigation"]', (adminNav: HTMLElement) => {
2001
+ // Resolve the admin page name from the aria-labelledby heading (e.g., "Plugins")
2002
+ // and construct a label like "Plugins Navigation"
2003
+ const labelledById = adminNav.getAttribute('aria-labelledby')
2004
+ const pageName = labelledById
2005
+ ? document.getElementById(labelledById)?.textContent?.trim()
2006
+ : null
2007
+ const navLabel = pageName
2008
+ ? `${pageName} ${window.BRIGHTSPOT?.ui.tooltips.skipLinks.navigation}`
2009
+ : window.BRIGHTSPOT?.ui.tooltips.skipLinks.navigation
2010
+
2011
+ focusRegionManager.register({
2012
+ id: RegionId.LeftNav,
2013
+ element: adminNav,
2014
+ label: navLabel,
2015
+ })
2016
+
2017
+ // Register the Admin main region, if it exists.
2018
+ const adminMain = adminNav.nextElementSibling as HTMLElement
2019
+ if (!adminMain || !adminMain.classList.contains('Admin-main')) return
2020
+
2021
+ focusRegionManager.register({
2022
+ id: RegionId.PageMain,
2023
+ element: adminMain,
2024
+ label: window.BRIGHTSPOT?.ui.tooltips.skipLinks.mainContent,
2025
+ })
2026
+
2027
+ // Assign an id to the main region label if one is not already present.
2028
+ const labelId = adminMain.getAttribute('aria-labelledby')
2029
+ if (!labelId) return
2030
+ if (document.getElementById(labelId)) return
2031
+ adminMain
2032
+ .querySelector<HTMLElement>('.Widget-title')
2033
+ ?.setAttribute('id', labelId)
2034
+ })
2035
+
2036
+ /**
2037
+ * Ensures focused elements are scrolled into view with proper offset
2038
+ * to account for sticky ActionBar at the bottom of scrollable containers.
2039
+ * Only scrolls when focus arrives via keyboard navigation, not mouse/touch clicks,
2040
+ * to avoid interfering with dropdown positioning and click actions.
2041
+ */
2042
+ onFind('.ActionBar', (actionBar) => {
2043
+ const widget = actionBar.closest('.Widget')
2044
+ if (!widget) return
2045
+ const widgetTitle = widget.querySelector<HTMLElement>('.Widget-title')
2046
+
2047
+ let lastPointerType = ''
2048
+ const handlePointerdown = (event: Event) => {
2049
+ lastPointerType = (event as PointerEvent).pointerType
2050
+ // Reset after current task so only the immediately following focusin is affected.
2051
+ setTimeout(() => {
2052
+ lastPointerType = ''
2053
+ }, 0)
2054
+ }
2055
+
2056
+ const handleFocusin = (event: Event) => {
2057
+ // Skip scroll-into-view when focus is triggered by mouse or touch click —
2058
+ // smooth scrolling mid-click disrupts dropdown positioning and button actions.
2059
+ if (lastPointerType) return
2060
+
2061
+ const target = event.target
2062
+ if (!(target instanceof HTMLElement)) return
2063
+
2064
+ requestAnimationFrame(() => {
2065
+ const targetRect = target.getBoundingClientRect()
2066
+ if (
2067
+ targetRect.bottom > actionBar.getBoundingClientRect().top ||
2068
+ targetRect.top < (widgetTitle?.getBoundingClientRect().bottom || 0)
2069
+ ) {
2070
+ target.scrollIntoView({ block: 'nearest', behavior: 'smooth' })
2071
+ }
2072
+ })
2073
+ }
2074
+
2075
+ widget.addEventListener('pointerdown', handlePointerdown, { passive: true })
2076
+ widget.addEventListener('focusin', handleFocusin, { passive: true })
2077
+ onRemove(widget, () => {
2078
+ widget.removeEventListener('pointerdown', handlePointerdown)
2079
+ widget.removeEventListener('focusin', handleFocusin)
2080
+ })
2081
+ })