@conduction/nextcloud-vue 2.56.0-beta.3 → 2.56.0-beta.5

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 (314) 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.map +1 -1
  6. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js +0 -3
  7. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
  8. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
  9. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +1 -2
  10. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
  11. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +21 -1
  12. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  13. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +1 -1
  14. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  15. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +12 -5
  16. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
  17. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +2 -1
  18. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
  19. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +9 -1
  20. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
  21. package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
  22. package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js +6 -1
  23. package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js.map +1 -1
  24. package/dist/esm/components/CnContextMenu/CnContextMenu.vue3.js.map +1 -1
  25. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +116 -15
  26. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  27. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +27 -14
  28. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  29. package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue2.js +13 -6
  30. package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue2.js.map +1 -1
  31. package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue3.js +2 -0
  32. package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue3.js.map +1 -1
  33. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +29 -1
  34. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  35. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  36. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
  37. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js +1 -2
  38. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
  39. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +3 -1
  40. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  41. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +17 -17
  42. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  43. package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue2.js +4 -21
  44. package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue2.js.map +1 -1
  45. package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue3.js.map +1 -1
  46. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js +0 -13
  47. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js.map +1 -1
  48. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js +8 -8
  49. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js.map +1 -1
  50. package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue2.js.map +1 -1
  51. package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue3.js +3 -2
  52. package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue3.js.map +1 -1
  53. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +67 -13
  54. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  55. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +15 -8
  56. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  57. package/dist/esm/components/CnIndexPage/manifestActionDispatch.js +31 -11
  58. package/dist/esm/components/CnIndexPage/manifestActionDispatch.js.map +1 -1
  59. package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue2.js.map +1 -1
  60. package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue3.js +1 -2
  61. package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue3.js.map +1 -1
  62. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js.map +1 -1
  63. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js +0 -1
  64. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js.map +1 -1
  65. package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue2.js +2 -17
  66. package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue2.js.map +1 -1
  67. package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue3.js.map +1 -1
  68. package/dist/esm/components/CnLogsPage/CnLogsPage.vue2.js +35 -11
  69. package/dist/esm/components/CnLogsPage/CnLogsPage.vue2.js.map +1 -1
  70. package/dist/esm/components/CnLogsPage/CnLogsPage.vue3.js +3 -2
  71. package/dist/esm/components/CnLogsPage/CnLogsPage.vue3.js.map +1 -1
  72. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +23 -5
  73. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
  74. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +1 -1
  75. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
  76. package/dist/esm/components/CnObjectList/CnObjectList.vue2.js +19 -3
  77. package/dist/esm/components/CnObjectList/CnObjectList.vue2.js.map +1 -1
  78. package/dist/esm/components/CnObjectList/CnObjectList.vue3.js.map +1 -1
  79. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +45 -15
  80. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  81. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +4 -2
  82. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  83. package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js +24 -6
  84. package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js.map +1 -1
  85. package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js +1 -1
  86. package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js.map +1 -1
  87. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  88. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +0 -1
  89. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  90. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js.map +1 -1
  91. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js +4 -2
  92. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js.map +1 -1
  93. package/dist/esm/components/CnRowActions/CnRowActions.vue2.js +7 -2
  94. package/dist/esm/components/CnRowActions/CnRowActions.vue2.js.map +1 -1
  95. package/dist/esm/components/CnRowActions/CnRowActions.vue3.js.map +1 -1
  96. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +49 -5
  97. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  98. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +1 -1
  99. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  100. package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue2.js +5 -3
  101. package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue2.js.map +1 -1
  102. package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue3.js.map +1 -1
  103. package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
  104. package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
  105. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +3 -1
  106. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
  107. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +1 -0
  108. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
  109. package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue2.js +3 -1
  110. package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue2.js.map +1 -1
  111. package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue3.js.map +1 -1
  112. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js +3 -1
  113. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js.map +1 -1
  114. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js +2 -4
  115. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js.map +1 -1
  116. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue2.js +6 -0
  117. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue2.js.map +1 -1
  118. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue3.js +68 -53
  119. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue3.js.map +1 -1
  120. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue2.js +17 -0
  121. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue2.js.map +1 -1
  122. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue3.js +21 -19
  123. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue3.js.map +1 -1
  124. package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue2.js +3 -1
  125. package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue2.js.map +1 -1
  126. package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue3.js.map +1 -1
  127. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue2.js +3 -1
  128. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue2.js.map +1 -1
  129. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue3.js +2 -4
  130. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue3.js.map +1 -1
  131. package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue2.js +2 -1
  132. package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue2.js.map +1 -1
  133. package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue3.js.map +1 -1
  134. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js +2 -1
  135. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js.map +1 -1
  136. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js +1 -2
  137. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js.map +1 -1
  138. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js +2 -1
  139. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js.map +1 -1
  140. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js.map +1 -1
  141. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js +2 -1
  142. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js.map +1 -1
  143. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js +1 -2
  144. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js.map +1 -1
  145. package/dist/esm/integrations/builtin/email/CnEmailCard.vue2.js.map +1 -1
  146. package/dist/esm/integrations/builtin/email/CnEmailCard.vue3.js.map +1 -1
  147. package/dist/esm/integrations/builtin/email/CnEmailTab.vue2.js.map +1 -1
  148. package/dist/esm/integrations/builtin/email/CnEmailTab.vue3.js +1 -2
  149. package/dist/esm/integrations/builtin/email/CnEmailTab.vue3.js.map +1 -1
  150. package/dist/esm/integrations/builtin/flow/CnFlowCard.vue2.js +2 -1
  151. package/dist/esm/integrations/builtin/flow/CnFlowCard.vue2.js.map +1 -1
  152. package/dist/esm/integrations/builtin/flow/CnFlowCard.vue3.js.map +1 -1
  153. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue2.js +2 -1
  154. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue2.js.map +1 -1
  155. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue3.js +1 -2
  156. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue3.js.map +1 -1
  157. package/dist/esm/integrations/builtin/forms/CnFormsCard.vue2.js +3 -1
  158. package/dist/esm/integrations/builtin/forms/CnFormsCard.vue2.js.map +1 -1
  159. package/dist/esm/integrations/builtin/forms/CnFormsCard.vue3.js.map +1 -1
  160. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue2.js +3 -1
  161. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue2.js.map +1 -1
  162. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue3.js +2 -4
  163. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue3.js.map +1 -1
  164. package/dist/esm/integrations/builtin/maps/CnMapsCard.vue2.js +3 -1
  165. package/dist/esm/integrations/builtin/maps/CnMapsCard.vue2.js.map +1 -1
  166. package/dist/esm/integrations/builtin/maps/CnMapsCard.vue3.js.map +1 -1
  167. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue2.js +3 -1
  168. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue2.js.map +1 -1
  169. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue3.js +2 -4
  170. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue3.js.map +1 -1
  171. package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue2.js +3 -1
  172. package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue2.js.map +1 -1
  173. package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue3.js.map +1 -1
  174. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue2.js +3 -1
  175. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue2.js.map +1 -1
  176. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue3.js +2 -4
  177. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue3.js.map +1 -1
  178. package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue2.js +2 -1
  179. package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue2.js.map +1 -1
  180. package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue3.js.map +1 -1
  181. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js +2 -1
  182. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js.map +1 -1
  183. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js +1 -2
  184. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js.map +1 -1
  185. package/dist/esm/integrations/builtin/polls/CnPollsCard.vue2.js +2 -1
  186. package/dist/esm/integrations/builtin/polls/CnPollsCard.vue2.js.map +1 -1
  187. package/dist/esm/integrations/builtin/polls/CnPollsCard.vue3.js.map +1 -1
  188. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue2.js +2 -1
  189. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue2.js.map +1 -1
  190. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue3.js +1 -2
  191. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue3.js.map +1 -1
  192. package/dist/esm/integrations/builtin/shares/CnSharesTab.vue2.js.map +1 -1
  193. package/dist/esm/integrations/builtin/shares/CnSharesTab.vue3.js +3 -6
  194. package/dist/esm/integrations/builtin/shares/CnSharesTab.vue3.js.map +1 -1
  195. package/dist/esm/integrations/builtin/talk/CnTalkCard.vue2.js +3 -1
  196. package/dist/esm/integrations/builtin/talk/CnTalkCard.vue2.js.map +1 -1
  197. package/dist/esm/integrations/builtin/talk/CnTalkCard.vue3.js.map +1 -1
  198. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue2.js +3 -1
  199. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue2.js.map +1 -1
  200. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue3.js +2 -4
  201. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue3.js.map +1 -1
  202. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue2.js +2 -1
  203. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue2.js.map +1 -1
  204. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue3.js.map +1 -1
  205. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue2.js +2 -1
  206. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue2.js.map +1 -1
  207. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue3.js +1 -2
  208. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue3.js.map +1 -1
  209. package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue2.js +4 -1
  210. package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue2.js.map +1 -1
  211. package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue3.js.map +1 -1
  212. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue2.js +6 -3
  213. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue2.js.map +1 -1
  214. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue3.js +2 -4
  215. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue3.js.map +1 -1
  216. package/dist/esm/l10n/en.json.js +2 -0
  217. package/dist/esm/l10n/en.json.js.map +1 -1
  218. package/dist/esm/l10n/nl.json.js +2 -0
  219. package/dist/esm/l10n/nl.json.js.map +1 -1
  220. package/dist/esm/nextcloud-vue.css +78 -7
  221. package/dist/esm/utils/actionLink.js +5 -0
  222. package/dist/esm/utils/actionLink.js.map +1 -1
  223. package/dist/esm/utils/actionsDispatcher.js +16 -1
  224. package/dist/esm/utils/actionsDispatcher.js.map +1 -1
  225. package/dist/esm/utils/linkNavigation.js +6 -3
  226. package/dist/esm/utils/linkNavigation.js.map +1 -1
  227. package/dist/esm/utils/rowAuxClick.js +30 -5
  228. package/dist/esm/utils/rowAuxClick.js.map +1 -1
  229. package/dist/esm/utils/safeHref.js +18 -3
  230. package/dist/esm/utils/safeHref.js.map +1 -1
  231. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  232. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  233. package/dist/nextcloud-vue.cjs.js +885 -393
  234. package/dist/nextcloud-vue.cjs.js.map +2 -2
  235. package/dist/nextcloud-vue.css +78 -7
  236. package/l10n/en.json +2 -0
  237. package/l10n/nl.json +2 -0
  238. package/package.json +1 -1
  239. package/src/components/CnActionsBar/CnActionsBar.vue +14 -5
  240. package/src/components/CnActionsMenu/CnActionsMenu.vue +0 -3
  241. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +1 -2
  242. package/src/components/CnAppRoot/CnAppRoot.vue +31 -2
  243. package/src/components/CnBoardView/CnBoardView.vue +13 -5
  244. package/src/components/CnCardGrid/CnCardGrid.vue +9 -1
  245. package/src/components/CnContextMenu/CnContextMenu.vue +6 -1
  246. package/src/components/CnDataTable/CnDataTable.md +1 -1
  247. package/src/components/CnDataTable/CnDataTable.vue +131 -17
  248. package/src/components/CnDateAxisView/CnDateAxisView.vue +15 -6
  249. package/src/components/CnDetailPage/CnDetailPage.md +1 -1
  250. package/src/components/CnDetailPage/CnDetailPage.vue +29 -1
  251. package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +1 -2
  252. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +5 -3
  253. package/src/components/CnFilesWidget/CnFilesWidget.vue +4 -21
  254. package/src/components/CnFlowRunsWidget/CnFlowRunsWidget.vue +8 -21
  255. package/src/components/CnFlowsPage/CnFlowsPage.vue +2 -1
  256. package/src/components/CnIndexPage/CnIndexPage.vue +75 -14
  257. package/src/components/CnIndexPage/manifestActionDispatch.js +31 -11
  258. package/src/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue +1 -2
  259. package/src/components/CnInteractionFormWidget/CnInteractionFormWidget.vue +0 -1
  260. package/src/components/CnLinkButtonWidget/CnLinkButtonWidget.vue +2 -17
  261. package/src/components/CnLogsPage/CnLogsPage.vue +37 -12
  262. package/src/components/CnObjectCard/CnObjectCard.vue +24 -6
  263. package/src/components/CnObjectList/CnObjectList.vue +19 -3
  264. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +47 -15
  265. package/src/components/CnObjectRow/CnObjectRow.vue +25 -7
  266. package/src/components/CnObjectSidebar/CnFilesTab.vue +0 -1
  267. package/src/components/CnPageRenderer/CnPageRenderer.vue +2 -0
  268. package/src/components/CnRowActions/CnRowActions.vue +7 -2
  269. package/src/components/CnSetupWizard/CnSetupWizard.vue +57 -6
  270. package/src/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue +5 -3
  271. package/src/components/CnTabs/CnTabs.vue +7 -3
  272. package/src/components/CnTasksWidget/CnTasksWidget.vue +4 -1
  273. package/src/css/table.css +26 -0
  274. package/src/integrations/builtin/analytics/CnAnalyticsCard.vue +3 -2
  275. package/src/integrations/builtin/analytics/CnAnalyticsTab.vue +5 -6
  276. package/src/integrations/builtin/bookmarks/CnBookmarksCard.vue +12 -1
  277. package/src/integrations/builtin/bookmarks/CnBookmarksTab.vue +23 -6
  278. package/src/integrations/builtin/bookmarks/__tests__/CnBookmarksTab.spec.js +7 -2
  279. package/src/integrations/builtin/collectives/CnCollectivesCard.vue +3 -2
  280. package/src/integrations/builtin/collectives/CnCollectivesTab.vue +5 -6
  281. package/src/integrations/builtin/cospend/CnCospendCard.vue +2 -2
  282. package/src/integrations/builtin/cospend/CnCospendTab.vue +3 -4
  283. package/src/integrations/builtin/deck/CnDeckCard.vue +2 -2
  284. package/src/integrations/builtin/deck/CnDeckTab.vue +3 -4
  285. package/src/integrations/builtin/email/CnEmailCard.vue +6 -2
  286. package/src/integrations/builtin/email/CnEmailTab.vue +7 -4
  287. package/src/integrations/builtin/flow/CnFlowCard.vue +2 -2
  288. package/src/integrations/builtin/flow/CnFlowTab.vue +3 -4
  289. package/src/integrations/builtin/forms/CnFormsCard.vue +3 -2
  290. package/src/integrations/builtin/forms/CnFormsTab.vue +5 -6
  291. package/src/integrations/builtin/maps/CnMapsCard.vue +3 -2
  292. package/src/integrations/builtin/maps/CnMapsTab.vue +5 -6
  293. package/src/integrations/builtin/maps/__tests__/CnMapsTab.spec.js +9 -0
  294. package/src/integrations/builtin/openproject/CnOpenprojectCard.vue +3 -2
  295. package/src/integrations/builtin/openproject/CnOpenprojectTab.vue +5 -6
  296. package/src/integrations/builtin/photos/CnPhotosCard.vue +2 -2
  297. package/src/integrations/builtin/photos/CnPhotosTab.vue +3 -4
  298. package/src/integrations/builtin/polls/CnPollsCard.vue +2 -2
  299. package/src/integrations/builtin/polls/CnPollsTab.vue +3 -4
  300. package/src/integrations/builtin/shares/CnSharesTab.vue +3 -6
  301. package/src/integrations/builtin/talk/CnTalkCard.vue +3 -2
  302. package/src/integrations/builtin/talk/CnTalkTab.vue +5 -6
  303. package/src/integrations/builtin/time-tracker/CnTimeTrackerCard.vue +2 -2
  304. package/src/integrations/builtin/time-tracker/CnTimeTrackerTab.vue +3 -4
  305. package/src/integrations/builtin/xwiki/CnXwikiCard.vue +4 -2
  306. package/src/integrations/builtin/xwiki/CnXwikiTab.vue +7 -7
  307. package/src/integrations/builtin/xwiki/__tests__/CnXwikiTab.spec.js +9 -0
  308. package/src/schemas/app-manifest-v2.schema.json +2 -1
  309. package/src/utils/actionLink.js +5 -0
  310. package/src/utils/actionsDispatcher.js +15 -0
  311. package/src/utils/linkNavigation.js +6 -3
  312. package/src/utils/rowAuxClick.js +29 -4
  313. package/src/utils/safeHref.js +18 -3
  314. package/src/utils/validateManifestV2.compiled.js +2 -2
@@ -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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"}
@@ -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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"}