@conduction/nextcloud-vue 2.58.0 → 2.60.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (507) hide show
  1. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +12 -2
  2. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
  3. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +1 -2
  4. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
  5. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js +14 -1
  6. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js.map +1 -1
  7. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js +0 -3
  8. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
  9. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
  10. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +1 -2
  11. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
  12. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +39 -0
  13. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  14. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +46 -4
  15. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  16. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +224 -9
  17. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
  18. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +79 -25
  19. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
  20. package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue2.js +42 -3
  21. package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue2.js.map +1 -1
  22. package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue3.js +39 -6
  23. package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue3.js.map +1 -1
  24. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +58 -5
  25. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
  26. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +13 -5
  27. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
  28. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue.js +10 -0
  29. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue.js.map +1 -0
  30. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js +39 -0
  31. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js.map +1 -0
  32. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js +16 -0
  33. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js.map +1 -0
  34. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +9 -1
  35. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
  36. package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
  37. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +117 -1
  38. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  39. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +147 -89
  40. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  41. package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js +6 -1
  42. package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js.map +1 -1
  43. package/dist/esm/components/CnContextMenu/CnContextMenu.vue3.js.map +1 -1
  44. package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +10 -0
  45. package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js.map +1 -0
  46. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js +259 -0
  47. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -0
  48. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js +131 -0
  49. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -0
  50. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js +10 -0
  51. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js.map +1 -0
  52. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +116 -0
  53. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -0
  54. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js +24 -0
  55. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -0
  56. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +116 -15
  57. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  58. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +27 -14
  59. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  60. package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue2.js +13 -6
  61. package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue2.js.map +1 -1
  62. package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue3.js +2 -0
  63. package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue3.js.map +1 -1
  64. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +532 -29
  65. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  66. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +300 -142
  67. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  68. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue.js +10 -0
  69. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue.js.map +1 -0
  70. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue2.js +237 -0
  71. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue2.js.map +1 -0
  72. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue3.js +120 -0
  73. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue3.js.map +1 -0
  74. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue.js +10 -0
  75. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue.js.map +1 -0
  76. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js +77 -0
  77. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js.map +1 -0
  78. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js +27 -0
  79. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js.map +1 -0
  80. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
  81. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js +1 -2
  82. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
  83. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +3 -1
  84. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  85. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +17 -17
  86. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  87. package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue2.js +4 -21
  88. package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue2.js.map +1 -1
  89. package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue3.js.map +1 -1
  90. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js +0 -13
  91. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js.map +1 -1
  92. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js +8 -8
  93. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js.map +1 -1
  94. package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue2.js.map +1 -1
  95. package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue3.js +3 -2
  96. package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue3.js.map +1 -1
  97. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +169 -12
  98. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
  99. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +15 -7
  100. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
  101. package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue2.js +37 -3
  102. package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue2.js.map +1 -1
  103. package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue3.js +44 -16
  104. package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue3.js.map +1 -1
  105. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +199 -16
  106. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  107. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +20 -10
  108. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  109. package/dist/esm/components/CnIndexPage/manifestActionDispatch.js +31 -11
  110. package/dist/esm/components/CnIndexPage/manifestActionDispatch.js.map +1 -1
  111. package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue2.js.map +1 -1
  112. package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue3.js +1 -2
  113. package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue3.js.map +1 -1
  114. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js.map +1 -1
  115. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js +0 -1
  116. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js.map +1 -1
  117. package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue2.js +2 -17
  118. package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue2.js.map +1 -1
  119. package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue3.js.map +1 -1
  120. package/dist/esm/components/CnLogsPage/CnLogsPage.vue2.js +35 -11
  121. package/dist/esm/components/CnLogsPage/CnLogsPage.vue2.js.map +1 -1
  122. package/dist/esm/components/CnLogsPage/CnLogsPage.vue3.js +3 -2
  123. package/dist/esm/components/CnLogsPage/CnLogsPage.vue3.js.map +1 -1
  124. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue.js +10 -0
  125. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue.js.map +1 -0
  126. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js +150 -0
  127. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js.map +1 -0
  128. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js +106 -0
  129. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js.map +1 -0
  130. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue.js +9 -0
  131. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue.js.map +1 -0
  132. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js +47 -0
  133. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js.map +1 -0
  134. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue3.js +17 -0
  135. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue3.js.map +1 -0
  136. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +23 -5
  137. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
  138. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +1 -1
  139. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
  140. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +49 -0
  141. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
  142. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
  143. package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue2.js +6 -0
  144. package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue2.js.map +1 -1
  145. package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue3.js.map +1 -1
  146. package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue2.js +46 -0
  147. package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue2.js.map +1 -1
  148. package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue3.js +15 -3
  149. package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue3.js.map +1 -1
  150. package/dist/esm/components/CnObjectList/CnObjectList.vue2.js +19 -3
  151. package/dist/esm/components/CnObjectList/CnObjectList.vue2.js.map +1 -1
  152. package/dist/esm/components/CnObjectList/CnObjectList.vue3.js.map +1 -1
  153. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +45 -15
  154. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  155. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +4 -2
  156. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  157. package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js +24 -6
  158. package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js.map +1 -1
  159. package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js +1 -1
  160. package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js.map +1 -1
  161. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  162. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +0 -1
  163. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  164. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js.map +1 -1
  165. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js +4 -2
  166. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js.map +1 -1
  167. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +37 -1
  168. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
  169. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js +29 -13
  170. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
  171. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js +100 -3
  172. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js.map +1 -1
  173. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js.map +1 -1
  174. package/dist/esm/components/CnRowActions/CnRowActions.vue2.js +7 -2
  175. package/dist/esm/components/CnRowActions/CnRowActions.vue2.js.map +1 -1
  176. package/dist/esm/components/CnRowActions/CnRowActions.vue3.js.map +1 -1
  177. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js +50 -3
  178. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js.map +1 -1
  179. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js.map +1 -1
  180. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue.js +10 -0
  181. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue.js.map +1 -0
  182. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js +186 -0
  183. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -0
  184. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js +46 -0
  185. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -0
  186. package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue2.js +5 -3
  187. package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue2.js.map +1 -1
  188. package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue3.js.map +1 -1
  189. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue.js +11 -0
  190. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue.js.map +1 -0
  191. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js +298 -0
  192. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js.map +1 -0
  193. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js +77 -0
  194. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js.map +1 -0
  195. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue.js +10 -0
  196. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue.js.map +1 -0
  197. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue2.js +169 -0
  198. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue2.js.map +1 -0
  199. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue3.js +48 -0
  200. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue3.js.map +1 -0
  201. package/dist/esm/components/CnTabs/CnTab.vue2.js +35 -0
  202. package/dist/esm/components/CnTabs/CnTab.vue2.js.map +1 -1
  203. package/dist/esm/components/CnTabs/CnTab.vue3.js.map +1 -1
  204. package/dist/esm/components/CnTabs/CnTabs.vue2.js +120 -7
  205. package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
  206. package/dist/esm/components/CnTabs/CnTabs.vue3.js +57 -5
  207. package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
  208. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +59 -4
  209. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  210. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +6 -2
  211. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  212. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +3 -1
  213. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
  214. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +1 -0
  215. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
  216. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js +10 -0
  217. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js.map +1 -0
  218. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js +484 -0
  219. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js.map +1 -0
  220. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js +115 -0
  221. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js.map +1 -0
  222. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue.js +10 -0
  223. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue.js.map +1 -0
  224. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue2.js +189 -0
  225. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue2.js.map +1 -0
  226. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue3.js +74 -0
  227. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue3.js.map +1 -0
  228. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +82 -8
  229. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  230. package/dist/esm/index.js +10 -0
  231. package/dist/esm/index.js.map +1 -1
  232. package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue2.js +3 -1
  233. package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue2.js.map +1 -1
  234. package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue3.js.map +1 -1
  235. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js +3 -1
  236. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js.map +1 -1
  237. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js +2 -4
  238. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js.map +1 -1
  239. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue2.js +6 -0
  240. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue2.js.map +1 -1
  241. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue3.js +68 -53
  242. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue3.js.map +1 -1
  243. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue2.js +17 -0
  244. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue2.js.map +1 -1
  245. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue3.js +21 -19
  246. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue3.js.map +1 -1
  247. package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue2.js +3 -1
  248. package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue2.js.map +1 -1
  249. package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue3.js.map +1 -1
  250. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue2.js +3 -1
  251. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue2.js.map +1 -1
  252. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue3.js +2 -4
  253. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue3.js.map +1 -1
  254. package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue2.js +2 -1
  255. package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue2.js.map +1 -1
  256. package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue3.js.map +1 -1
  257. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js +2 -1
  258. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js.map +1 -1
  259. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js +1 -2
  260. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js.map +1 -1
  261. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js +2 -1
  262. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js.map +1 -1
  263. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js.map +1 -1
  264. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js +2 -1
  265. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js.map +1 -1
  266. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js +1 -2
  267. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js.map +1 -1
  268. package/dist/esm/integrations/builtin/email/CnEmailCard.vue2.js.map +1 -1
  269. package/dist/esm/integrations/builtin/email/CnEmailCard.vue3.js.map +1 -1
  270. package/dist/esm/integrations/builtin/email/CnEmailTab.vue2.js.map +1 -1
  271. package/dist/esm/integrations/builtin/email/CnEmailTab.vue3.js +1 -2
  272. package/dist/esm/integrations/builtin/email/CnEmailTab.vue3.js.map +1 -1
  273. package/dist/esm/integrations/builtin/flow/CnFlowCard.vue2.js +2 -1
  274. package/dist/esm/integrations/builtin/flow/CnFlowCard.vue2.js.map +1 -1
  275. package/dist/esm/integrations/builtin/flow/CnFlowCard.vue3.js.map +1 -1
  276. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue2.js +2 -1
  277. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue2.js.map +1 -1
  278. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue3.js +1 -2
  279. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue3.js.map +1 -1
  280. package/dist/esm/integrations/builtin/forms/CnFormsCard.vue2.js +3 -1
  281. package/dist/esm/integrations/builtin/forms/CnFormsCard.vue2.js.map +1 -1
  282. package/dist/esm/integrations/builtin/forms/CnFormsCard.vue3.js.map +1 -1
  283. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue2.js +3 -1
  284. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue2.js.map +1 -1
  285. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue3.js +2 -4
  286. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue3.js.map +1 -1
  287. package/dist/esm/integrations/builtin/maps/CnMapsCard.vue2.js +3 -1
  288. package/dist/esm/integrations/builtin/maps/CnMapsCard.vue2.js.map +1 -1
  289. package/dist/esm/integrations/builtin/maps/CnMapsCard.vue3.js.map +1 -1
  290. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue2.js +3 -1
  291. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue2.js.map +1 -1
  292. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue3.js +2 -4
  293. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue3.js.map +1 -1
  294. package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue2.js +3 -1
  295. package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue2.js.map +1 -1
  296. package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue3.js.map +1 -1
  297. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue2.js +3 -1
  298. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue2.js.map +1 -1
  299. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue3.js +2 -4
  300. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue3.js.map +1 -1
  301. package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue2.js +2 -1
  302. package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue2.js.map +1 -1
  303. package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue3.js.map +1 -1
  304. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js +2 -1
  305. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js.map +1 -1
  306. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js +1 -2
  307. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js.map +1 -1
  308. package/dist/esm/integrations/builtin/polls/CnPollsCard.vue2.js +2 -1
  309. package/dist/esm/integrations/builtin/polls/CnPollsCard.vue2.js.map +1 -1
  310. package/dist/esm/integrations/builtin/polls/CnPollsCard.vue3.js.map +1 -1
  311. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue2.js +2 -1
  312. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue2.js.map +1 -1
  313. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue3.js +1 -2
  314. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue3.js.map +1 -1
  315. package/dist/esm/integrations/builtin/shares/CnSharesTab.vue2.js.map +1 -1
  316. package/dist/esm/integrations/builtin/shares/CnSharesTab.vue3.js +3 -6
  317. package/dist/esm/integrations/builtin/shares/CnSharesTab.vue3.js.map +1 -1
  318. package/dist/esm/integrations/builtin/talk/CnTalkCard.vue2.js +3 -1
  319. package/dist/esm/integrations/builtin/talk/CnTalkCard.vue2.js.map +1 -1
  320. package/dist/esm/integrations/builtin/talk/CnTalkCard.vue3.js.map +1 -1
  321. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue2.js +3 -1
  322. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue2.js.map +1 -1
  323. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue3.js +2 -4
  324. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue3.js.map +1 -1
  325. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue2.js +2 -1
  326. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue2.js.map +1 -1
  327. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue3.js.map +1 -1
  328. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue2.js +2 -1
  329. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue2.js.map +1 -1
  330. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue3.js +1 -2
  331. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue3.js.map +1 -1
  332. package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue2.js +4 -1
  333. package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue2.js.map +1 -1
  334. package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue3.js.map +1 -1
  335. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue2.js +6 -3
  336. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue2.js.map +1 -1
  337. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue3.js +2 -4
  338. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue3.js.map +1 -1
  339. package/dist/esm/l10n/en.json.js +55 -1
  340. package/dist/esm/l10n/en.json.js.map +1 -1
  341. package/dist/esm/l10n/nl.json.js +55 -1
  342. package/dist/esm/l10n/nl.json.js.map +1 -1
  343. package/dist/esm/nextcloud-vue.css +998 -7
  344. package/dist/esm/utils/actionLink.js +5 -0
  345. package/dist/esm/utils/actionLink.js.map +1 -1
  346. package/dist/esm/utils/actionsDispatcher.js +16 -1
  347. package/dist/esm/utils/actionsDispatcher.js.map +1 -1
  348. package/dist/esm/utils/dateVariant.js +124 -0
  349. package/dist/esm/utils/dateVariant.js.map +1 -0
  350. package/dist/esm/utils/detailActionModel.js +260 -0
  351. package/dist/esm/utils/detailActionModel.js.map +1 -0
  352. package/dist/esm/utils/dueRule.js +84 -0
  353. package/dist/esm/utils/dueRule.js.map +1 -0
  354. package/dist/esm/utils/fetchAggregate.js +37 -1
  355. package/dist/esm/utils/fetchAggregate.js.map +1 -1
  356. package/dist/esm/utils/fetchFilterCounts.js +186 -0
  357. package/dist/esm/utils/fetchFilterCounts.js.map +1 -0
  358. package/dist/esm/utils/libraryWidgetKeys.js +5 -0
  359. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  360. package/dist/esm/utils/linkNavigation.js +6 -3
  361. package/dist/esm/utils/linkNavigation.js.map +1 -1
  362. package/dist/esm/utils/readPath.js +30 -0
  363. package/dist/esm/utils/readPath.js.map +1 -0
  364. package/dist/esm/utils/rowAuxClick.js +30 -5
  365. package/dist/esm/utils/rowAuxClick.js.map +1 -1
  366. package/dist/esm/utils/safeHref.js +18 -3
  367. package/dist/esm/utils/safeHref.js.map +1 -1
  368. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  369. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  370. package/dist/nextcloud-vue.cjs.js +15855 -9353
  371. package/dist/nextcloud-vue.cjs.js.map +2 -2
  372. package/dist/nextcloud-vue.css +998 -7
  373. package/l10n/en.json +55 -1
  374. package/l10n/nl.json +55 -1
  375. package/package.json +1 -1
  376. package/scripts/.jsdoc-baselines.json +3 -0
  377. package/src/components/CnActionsBar/CnActionsBar.vue +14 -5
  378. package/src/components/CnActionsMenu/CnActionsMenu.vue +14 -4
  379. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +1 -2
  380. package/src/components/CnAppNav/CnAppNav.md +26 -0
  381. package/src/components/CnAppNav/CnAppNav.vue +104 -1
  382. package/src/components/CnBannerWidget/CnBannerWidget.vue +365 -10
  383. package/src/components/CnBannerWidgetForm/CnBannerWidgetForm.vue +65 -3
  384. package/src/components/CnBoardView/CnBoardView.vue +90 -5
  385. package/src/components/CnBrandStripe/CnBrandStripe.md +22 -0
  386. package/src/components/CnBrandStripe/CnBrandStripe.vue +100 -0
  387. package/src/components/CnBrandStripe/index.js +4 -0
  388. package/src/components/CnCardGrid/CnCardGrid.vue +9 -1
  389. package/src/components/CnCellRenderer/CnCellRenderer.vue +207 -1
  390. package/src/components/CnContextMenu/CnContextMenu.vue +6 -1
  391. package/src/components/CnConversationThread/CnConversationThread.md +51 -0
  392. package/src/components/CnConversationThread/CnConversationThread.vue +457 -0
  393. package/src/components/CnConversationThread/CnConversationWidget.vue +144 -0
  394. package/src/components/CnConversationThread/index.js +4 -0
  395. package/src/components/CnDataTable/CnDataTable.md +1 -1
  396. package/src/components/CnDataTable/CnDataTable.vue +131 -17
  397. package/src/components/CnDateAxisView/CnDateAxisView.vue +15 -6
  398. package/src/components/CnDetailPage/CnDetailPage.md +17 -1
  399. package/src/components/CnDetailPage/CnDetailPage.vue +712 -49
  400. package/src/components/CnDocumentReviewList/CnDocumentReviewList.md +55 -0
  401. package/src/components/CnDocumentReviewList/CnDocumentReviewList.vue +384 -0
  402. package/src/components/CnDocumentReviewList/CnDocumentReviewWidget.vue +108 -0
  403. package/src/components/CnDocumentReviewList/index.js +4 -0
  404. package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +1 -2
  405. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +5 -3
  406. package/src/components/CnFilesWidget/CnFilesWidget.vue +4 -21
  407. package/src/components/CnFlowRunsWidget/CnFlowRunsWidget.vue +8 -21
  408. package/src/components/CnFlowsPage/CnFlowsPage.vue +2 -1
  409. package/src/components/CnHeaderWidget/CnHeaderWidget.vue +189 -14
  410. package/src/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue +53 -3
  411. package/src/components/CnIndexPage/CnIndexPage.vue +210 -17
  412. package/src/components/CnIndexPage/manifestActionDispatch.js +31 -11
  413. package/src/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue +1 -2
  414. package/src/components/CnInteractionFormWidget/CnInteractionFormWidget.vue +0 -1
  415. package/src/components/CnLinkButtonWidget/CnLinkButtonWidget.vue +2 -17
  416. package/src/components/CnLogsPage/CnLogsPage.vue +37 -12
  417. package/src/components/CnNextStepCard/CnNextStepCard.md +40 -0
  418. package/src/components/CnNextStepCard/CnNextStepCard.vue +319 -0
  419. package/src/components/CnNextStepCard/CnNextStepWidget.vue +59 -0
  420. package/src/components/CnNextStepCard/index.js +4 -0
  421. package/src/components/CnObjectCard/CnObjectCard.vue +24 -6
  422. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +49 -0
  423. package/src/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue +6 -0
  424. package/src/components/CnObjectKanban/CnObjectKanban.vue +86 -2
  425. package/src/components/CnObjectList/CnObjectList.vue +19 -3
  426. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +47 -15
  427. package/src/components/CnObjectRow/CnObjectRow.vue +25 -7
  428. package/src/components/CnObjectSidebar/CnFilesTab.vue +0 -1
  429. package/src/components/CnPageRenderer/CnPageRenderer.vue +2 -0
  430. package/src/components/CnQuickFilterBar/CnQuickFilterBar.md +3 -0
  431. package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +60 -1
  432. package/src/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue +100 -3
  433. package/src/components/CnRowActions/CnRowActions.vue +7 -2
  434. package/src/components/CnSavedViewsControl/CnSavedViewsControl.vue +50 -3
  435. package/src/components/CnSegmentedControl/CnSegmentedControl.md +46 -0
  436. package/src/components/CnSegmentedControl/CnSegmentedControl.vue +291 -0
  437. package/src/components/CnSegmentedControl/index.js +4 -0
  438. package/src/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue +5 -3
  439. package/src/components/CnStackedBarWidget/CnStackedBarWidget.md +15 -0
  440. package/src/components/CnStackedBarWidget/CnStackedBarWidget.vue +414 -0
  441. package/src/components/CnStackedBarWidget/index.js +14 -0
  442. package/src/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue +224 -0
  443. package/src/components/CnStackedBarWidgetForm/index.js +4 -0
  444. package/src/components/CnTabs/CnTab.vue +35 -0
  445. package/src/components/CnTabs/CnTabs.vue +251 -13
  446. package/src/components/CnTabsWidget/CnTabsWidget.vue +63 -4
  447. package/src/components/CnTasksWidget/CnTasksWidget.vue +4 -1
  448. package/src/components/CnWeekStripWidget/CnWeekStripWidget.md +31 -0
  449. package/src/components/CnWeekStripWidget/CnWeekStripWidget.vue +668 -0
  450. package/src/components/CnWeekStripWidget/index.js +14 -0
  451. package/src/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue +265 -0
  452. package/src/components/CnWeekStripWidgetForm/index.js +4 -0
  453. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +74 -0
  454. package/src/components/index.js +11 -0
  455. package/src/css/detail-page.css +47 -0
  456. package/src/css/table.css +26 -0
  457. package/src/index.js +9 -0
  458. package/src/integrations/builtin/analytics/CnAnalyticsCard.vue +3 -2
  459. package/src/integrations/builtin/analytics/CnAnalyticsTab.vue +5 -6
  460. package/src/integrations/builtin/bookmarks/CnBookmarksCard.vue +12 -1
  461. package/src/integrations/builtin/bookmarks/CnBookmarksTab.vue +23 -6
  462. package/src/integrations/builtin/bookmarks/__tests__/CnBookmarksTab.spec.js +7 -2
  463. package/src/integrations/builtin/collectives/CnCollectivesCard.vue +3 -2
  464. package/src/integrations/builtin/collectives/CnCollectivesTab.vue +5 -6
  465. package/src/integrations/builtin/cospend/CnCospendCard.vue +2 -2
  466. package/src/integrations/builtin/cospend/CnCospendTab.vue +3 -4
  467. package/src/integrations/builtin/deck/CnDeckCard.vue +2 -2
  468. package/src/integrations/builtin/deck/CnDeckTab.vue +3 -4
  469. package/src/integrations/builtin/email/CnEmailCard.vue +6 -2
  470. package/src/integrations/builtin/email/CnEmailTab.vue +7 -4
  471. package/src/integrations/builtin/flow/CnFlowCard.vue +2 -2
  472. package/src/integrations/builtin/flow/CnFlowTab.vue +3 -4
  473. package/src/integrations/builtin/forms/CnFormsCard.vue +3 -2
  474. package/src/integrations/builtin/forms/CnFormsTab.vue +5 -6
  475. package/src/integrations/builtin/maps/CnMapsCard.vue +3 -2
  476. package/src/integrations/builtin/maps/CnMapsTab.vue +5 -6
  477. package/src/integrations/builtin/maps/__tests__/CnMapsTab.spec.js +9 -0
  478. package/src/integrations/builtin/openproject/CnOpenprojectCard.vue +3 -2
  479. package/src/integrations/builtin/openproject/CnOpenprojectTab.vue +5 -6
  480. package/src/integrations/builtin/photos/CnPhotosCard.vue +2 -2
  481. package/src/integrations/builtin/photos/CnPhotosTab.vue +3 -4
  482. package/src/integrations/builtin/polls/CnPollsCard.vue +2 -2
  483. package/src/integrations/builtin/polls/CnPollsTab.vue +3 -4
  484. package/src/integrations/builtin/shares/CnSharesTab.vue +3 -6
  485. package/src/integrations/builtin/talk/CnTalkCard.vue +3 -2
  486. package/src/integrations/builtin/talk/CnTalkTab.vue +5 -6
  487. package/src/integrations/builtin/time-tracker/CnTimeTrackerCard.vue +2 -2
  488. package/src/integrations/builtin/time-tracker/CnTimeTrackerTab.vue +3 -4
  489. package/src/integrations/builtin/xwiki/CnXwikiCard.vue +4 -2
  490. package/src/integrations/builtin/xwiki/CnXwikiTab.vue +7 -7
  491. package/src/integrations/builtin/xwiki/__tests__/CnXwikiTab.spec.js +9 -0
  492. package/src/schemas/app-manifest-v2.schema.json +389 -1
  493. package/src/schemas/app-manifest.schema.json +2 -1
  494. package/src/utils/actionLink.js +5 -0
  495. package/src/utils/actionsDispatcher.js +15 -0
  496. package/src/utils/dateVariant.js +120 -0
  497. package/src/utils/detailActionModel.js +256 -0
  498. package/src/utils/dueRule.js +94 -0
  499. package/src/utils/fetchAggregate.js +36 -0
  500. package/src/utils/fetchFilterCounts.js +182 -0
  501. package/src/utils/index.js +1 -0
  502. package/src/utils/libraryWidgetKeys.js +5 -0
  503. package/src/utils/linkNavigation.js +6 -3
  504. package/src/utils/readPath.js +27 -0
  505. package/src/utils/rowAuxClick.js +29 -4
  506. package/src/utils/safeHref.js +18 -3
  507. package/src/utils/validateManifestV2.compiled.js +2 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetchFilterCounts.js","sources":["../../../src/utils/fetchFilterCounts.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * fetchFilterCounts: how many records each of a set of filters matches, in as\n * few requests as the API allows.\n *\n * Quick filters and saved views usually differ in ONE field (`status: open`,\n * `status: closed`, ...). Those are answered by one `/grouped` request on that\n * field. A filter that narrows on something else (two fields, an operator, a\n * list of values) cannot be read off a grouped result, so each of those costs\n * one `/value` count. A filter that holds a LIST of values is counted through\n * the list endpoint, because the aggregation endpoints cannot express it.\n * Nothing is requested for an entry that did not ask.\n *\n * @module utils/fetchFilterCounts\n */\n\nimport { fetchAggregateValue, fetchGroupedCounts } from './fetchAggregate.js'\nimport { buildHeaders, buildQueryString, prefixUrl } from './headers.js'\nimport { resolveFilterTokens } from './resolveFilterTokens.js'\n\n/**\n * Whether a filter holds a list of values (`{ status: ['open', 'hold'] }`).\n *\n * The aggregation endpoints have no spelling for \"one of these values\", so a\n * filter like this is never sent to them. It is counted through the list\n * endpoint instead, which is where the page's own request sends it.\n *\n * @param {object} filter A filter map.\n * @return {boolean} True when any value is an array.\n * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views\n */\nexport function hasListValue(filter) {\n\tif (!filter || typeof filter !== 'object') {\n\t\treturn false\n\t}\n\treturn Object.values(filter).some((value) => Array.isArray(value))\n}\n\n/**\n * Count the records a filter matches by asking the LIST endpoint for one\n * record and reading its `total`. The filter is sent the way the list request\n * sends it: a list of values as repeated `field[]=`, an operator as\n * `field[op]=`.\n *\n * @param {string} register The register slug.\n * @param {string} schema The schema slug.\n * @param {object} filter The filter map.\n * @param {object} [ctx] Token-resolution context for the filter.\n * @return {Promise<number|null>} The total, or null when the response carries none.\n * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views\n */\nexport async function fetchListTotal(register, schema, filter, ctx) {\n\tconst resolved = resolveFilterTokens(filter || {}, ctx || {})\n\tconst params = {}\n\tfor (const [key, value] of Object.entries(resolved || {})) {\n\t\tif (value && typeof value === 'object' && !Array.isArray(value)) {\n\t\t\tfor (const [op, operand] of Object.entries(value)) {\n\t\t\t\tparams[`${key}[${op}]`] = operand\n\t\t\t}\n\t\t} else {\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tconst qs = buildQueryString({ ...params, _limit: 1 })\n\tconst url = prefixUrl(`/apps/openregister/api/objects/${encodeURIComponent(register)}/${encodeURIComponent(schema)}${qs}`)\n\tconst response = await fetch(url, { headers: buildHeaders() })\n\tif (!response.ok) {\n\t\tthrow new Error(`list returned ${response.status}`)\n\t}\n\tconst data = await response.json()\n\treturn typeof data.total === 'number' ? data.total : null\n}\n\n/**\n * The single field a filter narrows on with a plain equality, or null when the\n * filter is anything else.\n *\n * @param {object} filter A filter map.\n * @return {{field: string, value: string}|null} The field and the value it must equal.\n * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views\n */\nexport function singleEqualityOf(filter) {\n\tif (!filter || typeof filter !== 'object' || Array.isArray(filter)) {\n\t\treturn null\n\t}\n\tconst keys = Object.keys(filter)\n\tif (keys.length !== 1) {\n\t\treturn null\n\t}\n\tconst value = filter[keys[0]]\n\tif (value === null || value === undefined || value === '' || typeof value === 'object') {\n\t\treturn null\n\t}\n\t// A token (`@me`, `@today`) resolves to a value the grouped keys are\n\t// compared against only after resolution, which the count request does.\n\tif (typeof value === 'string' && value.charAt(0) === '@') {\n\t\treturn null\n\t}\n\treturn { field: keys[0], value: String(value) }\n}\n\n/**\n * Count the records each entry's filter matches.\n *\n * @param {object} options The request.\n * @param {string} options.register The register slug.\n * @param {string} options.schema The schema slug.\n * @param {Array<{key: (string|number), filter: object}>} options.entries The filters to count, each under its own key.\n * @param {object} [options.baseFilter] A filter every count is narrowed by (the page's fixed filter).\n * @param {object} [options.ctx] Token-resolution context for the filters.\n * @return {Promise<{[key: string]: number}>} The count per entry key. An entry whose count failed is left out.\n * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views\n */\nexport async function fetchFilterCounts({ register, schema, entries, baseFilter, ctx } = {}) {\n\tconst counts = {}\n\tif (!register || !schema || !Array.isArray(entries) || entries.length === 0) {\n\t\treturn counts\n\t}\n\tconst base = (baseFilter && typeof baseFilter === 'object') ? baseFilter : {}\n\n\t// Entries that share one equality field go into one grouped request, but\n\t// only when at least two do: a lone entry costs one request either way,\n\t// and `/value` returns exactly its number.\n\t// A list of values anywhere in a request rules the aggregation endpoints\n\t// out for it. A base filter with one rules them out for every entry.\n\tconst baseHasList = hasListValue(base)\n\tconst byField = {}\n\tconst single = []\n\tconst viaList = []\n\tfor (const entry of entries) {\n\t\tif (baseHasList || hasListValue(entry.filter)) {\n\t\t\tviaList.push(entry)\n\t\t\tcontinue\n\t\t}\n\t\tconst eq = singleEqualityOf(entry.filter)\n\t\tif (eq !== null && !(eq.field in base)) {\n\t\t\t(byField[eq.field] = byField[eq.field] || []).push({ entry, value: eq.value })\n\t\t} else {\n\t\t\tsingle.push(entry)\n\t\t}\n\t}\n\tconst jobs = []\n\tfor (const [field, members] of Object.entries(byField)) {\n\t\tif (members.length < 2) {\n\t\t\tsingle.push(members[0].entry)\n\t\t\tcontinue\n\t\t}\n\t\tjobs.push(fetchGroupedCounts({ register, schema, groupBy: field, filter: base }, ctx)\n\t\t\t.then((groups) => {\n\t\t\t\tconst map = {}\n\t\t\t\tfor (const group of groups) {\n\t\t\t\t\tmap[group.key] = group.count\n\t\t\t\t}\n\t\t\t\tfor (const member of members) {\n\t\t\t\t\tcounts[member.entry.key] = map[member.value] || 0\n\t\t\t\t}\n\t\t\t})\n\t\t\t.catch(() => {}))\n\t}\n\tfor (const entry of single) {\n\t\tjobs.push(fetchAggregateValue({ register, schema, metric: 'count', filter: { ...base, ...(entry.filter || {}) } }, ctx)\n\t\t\t.then((value) => {\n\t\t\t\tif (value !== null && Number.isFinite(Number(value))) {\n\t\t\t\t\tcounts[entry.key] = Number(value)\n\t\t\t\t}\n\t\t\t})\n\t\t\t.catch(() => {}))\n\t}\n\tfor (const entry of viaList) {\n\t\tjobs.push(fetchListTotal(register, schema, { ...base, ...(entry.filter || {}) }, ctx)\n\t\t\t.then((total) => {\n\t\t\t\tif (total !== null) {\n\t\t\t\t\tcounts[entry.key] = total\n\t\t\t\t}\n\t\t\t})\n\t\t\t.catch(() => {}))\n\t}\n\tawait Promise.all(jobs)\n\treturn counts\n}\n"],"names":[],"mappings":"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"}
@@ -94,10 +94,12 @@ const DASHBOARD_CATALOG_WIDGET_KEYS = Object.freeze([
94
94
  'calendar',
95
95
  'chart',
96
96
  'container',
97
+ 'conversation',
97
98
  'countdown',
98
99
  'data',
99
100
  'delta',
100
101
  'divider',
102
+ 'document-review',
101
103
  'files',
102
104
  'flow-runs',
103
105
  'gauge',
@@ -111,6 +113,7 @@ const DASHBOARD_CATALOG_WIDGET_KEYS = Object.freeze([
111
113
  'map',
112
114
  'menu',
113
115
  'nc-widget',
116
+ 'next-step',
114
117
  // `news` was here until the widget moved to LaunchPad, which owns the feed
115
118
  // fetching. An app that renders one now gets it from LaunchPad's registry.
116
119
  'object-geo',
@@ -120,6 +123,7 @@ const DASHBOARD_CATALOG_WIDGET_KEYS = Object.freeze([
120
123
  'quicklinks',
121
124
  'related',
122
125
  'spend-analytics',
126
+ 'stacked-bar',
123
127
  'stages',
124
128
  'stat',
125
129
  'stats-block',
@@ -129,6 +133,7 @@ const DASHBOARD_CATALOG_WIDGET_KEYS = Object.freeze([
129
133
  'text',
130
134
  'tile',
131
135
  'video',
136
+ 'week-strip',
132
137
  'workspace-filter',
133
138
  ]);
134
139
 
@@ -1 +1 @@
1
- {"version":3,"file":"libraryWidgetKeys.js","sources":["../../../src/utils/libraryWidgetKeys.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * libraryWidgetKeys — the one vocabulary of widget keys the LIBRARY itself\n * renders, and the only list `validateManifestV2` is allowed to consult.\n *\n * WHY THIS MODULE EXISTS\n * ----------------------\n * The validator used to carry its own hand-written array of \"library built-in\n * widget keys\", with a comment instructing whoever added a built-in to append\n * it \"in the same PR\". That instruction was followed for five keys and missed\n * for six, so the validator knew 11 keys while the library actually rendered\n * 45. Nothing failed when the two disagreed, because the only consumer is an\n * exemption: a key the validator does not recognise is treated as a CUSTOM\n * registry component, which makes a legitimate single-widget library dashboard\n * fail the ADR-036 Decision 1 anti-pattern rule with advice (\"declare it as\n * type:'custom'\") that is wrong for a library widget.\n *\n * A comment cannot enforce anything, so the array moved here and\n * `tests/utils/libraryWidgetKeys.spec.js` now binds it to the two registries\n * that actually resolve a key at runtime:\n *\n * 1. `BUILT_IN_WIDGETS` (components/CnWidgetGrid/builtInWidgets.js) — the v2\n * manifest widget set `CnWidgetGrid` resolves a `widgetKey` against.\n * 2. The dashboard widget catalog (`dashboardWidgetRegistry`), populated by\n * `registerDashboardWidgets.js`. `CnWidgetGrid` falls back to it (third in\n * the chain, after the consumer registry and `BUILT_IN_WIDGETS`), so a\n * catalog key renders on a v2 page exactly like a `BUILT_IN_WIDGETS` one.\n *\n * The test asserts SET EQUALITY in both directions against the live registries,\n * so adding a widget to either registry without touching this file fails CI,\n * and deleting one without removing it here fails too. The lists below are the\n * validator's view; the registries are the truth the test holds them to.\n *\n * THE CATALOG IS NOT LEGACY. It is a live half of the same vocabulary, and the\n * old array already conceded the point by exempting `chart` and `stats-block`,\n * which are catalog-only keys that `BUILT_IN_WIDGETS` has never held. What it\n * got wrong was doing that for two keys and not the other thirty-seven.\n * `widgetDispatch.js` unified the two resolution paths for the same reason: a\n * widget authored against one vocabulary was invisible to the other and said\n * nothing about why (101 of 236 detail widgets on hrmq).\n *\n * @module utils/libraryWidgetKeys\n */\n\nimport { WIDGET_TYPE_ALIASES } from './widgetTypeAliases.js'\n\n/**\n * Keys of `BUILT_IN_WIDGETS` — the v2 manifest widget set.\n *\n * Kept in the registry's own declaration order so a diff against\n * `builtInWidgets.js` reads straight down.\n *\n * @type {string[]} Frozen.\n */\nexport const BUILT_IN_WIDGET_KEYS = Object.freeze([\n\t'object-table',\n\t'form-renderer',\n\t'map-viewer',\n\t'object-geo',\n\t'card-grid',\n\t'nav-card-grid',\n\t'data',\n\t'metadata',\n\t'related',\n\t'integration',\n\t'banner',\n\t'audit-trail',\n\t'header',\n\t'text',\n\t'divider',\n])\n\n/**\n * Widget types the LIBRARY registers into the dashboard catalog at import time\n * (`registerDashboardWidgets.js` and the widget `index.js` modules it pulls in).\n *\n * Deliberately a static list rather than a read of `dashboardWidgetRegistry`:\n * that object is mutable and consumer-extensible, so deriving the exemption\n * from it at call time would let an app register its own full-page component in\n * the catalog and walk straight through the ADR-036 Decision 1 rule the\n * exemption exists to enforce. Only keys the library itself ships are exempt.\n *\n * Sorted, because unlike `BUILT_IN_WIDGETS` these registrations are spread over\n * ~30 files and have no meaningful declaration order.\n *\n * @type {string[]} Frozen.\n */\nexport const DASHBOARD_CATALOG_WIDGET_KEYS = Object.freeze([\n\t'audit-trail',\n\t'banner',\n\t'calendar',\n\t'chart',\n\t'container',\n\t'countdown',\n\t'data',\n\t'delta',\n\t'divider',\n\t'files',\n\t'flow-runs',\n\t'gauge',\n\t'header',\n\t'image',\n\t'interaction-form',\n\t'kb-search',\n\t'label',\n\t'link',\n\t'links',\n\t'map',\n\t'menu',\n\t'nc-widget',\n\t// `news` was here until the widget moved to LaunchPad, which owns the feed\n\t// fetching. An app that renders one now gets it from LaunchPad's registry.\n\t'object-geo',\n\t'object-list',\n\t'object-table',\n\t'people',\n\t'quicklinks',\n\t'related',\n\t'spend-analytics',\n\t'stages',\n\t'stat',\n\t'stats-block',\n\t'table',\n\t'tabs',\n\t'tasks',\n\t'text',\n\t'tile',\n\t'video',\n\t'workspace-filter',\n])\n\n/**\n * Expand a key set over `WIDGET_TYPE_ALIASES` so both spellings of an aliased\n * widget are present whenever either one is.\n *\n * `CnWidgetGrid` resolves a key and its canonical form (`object-table` ⇄\n * `table`, `map-viewer` ⇄ `map`), so both spellings render and both must be\n * exempt. Today each registry happens to hold one side of each pair, which is\n * exactly the accident that would silently stop being true when the next alias\n * lands.\n *\n * @param {Set<string>} keys The key set to expand, mutated in place.\n * @return {Set<string>} The same set.\n */\nfunction expandAliases(keys) {\n\tfor (const [alias, canonical] of Object.entries(WIDGET_TYPE_ALIASES)) {\n\t\tif (keys.has(alias) || keys.has(canonical)) {\n\t\t\tkeys.add(alias)\n\t\t\tkeys.add(canonical)\n\t\t}\n\t}\n\treturn keys\n}\n\n/**\n * Every widget key the library renders itself: both registries, plus both\n * spellings of every alias.\n *\n * A `widgetKey` in this list is served by a library `Cn*` SFC and does NOT\n * count as a custom registry component for the single-12×12-widget dashboard\n * rule (ADR-036 Decision 1).\n *\n * @type {string[]} Frozen.\n */\nexport const LIBRARY_WIDGET_KEYS = Object.freeze([\n\t...expandAliases(new Set([\n\t\t...BUILT_IN_WIDGET_KEYS,\n\t\t...DASHBOARD_CATALOG_WIDGET_KEYS,\n\t])),\n])\n"],"names":[],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,oBAAoB,GAAG,MAAM,CAAC,MAAM,CAAC;AAClD,CAAC,cAAc;AACf,CAAC,eAAe;AAChB,CAAC,YAAY;AACb,CAAC,YAAY;AACb,CAAC,WAAW;AACZ,CAAC,eAAe;AAChB,CAAC,MAAM;AACP,CAAC,UAAU;AACX,CAAC,SAAS;AACV,CAAC,aAAa;AACd,CAAC,QAAQ;AACT,CAAC,aAAa;AACd,CAAC,QAAQ;AACT,CAAC,MAAM;AACP,CAAC,SAAS;AACV,CAAC,EAAC;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,6BAA6B,GAAG,MAAM,CAAC,MAAM,CAAC;AAC3D,CAAC,aAAa;AACd,CAAC,QAAQ;AACT,CAAC,UAAU;AACX,CAAC,OAAO;AACR,CAAC,WAAW;AACZ,CAAC,WAAW;AACZ,CAAC,MAAM;AACP,CAAC,OAAO;AACR,CAAC,SAAS;AACV,CAAC,OAAO;AACR,CAAC,WAAW;AACZ,CAAC,OAAO;AACR,CAAC,QAAQ;AACT,CAAC,OAAO;AACR,CAAC,kBAAkB;AACnB,CAAC,WAAW;AACZ,CAAC,OAAO;AACR,CAAC,MAAM;AACP,CAAC,OAAO;AACR,CAAC,KAAK;AACN,CAAC,MAAM;AACP,CAAC,WAAW;AACZ;AACA;AACA,CAAC,YAAY;AACb,CAAC,aAAa;AACd,CAAC,cAAc;AACf,CAAC,QAAQ;AACT,CAAC,YAAY;AACb,CAAC,SAAS;AACV,CAAC,iBAAiB;AAClB,CAAC,QAAQ;AACT,CAAC,MAAM;AACP,CAAC,aAAa;AACd,CAAC,OAAO;AACR,CAAC,MAAM;AACP,CAAC,OAAO;AACR,CAAC,MAAM;AACP,CAAC,MAAM;AACP,CAAC,OAAO;AACR,CAAC,kBAAkB;AACnB,CAAC,EAAC;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,aAAa,CAAC,IAAI,EAAE;AAC7B,CAAC,KAAK,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,mBAAmB,CAAC,EAAE;AACvE,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE;AAC9C,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAC;AAClB,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,EAAC;AACtB,GAAG;AACH,EAAE;AACF,CAAC,OAAO,IAAI;AACZ,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,mBAAmB,GAAG,MAAM,CAAC,MAAM,CAAC;AACjD,CAAC,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC;AAC1B,EAAE,GAAG,oBAAoB;AACzB,EAAE,GAAG,6BAA6B;AAClC,EAAE,CAAC,CAAC;AACJ,CAAC;;;;"}
1
+ {"version":3,"file":"libraryWidgetKeys.js","sources":["../../../src/utils/libraryWidgetKeys.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * libraryWidgetKeys — the one vocabulary of widget keys the LIBRARY itself\n * renders, and the only list `validateManifestV2` is allowed to consult.\n *\n * WHY THIS MODULE EXISTS\n * ----------------------\n * The validator used to carry its own hand-written array of \"library built-in\n * widget keys\", with a comment instructing whoever added a built-in to append\n * it \"in the same PR\". That instruction was followed for five keys and missed\n * for six, so the validator knew 11 keys while the library actually rendered\n * 45. Nothing failed when the two disagreed, because the only consumer is an\n * exemption: a key the validator does not recognise is treated as a CUSTOM\n * registry component, which makes a legitimate single-widget library dashboard\n * fail the ADR-036 Decision 1 anti-pattern rule with advice (\"declare it as\n * type:'custom'\") that is wrong for a library widget.\n *\n * A comment cannot enforce anything, so the array moved here and\n * `tests/utils/libraryWidgetKeys.spec.js` now binds it to the two registries\n * that actually resolve a key at runtime:\n *\n * 1. `BUILT_IN_WIDGETS` (components/CnWidgetGrid/builtInWidgets.js) — the v2\n * manifest widget set `CnWidgetGrid` resolves a `widgetKey` against.\n * 2. The dashboard widget catalog (`dashboardWidgetRegistry`), populated by\n * `registerDashboardWidgets.js`. `CnWidgetGrid` falls back to it (third in\n * the chain, after the consumer registry and `BUILT_IN_WIDGETS`), so a\n * catalog key renders on a v2 page exactly like a `BUILT_IN_WIDGETS` one.\n *\n * The test asserts SET EQUALITY in both directions against the live registries,\n * so adding a widget to either registry without touching this file fails CI,\n * and deleting one without removing it here fails too. The lists below are the\n * validator's view; the registries are the truth the test holds them to.\n *\n * THE CATALOG IS NOT LEGACY. It is a live half of the same vocabulary, and the\n * old array already conceded the point by exempting `chart` and `stats-block`,\n * which are catalog-only keys that `BUILT_IN_WIDGETS` has never held. What it\n * got wrong was doing that for two keys and not the other thirty-seven.\n * `widgetDispatch.js` unified the two resolution paths for the same reason: a\n * widget authored against one vocabulary was invisible to the other and said\n * nothing about why (101 of 236 detail widgets on hrmq).\n *\n * @module utils/libraryWidgetKeys\n */\n\nimport { WIDGET_TYPE_ALIASES } from './widgetTypeAliases.js'\n\n/**\n * Keys of `BUILT_IN_WIDGETS` — the v2 manifest widget set.\n *\n * Kept in the registry's own declaration order so a diff against\n * `builtInWidgets.js` reads straight down.\n *\n * @type {string[]} Frozen.\n */\nexport const BUILT_IN_WIDGET_KEYS = Object.freeze([\n\t'object-table',\n\t'form-renderer',\n\t'map-viewer',\n\t'object-geo',\n\t'card-grid',\n\t'nav-card-grid',\n\t'data',\n\t'metadata',\n\t'related',\n\t'integration',\n\t'banner',\n\t'audit-trail',\n\t'header',\n\t'text',\n\t'divider',\n])\n\n/**\n * Widget types the LIBRARY registers into the dashboard catalog at import time\n * (`registerDashboardWidgets.js` and the widget `index.js` modules it pulls in).\n *\n * Deliberately a static list rather than a read of `dashboardWidgetRegistry`:\n * that object is mutable and consumer-extensible, so deriving the exemption\n * from it at call time would let an app register its own full-page component in\n * the catalog and walk straight through the ADR-036 Decision 1 rule the\n * exemption exists to enforce. Only keys the library itself ships are exempt.\n *\n * Sorted, because unlike `BUILT_IN_WIDGETS` these registrations are spread over\n * ~30 files and have no meaningful declaration order.\n *\n * @type {string[]} Frozen.\n */\nexport const DASHBOARD_CATALOG_WIDGET_KEYS = Object.freeze([\n\t'audit-trail',\n\t'banner',\n\t'calendar',\n\t'chart',\n\t'container',\n\t'conversation',\n\t'countdown',\n\t'data',\n\t'delta',\n\t'divider',\n\t'document-review',\n\t'files',\n\t'flow-runs',\n\t'gauge',\n\t'header',\n\t'image',\n\t'interaction-form',\n\t'kb-search',\n\t'label',\n\t'link',\n\t'links',\n\t'map',\n\t'menu',\n\t'nc-widget',\n\t'next-step',\n\t// `news` was here until the widget moved to LaunchPad, which owns the feed\n\t// fetching. An app that renders one now gets it from LaunchPad's registry.\n\t'object-geo',\n\t'object-list',\n\t'object-table',\n\t'people',\n\t'quicklinks',\n\t'related',\n\t'spend-analytics',\n\t'stacked-bar',\n\t'stages',\n\t'stat',\n\t'stats-block',\n\t'table',\n\t'tabs',\n\t'tasks',\n\t'text',\n\t'tile',\n\t'video',\n\t'week-strip',\n\t'workspace-filter',\n])\n\n/**\n * Expand a key set over `WIDGET_TYPE_ALIASES` so both spellings of an aliased\n * widget are present whenever either one is.\n *\n * `CnWidgetGrid` resolves a key and its canonical form (`object-table` ⇄\n * `table`, `map-viewer` ⇄ `map`), so both spellings render and both must be\n * exempt. Today each registry happens to hold one side of each pair, which is\n * exactly the accident that would silently stop being true when the next alias\n * lands.\n *\n * @param {Set<string>} keys The key set to expand, mutated in place.\n * @return {Set<string>} The same set.\n */\nfunction expandAliases(keys) {\n\tfor (const [alias, canonical] of Object.entries(WIDGET_TYPE_ALIASES)) {\n\t\tif (keys.has(alias) || keys.has(canonical)) {\n\t\t\tkeys.add(alias)\n\t\t\tkeys.add(canonical)\n\t\t}\n\t}\n\treturn keys\n}\n\n/**\n * Every widget key the library renders itself: both registries, plus both\n * spellings of every alias.\n *\n * A `widgetKey` in this list is served by a library `Cn*` SFC and does NOT\n * count as a custom registry component for the single-12×12-widget dashboard\n * rule (ADR-036 Decision 1).\n *\n * @type {string[]} Frozen.\n */\nexport const LIBRARY_WIDGET_KEYS = Object.freeze([\n\t...expandAliases(new Set([\n\t\t...BUILT_IN_WIDGET_KEYS,\n\t\t...DASHBOARD_CATALOG_WIDGET_KEYS,\n\t])),\n])\n"],"names":[],"mappings":"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"}
@@ -70,8 +70,11 @@ function resolveHref(target, router) {
70
70
  * click is routed through the router; a modified click, or one a handler
71
71
  * already prevented, is left to the browser.
72
72
  *
73
+ * Unlike `resolveHref`, a string target is a router path, so pair the two
74
+ * with a location object (or `{ path }`) for the href and click to agree.
75
+ *
73
76
  * @param {MouseEvent} event The click event.
74
- * @param {string|object} target The router location the link points at.
77
+ * @param {string|object} target The router location the link points at; a string is a router path.
75
78
  * @param {object} [router] The app's router.
76
79
  * @return {boolean} True when the app navigated.
77
80
  */
@@ -97,8 +100,8 @@ function followLinkClick(event, target, router) {
97
100
  *
98
101
  * A click that came from a control inside the surface (a button, a link, a
99
102
  * checkbox) is left to that control. Opening a new tab marks the event with
100
- * `preventDefault()`, and an event already marked that way is skipped, so
101
- * two listeners on the same click never open two tabs.
103
+ * `markNewTabHandled`, and an event already marked is skipped, so two
104
+ * listeners on the same click never open two tabs.
102
105
  *
103
106
  * @param {MouseEvent|KeyboardEvent|null|undefined} event The click event.
104
107
  * @param {string|object} target A URL, or a vue-router location.
@@ -1 +1 @@
1
- {"version":3,"file":"linkNavigation.js","sources":["../../../src/utils/linkNavigation.js"],"sourcesContent":["/**\n * SPDX-License-Identifier: EUPL-1.2\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n *\n * linkNavigation — make navigation behave like a link.\n *\n * A control whose only job is to go somewhere should be a real `<a href>`, so\n * it can be middle-clicked, opened in a new tab and copied. Two helpers cover\n * the two shapes that needs:\n *\n * - `followLinkClick` for a real `<a href>` that routes inside the app: a\n * plain click goes through the router, any other click is left to the\n * browser.\n * - `openRowTarget` for a surface that cannot be an `<a>` (a table row, a\n * draggable card): a plain click navigates in place, a ctrl/cmd/shift or\n * middle click opens the target in a new tab. Bind it to both `@click` and\n * `@auxclick`, since a middle click fires only the latter.\n *\n * A string target is a finished URL. Pass a router path as `{ path }` so the\n * router adds the app's base (`/index.php/apps/<app>`) to its href.\n *\n * @module utils/linkNavigation\n */\n\nimport { isFromNestedControl, isNewTabClick, isNewTabHandled, markNewTabHandled } from './rowAuxClick.js'\n\n/**\n * Whether the browser should handle this click itself (new tab, new window,\n * download) instead of the app navigating in place.\n *\n * @param {MouseEvent|KeyboardEvent|null|undefined} event The click event.\n * @return {boolean} True for a modifier key or a non-primary button.\n */\nexport function isModifiedClick(event) {\n\tif (!event) {\n\t\treturn false\n\t}\n\treturn Boolean(event.metaKey || event.ctrlKey || event.shiftKey || event.altKey\n\t\t|| (event.button !== undefined && event.button !== 0))\n}\n\n/**\n * The href for a navigation target. A string is returned as is, so a router\n * path must be passed as `{ path }` to get the app's base.\n *\n * @param {string|object|null|undefined} target A URL, or a vue-router location.\n * @param {object} [router] The app's router, needed for a location.\n * @return {string} The href, or '' when it cannot be resolved.\n */\nexport function resolveHref(target, router) {\n\tif (!target) {\n\t\treturn ''\n\t}\n\tif (typeof target === 'string') {\n\t\treturn target\n\t}\n\tif (!router || typeof router.resolve !== 'function') {\n\t\treturn ''\n\t}\n\ttry {\n\t\treturn router.resolve(target).href || ''\n\t} catch {\n\t\treturn ''\n\t}\n}\n\n/**\n * Click handler for a real `<a href>` that routes inside the app. A plain\n * click is routed through the router; a modified click, or one a handler\n * already prevented, is left to the browser.\n *\n * @param {MouseEvent} event The click event.\n * @param {string|object} target The router location the link points at.\n * @param {object} [router] The app's router.\n * @return {boolean} True when the app navigated.\n */\nexport function followLinkClick(event, target, router) {\n\tif (!target || !router || typeof router.push !== 'function') {\n\t\treturn false\n\t}\n\tif (event && (event.defaultPrevented || isModifiedClick(event))) {\n\t\treturn false\n\t}\n\tif (event) {\n\t\tevent.preventDefault()\n\t}\n\t// vue-router rejects the promise on a blocked or duplicate navigation.\n\trouter.push(target).catch(() => {})\n\treturn true\n}\n\n/**\n * Navigate from a surface that cannot be an `<a>` (a table row, a card),\n * the way a link would: a ctrl/cmd/shift or middle click opens the target\n * in a new tab, a plain click navigates in place.\n *\n * A click that came from a control inside the surface (a button, a link, a\n * checkbox) is left to that control. Opening a new tab marks the event with\n * `preventDefault()`, and an event already marked that way is skipped, so\n * two listeners on the same click never open two tabs.\n *\n * @param {MouseEvent|KeyboardEvent|null|undefined} event The click event.\n * @param {string|object} target A URL, or a vue-router location.\n * @param {object} [router] The app's router, needed for a location.\n * @return {boolean} True when it navigated.\n */\nexport function openRowTarget(event, target, router) {\n\tif (!target || isNewTabHandled(event) || isFromNestedControl(event)) {\n\t\treturn false\n\t}\n\tif (isNewTabClick(event)) {\n\t\tconst href = resolveHref(target, router)\n\t\tif (!href) {\n\t\t\treturn false\n\t\t}\n\t\twindow.open(href, '_blank', 'noopener,noreferrer')\n\t\tmarkNewTabHandled(event, true)\n\t\treturn true\n\t}\n\t// Only the primary button navigates in place; a right click opens the menu.\n\tif (event && event.button !== undefined && event.button !== 0) {\n\t\treturn false\n\t}\n\tif (typeof target === 'string') {\n\t\twindow.location.assign(target)\n\t\treturn true\n\t}\n\tif (!router || typeof router.push !== 'function') {\n\t\treturn false\n\t}\n\trouter.push(target).catch(() => {})\n\treturn true\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"linkNavigation.js","sources":["../../../src/utils/linkNavigation.js"],"sourcesContent":["/**\n * SPDX-License-Identifier: EUPL-1.2\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n *\n * linkNavigation — make navigation behave like a link.\n *\n * A control whose only job is to go somewhere should be a real `<a href>`, so\n * it can be middle-clicked, opened in a new tab and copied. Two helpers cover\n * the two shapes that needs:\n *\n * - `followLinkClick` for a real `<a href>` that routes inside the app: a\n * plain click goes through the router, any other click is left to the\n * browser.\n * - `openRowTarget` for a surface that cannot be an `<a>` (a table row, a\n * draggable card): a plain click navigates in place, a ctrl/cmd/shift or\n * middle click opens the target in a new tab. Bind it to both `@click` and\n * `@auxclick`, since a middle click fires only the latter.\n *\n * A string target is a finished URL. Pass a router path as `{ path }` so the\n * router adds the app's base (`/index.php/apps/<app>`) to its href.\n *\n * @module utils/linkNavigation\n */\n\nimport { isFromNestedControl, isNewTabClick, isNewTabHandled, markNewTabHandled } from './rowAuxClick.js'\n\n/**\n * Whether the browser should handle this click itself (new tab, new window,\n * download) instead of the app navigating in place.\n *\n * @param {MouseEvent|KeyboardEvent|null|undefined} event The click event.\n * @return {boolean} True for a modifier key or a non-primary button.\n */\nexport function isModifiedClick(event) {\n\tif (!event) {\n\t\treturn false\n\t}\n\treturn Boolean(event.metaKey || event.ctrlKey || event.shiftKey || event.altKey\n\t\t|| (event.button !== undefined && event.button !== 0))\n}\n\n/**\n * The href for a navigation target. A string is returned as is, so a router\n * path must be passed as `{ path }` to get the app's base.\n *\n * @param {string|object|null|undefined} target A URL, or a vue-router location.\n * @param {object} [router] The app's router, needed for a location.\n * @return {string} The href, or '' when it cannot be resolved.\n */\nexport function resolveHref(target, router) {\n\tif (!target) {\n\t\treturn ''\n\t}\n\tif (typeof target === 'string') {\n\t\treturn target\n\t}\n\tif (!router || typeof router.resolve !== 'function') {\n\t\treturn ''\n\t}\n\ttry {\n\t\treturn router.resolve(target).href || ''\n\t} catch {\n\t\treturn ''\n\t}\n}\n\n/**\n * Click handler for a real `<a href>` that routes inside the app. A plain\n * click is routed through the router; a modified click, or one a handler\n * already prevented, is left to the browser.\n *\n * Unlike `resolveHref`, a string target is a router path, so pair the two\n * with a location object (or `{ path }`) for the href and click to agree.\n *\n * @param {MouseEvent} event The click event.\n * @param {string|object} target The router location the link points at; a string is a router path.\n * @param {object} [router] The app's router.\n * @return {boolean} True when the app navigated.\n */\nexport function followLinkClick(event, target, router) {\n\tif (!target || !router || typeof router.push !== 'function') {\n\t\treturn false\n\t}\n\tif (event && (event.defaultPrevented || isModifiedClick(event))) {\n\t\treturn false\n\t}\n\tif (event) {\n\t\tevent.preventDefault()\n\t}\n\t// vue-router rejects the promise on a blocked or duplicate navigation.\n\trouter.push(target).catch(() => {})\n\treturn true\n}\n\n/**\n * Navigate from a surface that cannot be an `<a>` (a table row, a card),\n * the way a link would: a ctrl/cmd/shift or middle click opens the target\n * in a new tab, a plain click navigates in place.\n *\n * A click that came from a control inside the surface (a button, a link, a\n * checkbox) is left to that control. Opening a new tab marks the event with\n * `markNewTabHandled`, and an event already marked is skipped, so two\n * listeners on the same click never open two tabs.\n *\n * @param {MouseEvent|KeyboardEvent|null|undefined} event The click event.\n * @param {string|object} target A URL, or a vue-router location.\n * @param {object} [router] The app's router, needed for a location.\n * @return {boolean} True when it navigated.\n */\nexport function openRowTarget(event, target, router) {\n\tif (!target || isNewTabHandled(event) || isFromNestedControl(event)) {\n\t\treturn false\n\t}\n\tif (isNewTabClick(event)) {\n\t\tconst href = resolveHref(target, router)\n\t\tif (!href) {\n\t\t\treturn false\n\t\t}\n\t\twindow.open(href, '_blank', 'noopener,noreferrer')\n\t\tmarkNewTabHandled(event, true)\n\t\treturn true\n\t}\n\t// Only the primary button navigates in place; a right click opens the menu.\n\tif (event && event.button !== undefined && event.button !== 0) {\n\t\treturn false\n\t}\n\tif (typeof target === 'string') {\n\t\twindow.location.assign(target)\n\t\treturn true\n\t}\n\tif (!router || typeof router.push !== 'function') {\n\t\treturn false\n\t}\n\trouter.push(target).catch(() => {})\n\treturn true\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ * SPDX-License-Identifier: EUPL-1.2
4
+ *
5
+ * readPath: read a dot-path off an object.
6
+ *
7
+ * Its own module, with no imports, so the rules that read a record's fields
8
+ * (stage, checklist, due date) do not hang on a module a host or a test may
9
+ * replace.
10
+ *
11
+ * @module utils/readPath
12
+ */
13
+
14
+ /**
15
+ * Read a dot-path (`'a.b.c'`) off an object.
16
+ *
17
+ * @param {object|null} data The source object.
18
+ * @param {string} [path] Dot-path into the object. Empty returns the object itself.
19
+ * @return {unknown} The value at the path, or undefined when a segment is missing.
20
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-primary-action-follows-the-stage
21
+ */
22
+ function readPath(data, path) {
23
+ if (!path) {
24
+ return data
25
+ }
26
+ return String(path).split('.').reduce((value, key) => (value === null || value === undefined ? value : value[key]), data)
27
+ }
28
+
29
+ export { readPath };
30
+ //# sourceMappingURL=readPath.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readPath.js","sources":["../../../src/utils/readPath.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * readPath: read a dot-path off an object.\n *\n * Its own module, with no imports, so the rules that read a record's fields\n * (stage, checklist, due date) do not hang on a module a host or a test may\n * replace.\n *\n * @module utils/readPath\n */\n\n/**\n * Read a dot-path (`'a.b.c'`) off an object.\n *\n * @param {object|null} data The source object.\n * @param {string} [path] Dot-path into the object. Empty returns the object itself.\n * @return {unknown} The value at the path, or undefined when a segment is missing.\n * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-primary-action-follows-the-stage\n */\nexport function readPath(data, path) {\n\tif (!path) {\n\t\treturn data\n\t}\n\treturn String(path).split('.').reduce((value, key) => (value === null || value === undefined ? value : value[key]), data)\n}\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE;AACrC,CAAC,IAAI,CAAC,IAAI,EAAE;AACZ,EAAE,OAAO,IAAI;AACb,EAAE;AACF,CAAC,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,GAAG,MAAM,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,GAAG,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC;AAC1H;;;;"}
@@ -9,13 +9,17 @@
9
9
  * also fire `auxclick` for the right button, so a row that listens to it must
10
10
  * filter both out itself.
11
11
  *
12
- * Whoever opens a row in a new tab calls `preventDefault()` on the event, so a
13
- * second listener for the same click (a host behind a library default) can
12
+ * Whoever opens a row in a new tab marks the event with `markNewTabHandled`, so
13
+ * a second listener for the same click (a host behind a library default) can
14
14
  * check `isNewTabHandled` and not open a second tab.
15
15
  *
16
16
  * @module utils/rowAuxClick
17
17
  */
18
18
 
19
+ // Events that opened a new tab. Not `defaultPrevented`, which any unrelated
20
+ // handler can set on a modified or middle click.
21
+ const newTabHandled = new WeakSet();
22
+
19
23
  const NESTED_CONTROL = 'a, button, input, select, textarea, label, summary, [role="button"], [role="checkbox"], [role="link"], [contenteditable="true"]';
20
24
 
21
25
  /**
@@ -49,6 +53,21 @@ function isRowMiddleClick(event) {
49
53
  return Boolean(event && event.button === 1 && !isFromNestedControl(event))
50
54
  }
51
55
 
56
+ /**
57
+ * Cancel a middle-button press on a clickable row or card body, so the
58
+ * browser does not start autoscroll (Chrome/Edge on Windows) instead of the
59
+ * middle click opening the row. A press on a nested control, a link
60
+ * included, keeps its default.
61
+ *
62
+ * @param {MouseEvent} event The mousedown event.
63
+ * @return {void}
64
+ */
65
+ function preventMiddleClickAutoscroll(event) {
66
+ if (event && event.button === 1 && !isFromNestedControl(event) && typeof event.preventDefault === 'function') {
67
+ event.preventDefault();
68
+ }
69
+ }
70
+
52
71
  /**
53
72
  * Whether `openRowTarget` would open this click in a new tab.
54
73
  *
@@ -66,21 +85,27 @@ function isNewTabClick(event) {
66
85
  * @return {boolean}
67
86
  */
68
87
  function isNewTabHandled(event) {
69
- return Boolean(isNewTabClick(event) && event.defaultPrevented)
88
+ return Boolean(isNewTabClick(event) && newTabHandled.has(event))
70
89
  }
71
90
 
72
91
  /**
73
92
  * Mark a new-tab click as handled after opening it, see `isNewTabHandled`.
93
+ * Also prevents the event's default, so the browser does not act on the
94
+ * click as well.
74
95
  *
75
96
  * @param {MouseEvent|KeyboardEvent|null|undefined} event The click event.
76
97
  * @param {boolean} opened Whether the target was opened.
77
98
  * @return {void}
78
99
  */
79
100
  function markNewTabHandled(event, opened) {
80
- if (opened && isNewTabClick(event) && typeof event.preventDefault === 'function') {
101
+ if (!opened || !isNewTabClick(event)) {
102
+ return
103
+ }
104
+ newTabHandled.add(event);
105
+ if (typeof event.preventDefault === 'function') {
81
106
  event.preventDefault();
82
107
  }
83
108
  }
84
109
 
85
- export { isFromNestedControl, isNewTabClick, isNewTabHandled, isRowMiddleClick, markNewTabHandled };
110
+ export { isFromNestedControl, isNewTabClick, isNewTabHandled, isRowMiddleClick, markNewTabHandled, preventMiddleClickAutoscroll };
86
111
  //# sourceMappingURL=rowAuxClick.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"rowAuxClick.js","sources":["../../../src/utils/rowAuxClick.js"],"sourcesContent":["/**\n * SPDX-License-Identifier: EUPL-1.2\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n *\n * rowAuxClick — decide whether an `auxclick` on a clickable row or card is a\n * middle click meant for the row itself.\n *\n * `@click.stop` on a nested control does not stop `auxclick`, and browsers\n * also fire `auxclick` for the right button, so a row that listens to it must\n * filter both out itself.\n *\n * Whoever opens a row in a new tab calls `preventDefault()` on the event, so a\n * second listener for the same click (a host behind a library default) can\n * check `isNewTabHandled` and not open a second tab.\n *\n * @module utils/rowAuxClick\n */\n\nconst NESTED_CONTROL = 'a, button, input, select, textarea, label, summary, [role=\"button\"], [role=\"checkbox\"], [role=\"link\"], [contenteditable=\"true\"]'\n\n/**\n * Whether the event started on a control nested inside the element that\n * listens for it (not the listening element itself).\n *\n * @param {Event} event The click or auxclick event.\n * @return {boolean}\n */\nexport function isFromNestedControl(event) {\n\tconst target = event && event.target\n\tconst root = event && event.currentTarget\n\tif (!target || typeof target.closest !== 'function') {\n\t\treturn false\n\t}\n\tconst control = target.closest(NESTED_CONTROL)\n\tif (!control || control === root) {\n\t\treturn false\n\t}\n\treturn !root || typeof root.contains !== 'function' || root.contains(control)\n}\n\n/**\n * Whether an `auxclick` is a middle click on the row body, which should open\n * the row's target in a new tab.\n *\n * @param {MouseEvent} event The auxclick event.\n * @return {boolean}\n */\nexport function isRowMiddleClick(event) {\n\treturn Boolean(event && event.button === 1 && !isFromNestedControl(event))\n}\n\n/**\n * Whether `openRowTarget` would open this click in a new tab.\n *\n * @param {MouseEvent|KeyboardEvent|null|undefined} event The click event.\n * @return {boolean}\n */\nexport function isNewTabClick(event) {\n\treturn Boolean(event && (event.metaKey || event.ctrlKey || event.shiftKey || event.button === 1))\n}\n\n/**\n * Whether an earlier listener already opened this click in a new tab.\n *\n * @param {MouseEvent|KeyboardEvent|null|undefined} event The click event.\n * @return {boolean}\n */\nexport function isNewTabHandled(event) {\n\treturn Boolean(isNewTabClick(event) && event.defaultPrevented)\n}\n\n/**\n * Mark a new-tab click as handled after opening it, see `isNewTabHandled`.\n *\n * @param {MouseEvent|KeyboardEvent|null|undefined} event The click event.\n * @param {boolean} opened Whether the target was opened.\n * @return {void}\n */\nexport function markNewTabHandled(event, opened) {\n\tif (opened && isNewTabClick(event) && typeof event.preventDefault === 'function') {\n\t\tevent.preventDefault()\n\t}\n}\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM,cAAc,GAAG,kIAAiI;AACxJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,mBAAmB,CAAC,KAAK,EAAE;AAC3C,CAAC,MAAM,MAAM,GAAG,KAAK,IAAI,KAAK,CAAC,OAAM;AACrC,CAAC,MAAM,IAAI,GAAG,KAAK,IAAI,KAAK,CAAC,cAAa;AAC1C,CAAC,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,CAAC,OAAO,KAAK,UAAU,EAAE;AACtD,EAAE,OAAO,KAAK;AACd,EAAE;AACF,CAAC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,cAAc,EAAC;AAC/C,CAAC,IAAI,CAAC,OAAO,IAAI,OAAO,KAAK,IAAI,EAAE;AACnC,EAAE,OAAO,KAAK;AACd,EAAE;AACF,CAAC,OAAO,CAAC,IAAI,IAAI,OAAO,IAAI,CAAC,QAAQ,KAAK,UAAU,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC;AAC9E,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,gBAAgB,CAAC,KAAK,EAAE;AACxC,CAAC,OAAO,OAAO,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC;AAC3E,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,aAAa,CAAC,KAAK,EAAE;AACrC,CAAC,OAAO,OAAO,CAAC,KAAK,KAAK,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC;AAClG,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,eAAe,CAAC,KAAK,EAAE;AACvC,CAAC,OAAO,OAAO,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,gBAAgB,CAAC;AAC/D,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE;AACjD,CAAC,IAAI,MAAM,IAAI,aAAa,CAAC,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,cAAc,KAAK,UAAU,EAAE;AACnF,EAAE,KAAK,CAAC,cAAc,GAAE;AACxB,EAAE;AACF;;;;"}
1
+ {"version":3,"file":"rowAuxClick.js","sources":["../../../src/utils/rowAuxClick.js"],"sourcesContent":["/**\n * SPDX-License-Identifier: EUPL-1.2\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n *\n * rowAuxClick — decide whether an `auxclick` on a clickable row or card is a\n * middle click meant for the row itself.\n *\n * `@click.stop` on a nested control does not stop `auxclick`, and browsers\n * also fire `auxclick` for the right button, so a row that listens to it must\n * filter both out itself.\n *\n * Whoever opens a row in a new tab marks the event with `markNewTabHandled`, so\n * a second listener for the same click (a host behind a library default) can\n * check `isNewTabHandled` and not open a second tab.\n *\n * @module utils/rowAuxClick\n */\n\n// Events that opened a new tab. Not `defaultPrevented`, which any unrelated\n// handler can set on a modified or middle click.\nconst newTabHandled = new WeakSet()\n\nconst NESTED_CONTROL = 'a, button, input, select, textarea, label, summary, [role=\"button\"], [role=\"checkbox\"], [role=\"link\"], [contenteditable=\"true\"]'\n\n/**\n * Whether the event started on a control nested inside the element that\n * listens for it (not the listening element itself).\n *\n * @param {Event} event The click or auxclick event.\n * @return {boolean}\n */\nexport function isFromNestedControl(event) {\n\tconst target = event && event.target\n\tconst root = event && event.currentTarget\n\tif (!target || typeof target.closest !== 'function') {\n\t\treturn false\n\t}\n\tconst control = target.closest(NESTED_CONTROL)\n\tif (!control || control === root) {\n\t\treturn false\n\t}\n\treturn !root || typeof root.contains !== 'function' || root.contains(control)\n}\n\n/**\n * Whether an `auxclick` is a middle click on the row body, which should open\n * the row's target in a new tab.\n *\n * @param {MouseEvent} event The auxclick event.\n * @return {boolean}\n */\nexport function isRowMiddleClick(event) {\n\treturn Boolean(event && event.button === 1 && !isFromNestedControl(event))\n}\n\n/**\n * Cancel a middle-button press on a clickable row or card body, so the\n * browser does not start autoscroll (Chrome/Edge on Windows) instead of the\n * middle click opening the row. A press on a nested control, a link\n * included, keeps its default.\n *\n * @param {MouseEvent} event The mousedown event.\n * @return {void}\n */\nexport function preventMiddleClickAutoscroll(event) {\n\tif (event && event.button === 1 && !isFromNestedControl(event) && typeof event.preventDefault === 'function') {\n\t\tevent.preventDefault()\n\t}\n}\n\n/**\n * Whether `openRowTarget` would open this click in a new tab.\n *\n * @param {MouseEvent|KeyboardEvent|null|undefined} event The click event.\n * @return {boolean}\n */\nexport function isNewTabClick(event) {\n\treturn Boolean(event && (event.metaKey || event.ctrlKey || event.shiftKey || event.button === 1))\n}\n\n/**\n * Whether an earlier listener already opened this click in a new tab.\n *\n * @param {MouseEvent|KeyboardEvent|null|undefined} event The click event.\n * @return {boolean}\n */\nexport function isNewTabHandled(event) {\n\treturn Boolean(isNewTabClick(event) && newTabHandled.has(event))\n}\n\n/**\n * Mark a new-tab click as handled after opening it, see `isNewTabHandled`.\n * Also prevents the event's default, so the browser does not act on the\n * click as well.\n *\n * @param {MouseEvent|KeyboardEvent|null|undefined} event The click event.\n * @param {boolean} opened Whether the target was opened.\n * @return {void}\n */\nexport function markNewTabHandled(event, opened) {\n\tif (!opened || !isNewTabClick(event)) {\n\t\treturn\n\t}\n\tnewTabHandled.add(event)\n\tif (typeof event.preventDefault === 'function') {\n\t\tevent.preventDefault()\n\t}\n}\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM,aAAa,GAAG,IAAI,OAAO,GAAE;AACnC;AACA,MAAM,cAAc,GAAG,kIAAiI;AACxJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,mBAAmB,CAAC,KAAK,EAAE;AAC3C,CAAC,MAAM,MAAM,GAAG,KAAK,IAAI,KAAK,CAAC,OAAM;AACrC,CAAC,MAAM,IAAI,GAAG,KAAK,IAAI,KAAK,CAAC,cAAa;AAC1C,CAAC,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,CAAC,OAAO,KAAK,UAAU,EAAE;AACtD,EAAE,OAAO,KAAK;AACd,EAAE;AACF,CAAC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,cAAc,EAAC;AAC/C,CAAC,IAAI,CAAC,OAAO,IAAI,OAAO,KAAK,IAAI,EAAE;AACnC,EAAE,OAAO,KAAK;AACd,EAAE;AACF,CAAC,OAAO,CAAC,IAAI,IAAI,OAAO,IAAI,CAAC,QAAQ,KAAK,UAAU,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC;AAC9E,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,gBAAgB,CAAC,KAAK,EAAE;AACxC,CAAC,OAAO,OAAO,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC;AAC3E,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,4BAA4B,CAAC,KAAK,EAAE;AACpD,CAAC,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,cAAc,KAAK,UAAU,EAAE;AAC/G,EAAE,KAAK,CAAC,cAAc,GAAE;AACxB,EAAE;AACF,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,aAAa,CAAC,KAAK,EAAE;AACrC,CAAC,OAAO,OAAO,CAAC,KAAK,KAAK,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC;AAClG,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,eAAe,CAAC,KAAK,EAAE;AACvC,CAAC,OAAO,OAAO,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AACjE,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE;AACjD,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE;AACvC,EAAE,MAAM;AACR,EAAE;AACF,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,EAAC;AACzB,CAAC,IAAI,OAAO,KAAK,CAAC,cAAc,KAAK,UAAU,EAAE;AACjD,EAAE,KAAK,CAAC,cAAc,GAAE;AACxB,EAAE;AACF;;;;"}
@@ -18,6 +18,20 @@
18
18
  * @module utils/safeHref
19
19
  */
20
20
 
21
+ /**
22
+ * Whether a URL is protocol-relative the way a browser reads it: leading
23
+ * control characters and spaces are dropped, tabs and newlines are ignored
24
+ * anywhere, and a backslash counts as `/`.
25
+ *
26
+ * @param {string} url The URL.
27
+ * @return {boolean}
28
+ */
29
+ function isProtocolRelative(url) {
30
+ // eslint-disable-next-line no-control-regex
31
+ const normalized = url.replace(/[\t\n\r]/g, '').replace(/^[\u0000-\u0020]+/, '');
32
+ return /^[/\\]{2}/.test(normalized)
33
+ }
34
+
21
35
  /**
22
36
  * Validate a URL and return it only if the scheme is safe for `:href`.
23
37
  *
@@ -30,7 +44,8 @@
30
44
  * - `javascript:...`
31
45
  * - `data:...`
32
46
  * - `vbscript:...`
33
- * - Protocol-relative `//attacker.com/...` (ambiguous origin)
47
+ * - Protocol-relative `//attacker.com/...` (ambiguous origin), also as
48
+ * `/\attacker.com` or with leading whitespace
34
49
  * - Any other unrecognised scheme
35
50
  * - `null`, `undefined`, empty string
36
51
  *
@@ -52,7 +67,7 @@ function safeHref(url) {
52
67
  }
53
68
  // Reject protocol-relative URLs — `//attacker.com/x` looks like a relative
54
69
  // path but resolves to an arbitrary origin, bypassing same-origin intent.
55
- if (url.startsWith('//')) {
70
+ if (isProtocolRelative(url)) {
56
71
  return '#'
57
72
  }
58
73
  // Allow root-relative paths (same-origin navigation)
@@ -97,7 +112,7 @@ function safeImageSrc(url) {
97
112
  return url
98
113
  }
99
114
  // Reject protocol-relative
100
- if (url.startsWith('//')) {
115
+ if (isProtocolRelative(url)) {
101
116
  return ''
102
117
  }
103
118
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"safeHref.js","sources":["../../../src/utils/safeHref.js"],"sourcesContent":["/**\n * SPDX-License-Identifier: EUPL-1.2\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n *\n * safeHref — URL scheme validation for `:href` bindings.\n *\n * Components that bind user-controlled URLs to `:href` attributes must\n * validate the scheme before rendering. Without this, a malicious data\n * source returning `javascript:alert(1)` or `data:text/html,...` as a\n * URL would execute code when the user clicks the link.\n *\n * This utility returns the original URL when the scheme is safe\n * (`https:`, `http:`, `mailto:`) or when the URL is a relative path\n * starting with `/`. It returns `'#'` for all other inputs including\n * `javascript:`, `data:`, `vbscript:`, protocol-relative (`//`), and any\n * other unrecognised scheme.\n *\n * @module utils/safeHref\n */\n\n/**\n * Validate a URL and return it only if the scheme is safe for `:href`.\n *\n * Safe inputs:\n * - `https://...` and `http://...` absolute URLs\n * - `mailto:...` links\n * - Relative paths starting with `/` but NOT `//` (same-origin)\n *\n * Unsafe inputs (returns `'#'`):\n * - `javascript:...`\n * - `data:...`\n * - `vbscript:...`\n * - Protocol-relative `//attacker.com/...` (ambiguous origin)\n * - Any other unrecognised scheme\n * - `null`, `undefined`, empty string\n *\n * @param {string|null|undefined} url The URL to validate.\n * @return {string} The original URL if safe, or `'#'` otherwise.\n *\n * @example\n * safeHref('https://example.com') // -> 'https://example.com'\n * safeHref('/apps/files') // -> '/apps/files'\n * safeHref('mailto:info@example.com') // -> 'mailto:info@example.com'\n * safeHref('javascript:alert(1)') // -> '#'\n * safeHref('data:text/html,<h1>x</h1>') // -> '#'\n * safeHref('//attacker.com/x') // -> '#'\n * safeHref(null) // -> '#'\n */\nexport function safeHref(url) {\n\tif (!url || typeof url !== 'string') {\n\t\treturn '#'\n\t}\n\t// Reject protocol-relative URLs — `//attacker.com/x` looks like a relative\n\t// path but resolves to an arbitrary origin, bypassing same-origin intent.\n\tif (url.startsWith('//')) {\n\t\treturn '#'\n\t}\n\t// Allow root-relative paths (same-origin navigation)\n\tif (url.startsWith('/')) {\n\t\treturn url\n\t}\n\ttry {\n\t\tconst parsed = new URL(url, window.location.origin)\n\t\tif (['https:', 'http:', 'mailto:'].includes(parsed.protocol)) {\n\t\t\treturn url\n\t\t}\n\t} catch {\n\t\t// URL constructor threw — not a valid absolute URL and not a\n\t\t// relative path either; treat as unsafe.\n\t}\n\treturn '#'\n}\n\n/**\n * Validate a URL for use in `src` attributes of `<img>` elements.\n *\n * Allows `https:`, `http:`, and safe `data:image/...` URIs for inline\n * images (PNG, JPEG, GIF, WebP). Rejects all other schemes including\n * `javascript:`, `data:text/html,...`, and protocol-relative URLs.\n *\n * @param {string|null|undefined} url The image URL to validate.\n * @return {string} The original URL if safe, or `''` otherwise.\n *\n * @example\n * safeImageSrc('https://example.com/logo.png') // -> 'https://...'\n * safeImageSrc('data:image/png;base64,iVBOR...') // -> 'data:image/...'\n * safeImageSrc('data:text/html,<script>alert(1)</script>') // -> ''\n * safeImageSrc('javascript:alert(1)') // -> ''\n * safeImageSrc(null) // -> ''\n */\nexport function safeImageSrc(url) {\n\tif (!url || typeof url !== 'string') {\n\t\treturn ''\n\t}\n\t// Allow safe inline image data URIs (png/jpeg/gif/webp only)\n\tif (/^data:image\\/(png|jpe?g|gif|webp);base64,/i.test(url)) {\n\t\treturn url\n\t}\n\t// Reject protocol-relative\n\tif (url.startsWith('//')) {\n\t\treturn ''\n\t}\n\ttry {\n\t\tconst parsed = new URL(url, window.location.origin)\n\t\tif (['https:', 'http:'].includes(parsed.protocol)) {\n\t\t\treturn url\n\t\t}\n\t} catch {\n\t\t// Not a valid URL — unsafe\n\t}\n\treturn ''\n}\n\n/**\n * SVG path `d` attribute allowlist validator.\n *\n * Restricts a path string to the characters used by SVG path commands:\n * letters (commands), digits, decimal points, commas, whitespace, and\n * the minus sign. Rejects any string containing other characters (e.g.\n * embedded script, unicode trickery).\n *\n * Returns the empty string for invalid inputs so a `:d` binding receives\n * an inert value rather than an attacker-controlled string.\n *\n * @param {string|null|undefined} pathData The SVG path `d` value.\n * @return {string} The original string if valid, or `''` otherwise.\n *\n * @example\n * safeSvgPath('M12 2 L20 20 Z') // -> 'M12 2 L20 20 Z'\n * safeSvgPath('M0,0<script>') // -> ''\n */\nexport function safeSvgPath(pathData) {\n\tif (!pathData || typeof pathData !== 'string') {\n\t\treturn ''\n\t}\n\t// Allowlist: SVG path command letters, digits, decimal points,\n\t// commas, whitespace, minus (for negative coordinates), and `e`/`E`\n\t// for scientific notation in arc radii.\n\tif (/^[MmLlHhVvCcSsQqTtAaZz0-9.,\\s\\-eE]+$/.test(pathData)) {\n\t\treturn pathData\n\t}\n\treturn ''\n}\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,QAAQ,CAAC,GAAG,EAAE;AAC9B,CAAC,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE;AACtC,EAAE,OAAO,GAAG;AACZ,EAAE;AACF;AACA;AACA,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE;AAC3B,EAAE,OAAO,GAAG;AACZ,EAAE;AACF;AACA,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE;AAC1B,EAAE,OAAO,GAAG;AACZ,EAAE;AACF,CAAC,IAAI;AACL,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAC;AACrD,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE;AAChE,GAAG,OAAO,GAAG;AACb,GAAG;AACH,EAAE,CAAC,MAAM;AACT;AACA;AACA,EAAE;AACF,CAAC,OAAO,GAAG;AACX,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,YAAY,CAAC,GAAG,EAAE;AAClC,CAAC,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE;AACtC,EAAE,OAAO,EAAE;AACX,EAAE;AACF;AACA,CAAC,IAAI,4CAA4C,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;AAC7D,EAAE,OAAO,GAAG;AACZ,EAAE;AACF;AACA,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE;AAC3B,EAAE,OAAO,EAAE;AACX,EAAE;AACF,CAAC,IAAI;AACL,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAC;AACrD,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE;AACrD,GAAG,OAAO,GAAG;AACb,GAAG;AACH,EAAE,CAAC,MAAM;AACT;AACA,EAAE;AACF,CAAC,OAAO,EAAE;AACV,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,WAAW,CAAC,QAAQ,EAAE;AACtC,CAAC,IAAI,CAAC,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE;AAChD,EAAE,OAAO,EAAE;AACX,EAAE;AACF;AACA;AACA;AACA,CAAC,IAAI,sCAAsC,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE;AAC5D,EAAE,OAAO,QAAQ;AACjB,EAAE;AACF,CAAC,OAAO,EAAE;AACV;;;;"}
1
+ {"version":3,"file":"safeHref.js","sources":["../../../src/utils/safeHref.js"],"sourcesContent":["/**\n * SPDX-License-Identifier: EUPL-1.2\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n *\n * safeHref — URL scheme validation for `:href` bindings.\n *\n * Components that bind user-controlled URLs to `:href` attributes must\n * validate the scheme before rendering. Without this, a malicious data\n * source returning `javascript:alert(1)` or `data:text/html,...` as a\n * URL would execute code when the user clicks the link.\n *\n * This utility returns the original URL when the scheme is safe\n * (`https:`, `http:`, `mailto:`) or when the URL is a relative path\n * starting with `/`. It returns `'#'` for all other inputs including\n * `javascript:`, `data:`, `vbscript:`, protocol-relative (`//`), and any\n * other unrecognised scheme.\n *\n * @module utils/safeHref\n */\n\n/**\n * Whether a URL is protocol-relative the way a browser reads it: leading\n * control characters and spaces are dropped, tabs and newlines are ignored\n * anywhere, and a backslash counts as `/`.\n *\n * @param {string} url The URL.\n * @return {boolean}\n */\nfunction isProtocolRelative(url) {\n\t// eslint-disable-next-line no-control-regex\n\tconst normalized = url.replace(/[\\t\\n\\r]/g, '').replace(/^[\\u0000-\\u0020]+/, '')\n\treturn /^[/\\\\]{2}/.test(normalized)\n}\n\n/**\n * Validate a URL and return it only if the scheme is safe for `:href`.\n *\n * Safe inputs:\n * - `https://...` and `http://...` absolute URLs\n * - `mailto:...` links\n * - Relative paths starting with `/` but NOT `//` (same-origin)\n *\n * Unsafe inputs (returns `'#'`):\n * - `javascript:...`\n * - `data:...`\n * - `vbscript:...`\n * - Protocol-relative `//attacker.com/...` (ambiguous origin), also as\n * `/\\attacker.com` or with leading whitespace\n * - Any other unrecognised scheme\n * - `null`, `undefined`, empty string\n *\n * @param {string|null|undefined} url The URL to validate.\n * @return {string} The original URL if safe, or `'#'` otherwise.\n *\n * @example\n * safeHref('https://example.com') // -> 'https://example.com'\n * safeHref('/apps/files') // -> '/apps/files'\n * safeHref('mailto:info@example.com') // -> 'mailto:info@example.com'\n * safeHref('javascript:alert(1)') // -> '#'\n * safeHref('data:text/html,<h1>x</h1>') // -> '#'\n * safeHref('//attacker.com/x') // -> '#'\n * safeHref(null) // -> '#'\n */\nexport function safeHref(url) {\n\tif (!url || typeof url !== 'string') {\n\t\treturn '#'\n\t}\n\t// Reject protocol-relative URLs — `//attacker.com/x` looks like a relative\n\t// path but resolves to an arbitrary origin, bypassing same-origin intent.\n\tif (isProtocolRelative(url)) {\n\t\treturn '#'\n\t}\n\t// Allow root-relative paths (same-origin navigation)\n\tif (url.startsWith('/')) {\n\t\treturn url\n\t}\n\ttry {\n\t\tconst parsed = new URL(url, window.location.origin)\n\t\tif (['https:', 'http:', 'mailto:'].includes(parsed.protocol)) {\n\t\t\treturn url\n\t\t}\n\t} catch {\n\t\t// URL constructor threw — not a valid absolute URL and not a\n\t\t// relative path either; treat as unsafe.\n\t}\n\treturn '#'\n}\n\n/**\n * Validate a URL for use in `src` attributes of `<img>` elements.\n *\n * Allows `https:`, `http:`, and safe `data:image/...` URIs for inline\n * images (PNG, JPEG, GIF, WebP). Rejects all other schemes including\n * `javascript:`, `data:text/html,...`, and protocol-relative URLs.\n *\n * @param {string|null|undefined} url The image URL to validate.\n * @return {string} The original URL if safe, or `''` otherwise.\n *\n * @example\n * safeImageSrc('https://example.com/logo.png') // -> 'https://...'\n * safeImageSrc('data:image/png;base64,iVBOR...') // -> 'data:image/...'\n * safeImageSrc('data:text/html,<script>alert(1)</script>') // -> ''\n * safeImageSrc('javascript:alert(1)') // -> ''\n * safeImageSrc(null) // -> ''\n */\nexport function safeImageSrc(url) {\n\tif (!url || typeof url !== 'string') {\n\t\treturn ''\n\t}\n\t// Allow safe inline image data URIs (png/jpeg/gif/webp only)\n\tif (/^data:image\\/(png|jpe?g|gif|webp);base64,/i.test(url)) {\n\t\treturn url\n\t}\n\t// Reject protocol-relative\n\tif (isProtocolRelative(url)) {\n\t\treturn ''\n\t}\n\ttry {\n\t\tconst parsed = new URL(url, window.location.origin)\n\t\tif (['https:', 'http:'].includes(parsed.protocol)) {\n\t\t\treturn url\n\t\t}\n\t} catch {\n\t\t// Not a valid URL — unsafe\n\t}\n\treturn ''\n}\n\n/**\n * SVG path `d` attribute allowlist validator.\n *\n * Restricts a path string to the characters used by SVG path commands:\n * letters (commands), digits, decimal points, commas, whitespace, and\n * the minus sign. Rejects any string containing other characters (e.g.\n * embedded script, unicode trickery).\n *\n * Returns the empty string for invalid inputs so a `:d` binding receives\n * an inert value rather than an attacker-controlled string.\n *\n * @param {string|null|undefined} pathData The SVG path `d` value.\n * @return {string} The original string if valid, or `''` otherwise.\n *\n * @example\n * safeSvgPath('M12 2 L20 20 Z') // -> 'M12 2 L20 20 Z'\n * safeSvgPath('M0,0<script>') // -> ''\n */\nexport function safeSvgPath(pathData) {\n\tif (!pathData || typeof pathData !== 'string') {\n\t\treturn ''\n\t}\n\t// Allowlist: SVG path command letters, digits, decimal points,\n\t// commas, whitespace, minus (for negative coordinates), and `e`/`E`\n\t// for scientific notation in arc radii.\n\tif (/^[MmLlHhVvCcSsQqTtAaZz0-9.,\\s\\-eE]+$/.test(pathData)) {\n\t\treturn pathData\n\t}\n\treturn ''\n}\n"],"names":[],"mappings":"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"}