@conduction/nextcloud-vue 2.85.0 → 2.87.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 (301) hide show
  1. package/dist/esm/components/CnAdvancedFormDialog/CnMetadataTab.vue.js +1 -1
  2. package/dist/esm/components/CnAdvancedFormDialog/CnMetadataTab.vue2.js +3 -2
  3. package/dist/esm/components/CnAdvancedFormDialog/CnMetadataTab.vue2.js.map +1 -1
  4. package/dist/esm/components/CnAdvancedFormDialog/CnMetadataTab.vue3.js.map +1 -1
  5. package/dist/esm/components/CnAdvancedFormDialog/CnPropertyValueCell.vue.js +1 -1
  6. package/dist/esm/components/CnAdvancedFormDialog/CnPropertyValueCell.vue2.js +5 -4
  7. package/dist/esm/components/CnAdvancedFormDialog/CnPropertyValueCell.vue2.js.map +1 -1
  8. package/dist/esm/components/CnAdvancedFormDialog/CnPropertyValueCell.vue3.js.map +1 -1
  9. package/dist/esm/components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue.js +1 -1
  10. package/dist/esm/components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue2.js +2 -1
  11. package/dist/esm/components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue2.js.map +1 -1
  12. package/dist/esm/components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue3.js.map +1 -1
  13. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue.js +1 -1
  14. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +2 -1
  15. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
  16. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js.map +1 -1
  17. package/dist/esm/components/CnBoardView/CnBoardView.vue.js +1 -1
  18. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +156 -14
  19. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
  20. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +105 -74
  21. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
  22. package/dist/esm/components/CnCalendarEventPicker/CnCalendarEventPicker.vue.js +1 -1
  23. package/dist/esm/components/CnCalendarEventPicker/CnCalendarEventPicker.vue2.js +2 -1
  24. package/dist/esm/components/CnCalendarEventPicker/CnCalendarEventPicker.vue2.js.map +1 -1
  25. package/dist/esm/components/CnCalendarEventPicker/CnCalendarEventPicker.vue3.js.map +1 -1
  26. package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue.js +1 -1
  27. package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue2.js +2 -1
  28. package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue2.js.map +1 -1
  29. package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue3.js.map +1 -1
  30. package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
  31. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +6 -5
  32. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
  33. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
  34. package/dist/esm/components/CnCollectivePagePicker/CnCollectivePagePicker.vue.js +1 -1
  35. package/dist/esm/components/CnCollectivePagePicker/CnCollectivePagePicker.vue2.js +2 -1
  36. package/dist/esm/components/CnCollectivePagePicker/CnCollectivePagePicker.vue2.js.map +1 -1
  37. package/dist/esm/components/CnCollectivePagePicker/CnCollectivePagePicker.vue3.js.map +1 -1
  38. package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +1 -1
  39. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js +2 -1
  40. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -1
  41. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -1
  42. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue.js +1 -1
  43. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js +2 -1
  44. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js.map +1 -1
  45. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue3.js.map +1 -1
  46. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  47. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +2 -1
  48. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  49. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  50. package/dist/esm/components/CnDataMatrix/CnDataMatrix.vue.js +1 -1
  51. package/dist/esm/components/CnDataMatrix/CnDataMatrix.vue2.js +3 -1
  52. package/dist/esm/components/CnDataMatrix/CnDataMatrix.vue2.js.map +1 -1
  53. package/dist/esm/components/CnDataMatrix/CnDataMatrix.vue3.js.map +1 -1
  54. package/dist/esm/components/CnDetailPage/CnDetailHeaderChips.vue2.js +2 -1
  55. package/dist/esm/components/CnDetailPage/CnDetailHeaderChips.vue2.js.map +1 -1
  56. package/dist/esm/components/CnDetailPage/CnDetailHeaderChips.vue3.js.map +1 -1
  57. package/dist/esm/components/CnEmailPicker/CnEmailPicker.vue.js +1 -1
  58. package/dist/esm/components/CnEmailPicker/CnEmailPicker.vue2.js +2 -1
  59. package/dist/esm/components/CnEmailPicker/CnEmailPicker.vue2.js.map +1 -1
  60. package/dist/esm/components/CnEmailPicker/CnEmailPicker.vue3.js.map +1 -1
  61. package/dist/esm/components/CnFederationStatus/CnFederationStatus.vue.js +1 -1
  62. package/dist/esm/components/CnFederationStatus/CnFederationStatus.vue2.js +3 -1
  63. package/dist/esm/components/CnFederationStatus/CnFederationStatus.vue2.js.map +1 -1
  64. package/dist/esm/components/CnFederationStatus/CnFederationStatus.vue3.js.map +1 -1
  65. package/dist/esm/components/CnFileManager/CnFileManager.vue.js +1 -1
  66. package/dist/esm/components/CnFileManager/CnFileManager.vue2.js +3 -1
  67. package/dist/esm/components/CnFileManager/CnFileManager.vue2.js.map +1 -1
  68. package/dist/esm/components/CnFileManager/CnFileManager.vue3.js.map +1 -1
  69. package/dist/esm/components/CnFilePreview/CnFilePreview.vue.js +1 -1
  70. package/dist/esm/components/CnFilePreview/CnFilePreview.vue2.js +2 -1
  71. package/dist/esm/components/CnFilePreview/CnFilePreview.vue2.js.map +1 -1
  72. package/dist/esm/components/CnFilePreview/CnFilePreview.vue3.js.map +1 -1
  73. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
  74. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +49 -1
  75. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  76. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +3 -3
  77. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  78. package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue.js +1 -1
  79. package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue2.js +2 -1
  80. package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue2.js.map +1 -1
  81. package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue3.js.map +1 -1
  82. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +30 -5
  83. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  84. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  85. package/dist/esm/components/CnNotesCard/CnNotesCard.vue.js +1 -1
  86. package/dist/esm/components/CnNotesCard/CnNotesCard.vue2.js +2 -1
  87. package/dist/esm/components/CnNotesCard/CnNotesCard.vue2.js.map +1 -1
  88. package/dist/esm/components/CnNotesCard/CnNotesCard.vue3.js.map +1 -1
  89. package/dist/esm/components/CnObjectCalendar/CnObjectCalendar.vue.js +1 -1
  90. package/dist/esm/components/CnObjectCalendar/CnObjectCalendar.vue2.js +3 -2
  91. package/dist/esm/components/CnObjectCalendar/CnObjectCalendar.vue2.js.map +1 -1
  92. package/dist/esm/components/CnObjectCalendar/CnObjectCalendar.vue3.js.map +1 -1
  93. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue.js +1 -1
  94. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +28 -0
  95. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
  96. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js +82 -75
  97. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
  98. package/dist/esm/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue.js +1 -1
  99. package/dist/esm/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue2.js +5 -4
  100. package/dist/esm/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue2.js.map +1 -1
  101. package/dist/esm/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue3.js.map +1 -1
  102. package/dist/esm/components/CnObjectSidebar/CnAuditTrailTab.vue.js +1 -1
  103. package/dist/esm/components/CnObjectSidebar/CnAuditTrailTab.vue2.js +2 -1
  104. package/dist/esm/components/CnObjectSidebar/CnAuditTrailTab.vue2.js.map +1 -1
  105. package/dist/esm/components/CnObjectSidebar/CnAuditTrailTab.vue3.js.map +1 -1
  106. package/dist/esm/components/CnObjectSidebar/CnFlowTasksPanel.vue.js +1 -1
  107. package/dist/esm/components/CnObjectSidebar/CnFlowTasksPanel.vue2.js +2 -1
  108. package/dist/esm/components/CnObjectSidebar/CnFlowTasksPanel.vue2.js.map +1 -1
  109. package/dist/esm/components/CnObjectSidebar/CnFlowTasksPanel.vue3.js.map +1 -1
  110. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue.js +1 -1
  111. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js +2 -1
  112. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js.map +1 -1
  113. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js.map +1 -1
  114. package/dist/esm/components/CnObjectSidebar/CnTasksTab.vue.js +1 -1
  115. package/dist/esm/components/CnObjectSidebar/CnTasksTab.vue2.js +2 -1
  116. package/dist/esm/components/CnObjectSidebar/CnTasksTab.vue2.js.map +1 -1
  117. package/dist/esm/components/CnObjectSidebar/CnTasksTab.vue3.js.map +1 -1
  118. package/dist/esm/components/CnPropertySourceField/CnPropertySourceField.vue.js +1 -1
  119. package/dist/esm/components/CnPropertySourceField/CnPropertySourceField.vue2.js +2 -1
  120. package/dist/esm/components/CnPropertySourceField/CnPropertySourceField.vue2.js.map +1 -1
  121. package/dist/esm/components/CnPropertySourceField/CnPropertySourceField.vue3.js.map +1 -1
  122. package/dist/esm/components/CnRoadmapItem/CnRoadmapItem.vue.js +1 -1
  123. package/dist/esm/components/CnRoadmapItem/CnRoadmapItem.vue2.js +2 -1
  124. package/dist/esm/components/CnRoadmapItem/CnRoadmapItem.vue2.js.map +1 -1
  125. package/dist/esm/components/CnRoadmapItem/CnRoadmapItem.vue3.js.map +1 -1
  126. package/dist/esm/components/CnSchemaFormDialog/CnSchemaFormDialog.vue2.js +2 -0
  127. package/dist/esm/components/CnSchemaFormDialog/CnSchemaFormDialog.vue2.js.map +1 -1
  128. package/dist/esm/components/CnSchemaFormDialog/CnSchemaFormDialog.vue3.js +2 -2
  129. package/dist/esm/components/CnSchemaFormDialog/CnSchemaFormDialog.vue3.js.map +1 -1
  130. package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue.js +1 -1
  131. package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js +2 -1
  132. package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js.map +1 -1
  133. package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue3.js.map +1 -1
  134. package/dist/esm/components/CnSummaryAggregates/CnSummaryAggregates.vue.js +1 -1
  135. package/dist/esm/components/CnSummaryAggregates/CnSummaryAggregates.vue2.js +2 -1
  136. package/dist/esm/components/CnSummaryAggregates/CnSummaryAggregates.vue2.js.map +1 -1
  137. package/dist/esm/components/CnSummaryAggregates/CnSummaryAggregates.vue3.js.map +1 -1
  138. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
  139. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +36 -0
  140. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  141. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +55 -40
  142. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  143. package/dist/esm/components/CnTasksCard/CnTasksCard.vue.js +1 -1
  144. package/dist/esm/components/CnTasksCard/CnTasksCard.vue2.js +2 -1
  145. package/dist/esm/components/CnTasksCard/CnTasksCard.vue2.js.map +1 -1
  146. package/dist/esm/components/CnTasksCard/CnTasksCard.vue3.js.map +1 -1
  147. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js +1 -1
  148. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js +2 -1
  149. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js.map +1 -1
  150. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js.map +1 -1
  151. package/dist/esm/components/CnTranslatedBadge/CnTranslatedBadge.vue.js +1 -1
  152. package/dist/esm/components/CnTranslatedBadge/CnTranslatedBadge.vue2.js +2 -1
  153. package/dist/esm/components/CnTranslatedBadge/CnTranslatedBadge.vue2.js.map +1 -1
  154. package/dist/esm/components/CnVersionHistory/CnVersionHistory.vue.js +1 -1
  155. package/dist/esm/components/CnVersionHistory/CnVersionHistory.vue2.js +2 -1
  156. package/dist/esm/components/CnVersionHistory/CnVersionHistory.vue2.js.map +1 -1
  157. package/dist/esm/components/CnVersionHistory/CnVersionHistory.vue3.js.map +1 -1
  158. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue.js +1 -1
  159. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +3 -26
  160. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
  161. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
  162. package/dist/esm/dialogs/CnNoteHistoryDialog.vue.js +1 -1
  163. package/dist/esm/dialogs/CnNoteHistoryDialog.vue2.js +2 -1
  164. package/dist/esm/dialogs/CnNoteHistoryDialog.vue2.js.map +1 -1
  165. package/dist/esm/dialogs/CnNoteHistoryDialog.vue3.js.map +1 -1
  166. package/dist/esm/integrations/builtin/activity/CnActivityCard.vue.js +1 -1
  167. package/dist/esm/integrations/builtin/activity/CnActivityCard.vue2.js +3 -2
  168. package/dist/esm/integrations/builtin/activity/CnActivityCard.vue2.js.map +1 -1
  169. package/dist/esm/integrations/builtin/activity/CnActivityCard.vue3.js.map +1 -1
  170. package/dist/esm/integrations/builtin/activity/CnActivityTab.vue.js +1 -1
  171. package/dist/esm/integrations/builtin/activity/CnActivityTab.vue2.js +4 -3
  172. package/dist/esm/integrations/builtin/activity/CnActivityTab.vue2.js.map +1 -1
  173. package/dist/esm/integrations/builtin/activity/CnActivityTab.vue3.js.map +1 -1
  174. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue.js +1 -1
  175. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js +3 -2
  176. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js.map +1 -1
  177. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js.map +1 -1
  178. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue.js +1 -1
  179. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue2.js +2 -1
  180. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue2.js.map +1 -1
  181. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue3.js.map +1 -1
  182. package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue.js +1 -1
  183. package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue2.js +6 -5
  184. package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue2.js.map +1 -1
  185. package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue3.js.map +1 -1
  186. package/dist/esm/integrations/builtin/contactmoment/CnContactmomentCard.vue.js +1 -1
  187. package/dist/esm/integrations/builtin/contactmoment/CnContactmomentCard.vue2.js +2 -1
  188. package/dist/esm/integrations/builtin/contactmoment/CnContactmomentCard.vue2.js.map +1 -1
  189. package/dist/esm/integrations/builtin/contactmoment/CnContactmomentCard.vue3.js.map +1 -1
  190. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue.js +1 -1
  191. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js +2 -1
  192. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js.map +1 -1
  193. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js.map +1 -1
  194. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue.js +1 -1
  195. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js +2 -1
  196. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js.map +1 -1
  197. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js.map +1 -1
  198. package/dist/esm/integrations/builtin/email/CnEmailCard.vue.js +1 -1
  199. package/dist/esm/integrations/builtin/email/CnEmailCard.vue2.js +2 -1
  200. package/dist/esm/integrations/builtin/email/CnEmailCard.vue2.js.map +1 -1
  201. package/dist/esm/integrations/builtin/email/CnEmailCard.vue3.js.map +1 -1
  202. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue.js +1 -1
  203. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js +2 -1
  204. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js.map +1 -1
  205. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue3.js.map +1 -1
  206. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionTab.vue.js +1 -1
  207. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionTab.vue2.js +2 -1
  208. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionTab.vue2.js.map +1 -1
  209. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionTab.vue3.js.map +1 -1
  210. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue.js +1 -1
  211. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js +2 -1
  212. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js.map +1 -1
  213. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js.map +1 -1
  214. package/dist/esm/l10n/en.json.js +35 -2
  215. package/dist/esm/l10n/en.json.js.map +1 -1
  216. package/dist/esm/l10n/nl.json.js +38 -2
  217. package/dist/esm/l10n/nl.json.js.map +1 -1
  218. package/dist/esm/library-style.js +1 -1
  219. package/dist/esm/nextcloud-vue.css +1068 -962
  220. package/dist/esm/store/useObjectStore.js +7 -2
  221. package/dist/esm/store/useObjectStore.js.map +1 -1
  222. package/dist/esm/utils/boardColumns.js +50 -1
  223. package/dist/esm/utils/boardColumns.js.map +1 -1
  224. package/dist/esm/utils/boardReferenceColumns.js +115 -4
  225. package/dist/esm/utils/boardReferenceColumns.js.map +1 -1
  226. package/dist/esm/utils/contentHeaderLink.js +48 -1
  227. package/dist/esm/utils/contentHeaderLink.js.map +1 -1
  228. package/dist/esm/utils/schema.js +5 -4
  229. package/dist/esm/utils/schema.js.map +1 -1
  230. package/dist/esm/utils/userLocale.js +30 -0
  231. package/dist/esm/utils/userLocale.js.map +1 -0
  232. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  233. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  234. package/dist/nextcloud-vue.cjs.js +1088 -476
  235. package/dist/nextcloud-vue.cjs.js.map +2 -2
  236. package/dist/nextcloud-vue.css +1068 -962
  237. package/l10n/en.json +35 -2
  238. package/l10n/nl.json +38 -2
  239. package/package.json +1 -1
  240. package/src/components/CnAdvancedFormDialog/CnMetadataTab.vue +3 -2
  241. package/src/components/CnAdvancedFormDialog/CnPropertyValueCell.vue +5 -4
  242. package/src/components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue +2 -1
  243. package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +2 -1
  244. package/src/components/CnBoardView/CnBoardView.vue +189 -24
  245. package/src/components/CnCalendarEventPicker/CnCalendarEventPicker.vue +2 -1
  246. package/src/components/CnCalendarWidget/CnCalendarWidget.vue +2 -1
  247. package/src/components/CnChartWidget/CnChartWidget.vue +6 -5
  248. package/src/components/CnCollectivePagePicker/CnCollectivePagePicker.vue +2 -1
  249. package/src/components/CnConversationThread/CnConversationThread.vue +2 -1
  250. package/src/components/CnCountdownWidget/CnCountdownWidget.vue +2 -1
  251. package/src/components/CnDashboardPage/CnDashboardPage.vue +2 -1
  252. package/src/components/CnDataMatrix/CnDataMatrix.vue +2 -1
  253. package/src/components/CnDetailPage/CnDetailHeaderChips.vue +2 -1
  254. package/src/components/CnEmailPicker/CnEmailPicker.vue +2 -1
  255. package/src/components/CnFederationStatus/CnFederationStatus.vue +3 -1
  256. package/src/components/CnFileManager/CnFileManager.vue +2 -1
  257. package/src/components/CnFilePreview/CnFilePreview.vue +2 -1
  258. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +54 -6
  259. package/src/components/CnFollowToggle/CnFollowToggle.vue +2 -1
  260. package/src/components/CnIndexPage/CnIndexPage.vue +30 -4
  261. package/src/components/CnNotesCard/CnNotesCard.vue +2 -1
  262. package/src/components/CnObjectCalendar/CnObjectCalendar.vue +3 -2
  263. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +36 -0
  264. package/src/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue +5 -4
  265. package/src/components/CnObjectSidebar/CnAuditTrailTab.vue +2 -1
  266. package/src/components/CnObjectSidebar/CnFlowTasksPanel.vue +2 -1
  267. package/src/components/CnObjectSidebar/CnNotesTab.vue +2 -1
  268. package/src/components/CnObjectSidebar/CnTasksTab.vue +2 -1
  269. package/src/components/CnPropertySourceField/CnPropertySourceField.vue +2 -1
  270. package/src/components/CnRoadmapItem/CnRoadmapItem.vue +2 -1
  271. package/src/components/CnSchemaFormDialog/CnSchemaFormDialog.vue +4 -2
  272. package/src/components/CnStatsBlock/CnStatsBlock.vue +2 -1
  273. package/src/components/CnSummaryAggregates/CnSummaryAggregates.vue +2 -1
  274. package/src/components/CnTabsWidget/CnTabsWidget.vue +71 -0
  275. package/src/components/CnTasksCard/CnTasksCard.vue +2 -1
  276. package/src/components/CnTimelineWidget/CnTimelineWidget.vue +2 -1
  277. package/src/components/CnTranslatedBadge/CnTranslatedBadge.vue +2 -1
  278. package/src/components/CnVersionHistory/CnVersionHistory.vue +2 -1
  279. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +3 -26
  280. package/src/css/board.css +40 -0
  281. package/src/dialogs/CnNoteHistoryDialog.vue +2 -1
  282. package/src/integrations/builtin/activity/CnActivityCard.vue +3 -2
  283. package/src/integrations/builtin/activity/CnActivityTab.vue +4 -3
  284. package/src/integrations/builtin/analytics/CnAnalyticsTab.vue +3 -2
  285. package/src/integrations/builtin/calendar/CnCalendarCard.vue +2 -1
  286. package/src/integrations/builtin/calendar/CnCalendarTab.vue +6 -5
  287. package/src/integrations/builtin/contactmoment/CnContactmomentCard.vue +2 -1
  288. package/src/integrations/builtin/cospend/CnCospendTab.vue +2 -1
  289. package/src/integrations/builtin/deck/CnDeckTab.vue +2 -1
  290. package/src/integrations/builtin/email/CnEmailCard.vue +2 -1
  291. package/src/integrations/builtin/field-inspection/CnFieldInspectionCard.vue +2 -1
  292. package/src/integrations/builtin/field-inspection/CnFieldInspectionTab.vue +2 -1
  293. package/src/integrations/builtin/photos/CnPhotosTab.vue +2 -1
  294. package/src/schemas/app-manifest-v2.schema.json +4 -1
  295. package/src/store/useObjectStore.js +7 -2
  296. package/src/utils/boardColumns.js +49 -0
  297. package/src/utils/boardReferenceColumns.js +114 -3
  298. package/src/utils/contentHeaderLink.js +47 -0
  299. package/src/utils/schema.js +5 -4
  300. package/src/utils/userLocale.js +26 -0
  301. package/src/utils/validateManifestV2.compiled.js +2 -2
@@ -2,7 +2,7 @@ import script from './CnDataMatrix.vue2.js';
2
2
  import { render } from './CnDataMatrix.vue3.js';
3
3
 
4
4
  script.render = render;
5
- script.__scopeId = "data-v-21e92577";
5
+ script.__scopeId = "data-v-b800caca";
6
6
  const __sfc_main = script;
7
7
 
8
8
  export { __sfc_main as default };
@@ -1,3 +1,5 @@
1
+ import { userLocale } from '../../utils/userLocale.js';
2
+
1
3
  /**
2
4
  * CnDataMatrix — Inline-edit data grid for matrix-shaped data
3
5
  * (rows × columns). Each cell is click-to-edit; commits emit
@@ -154,7 +156,7 @@ var script = {
154
156
  if (!Number.isFinite(n)) {
155
157
  return value
156
158
  }
157
- return n.toLocaleString()
159
+ return n.toLocaleString(userLocale())
158
160
  }
159
161
  return value
160
162
  },
@@ -1 +1 @@
1
- {"version":3,"file":"CnDataMatrix.vue2.js","sources":["../../../../src/components/CnDataMatrix/CnDataMatrix.vue"],"sourcesContent":["<template>\n\t<div class=\"cn-data-matrix\" data-testid=\"cn-data-matrix\">\n\t\t<header v-if=\"title || description\" class=\"cn-data-matrix__header\">\n\t\t\t<h3 v-if=\"title\" class=\"cn-data-matrix__title\">\n\t\t\t\t{{ title }}\n\t\t\t</h3>\n\t\t\t<p v-if=\"description\" class=\"cn-data-matrix__description\">\n\t\t\t\t{{ description }}\n\t\t\t</p>\n\t\t</header>\n\n\t\t<div class=\"cn-data-matrix__scroller\">\n\t\t\t<table class=\"cn-data-matrix__table\">\n\t\t\t\t<thead>\n\t\t\t\t\t<tr>\n\t\t\t\t\t\t<th v-if=\"rowHeader\" class=\"cn-data-matrix__row-header-corner\">\n\t\t\t\t\t\t\t{{ rowHeader }}\n\t\t\t\t\t\t</th>\n\t\t\t\t\t\t<th v-for=\"col in columns\"\n\t\t\t\t\t\t\t:key=\"col.key\"\n\t\t\t\t\t\t\tclass=\"cn-data-matrix__col-header\"\n\t\t\t\t\t\t\t:style=\"col.width ? { width: col.width } : null\">\n\t\t\t\t\t\t\t{{ col.label || col.key }}\n\t\t\t\t\t\t</th>\n\t\t\t\t\t\t<th v-if=\"showRowTotals\" class=\"cn-data-matrix__total-header\">\n\t\t\t\t\t\t\t{{ rowTotalsLabel }}\n\t\t\t\t\t\t</th>\n\t\t\t\t\t</tr>\n\t\t\t\t</thead>\n\t\t\t\t<tbody>\n\t\t\t\t\t<tr v-for=\"row in rows\"\n\t\t\t\t\t\t:key=\"row[rowIdKey]\"\n\t\t\t\t\t\tclass=\"cn-data-matrix__row\">\n\t\t\t\t\t\t<th v-if=\"rowHeader\" class=\"cn-data-matrix__row-header\" :title=\"row[rowLabelKey]\">\n\t\t\t\t\t\t\t{{ row[rowLabelKey] }}\n\t\t\t\t\t\t</th>\n\t\t\t\t\t\t<td v-for=\"col in columns\"\n\t\t\t\t\t\t\t:key=\"col.key\"\n\t\t\t\t\t\t\tclass=\"cn-data-matrix__cell\"\n\t\t\t\t\t\t\t:class=\"cellClass(row, col)\"\n\t\t\t\t\t\t\t:data-row-id=\"row[rowIdKey]\"\n\t\t\t\t\t\t\t:data-col-key=\"col.key\">\n\t\t\t\t\t\t\t<input v-if=\"isEditing(row, col)\"\n\t\t\t\t\t\t\t\tref=\"activeInput\"\n\t\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\t\tclass=\"cn-data-matrix__cell-input\"\n\t\t\t\t\t\t\t\t:value=\"cellValue(row, col)\"\n\t\t\t\t\t\t\t\t:disabled=\"readOnly || isCellReadOnly(row, col)\"\n\t\t\t\t\t\t\t\t@blur=\"commitEdit(row, col, $event.target.value)\"\n\t\t\t\t\t\t\t\t@keydown.enter=\"commitEdit(row, col, $event.target.value)\"\n\t\t\t\t\t\t\t\t@keydown.esc=\"cancelEdit\">\n\t\t\t\t\t\t\t<span v-else\n\t\t\t\t\t\t\t\tclass=\"cn-data-matrix__cell-value\"\n\t\t\t\t\t\t\t\t@click=\"startEdit(row, col)\">\n\t\t\t\t\t\t\t\t{{ formatCell(cellValue(row, col), col) }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</td>\n\t\t\t\t\t\t<td v-if=\"showRowTotals\" class=\"cn-data-matrix__total-cell\">\n\t\t\t\t\t\t\t{{ formatCell(rowTotal(row), { type: 'number' }) }}\n\t\t\t\t\t\t</td>\n\t\t\t\t\t</tr>\n\t\t\t\t\t<tr v-if=\"showColumnTotals\" class=\"cn-data-matrix__totals-row\">\n\t\t\t\t\t\t<th v-if=\"rowHeader\" class=\"cn-data-matrix__totals-label\">\n\t\t\t\t\t\t\t{{ columnTotalsLabel }}\n\t\t\t\t\t\t</th>\n\t\t\t\t\t\t<td v-for=\"col in columns\"\n\t\t\t\t\t\t\t:key=\"col.key\"\n\t\t\t\t\t\t\tclass=\"cn-data-matrix__total-cell\">\n\t\t\t\t\t\t\t{{ formatCell(columnTotal(col), { type: 'number' }) }}\n\t\t\t\t\t\t</td>\n\t\t\t\t\t\t<td v-if=\"showRowTotals\" class=\"cn-data-matrix__total-cell cn-data-matrix__grand-total\">\n\t\t\t\t\t\t\t{{ formatCell(grandTotal, { type: 'number' }) }}\n\t\t\t\t\t\t</td>\n\t\t\t\t\t</tr>\n\t\t\t\t</tbody>\n\t\t\t</table>\n\t\t</div>\n\n\t\t<p v-if=\"rows.length === 0\" class=\"cn-data-matrix__empty\">\n\t\t\t{{ emptyLabel }}\n\t\t</p>\n\t</div>\n</template>\n\n<script>\n/**\n * CnDataMatrix — Inline-edit data grid for matrix-shaped data\n * (rows × columns). Each cell is click-to-edit; commits emit\n * `@cell-edit({ rowId, colKey, value })` so the parent can persist.\n * Optional row / column / grand totals computed from the live data\n * with the column's `aggregate` setting (`sum` by default).\n *\n * Use for gradebooks, weight matrices, allocation tables, scoring\n * rubrics — the \"spreadsheet\" shape where cells are independently\n * editable values rather than a row/object form.\n *\n * Not a full spreadsheet engine — there's no formula language, no\n * cell-range references, no charts. Consumers wanting that should\n * embed a dedicated library (e.g. handsontable) and use this for\n * the simpler cases.\n *\n * ```vue\n * <CnDataMatrix\n * title=\"Cohort gradebook\"\n * row-header=\"Student\"\n * :rows=\"students\"\n * :columns=\"[\n * { key: 'midterm', label: 'Midterm', type: 'number', aggregate: 'sum' },\n * { key: 'final', label: 'Final', type: 'number', aggregate: 'sum' },\n * { key: 'notes', label: 'Notes', type: 'string', readOnly: true },\n * ]\"\n * :show-row-totals=\"true\"\n * :show-column-totals=\"true\"\n * @cell-edit=\"onCellEdit\" />\n * ```\n */\nexport default {\n\tname: 'CnDataMatrix',\n\tprops: {\n\t\t/**\n\t\t * Row records. Each record holds an `id`, an optional row\n\t\t * label, and one field per column key.\n\t\t *\n\t\t * @type {Array<object>}\n\t\t */\n\t\trows: { type: Array, default: () => [] },\n\t\t/**\n\t\t * Column declarations.\n\t\t *\n\t\t * @type {Array<{key:string,label?:string,type?:'number'|'string',readOnly?:boolean,formatter?:(value: unknown) => string,aggregate?:'sum'|'avg'|'count'|'none',width?:string}>}\n\t\t */\n\t\tcolumns: { type: Array, default: () => [] },\n\t\t/** Field on each row carrying its id. */\n\t\trowIdKey: { type: String, default: 'id' },\n\t\t/** Field on each row carrying its label (shown in the row header). */\n\t\trowLabelKey: { type: String, default: 'label' },\n\t\t/** Header for the row-label column (empty hides it). */\n\t\trowHeader: { type: String, default: '' },\n\t\t/** Optional title. */\n\t\ttitle: { type: String, default: '' },\n\t\t/** Optional description. */\n\t\tdescription: { type: String, default: '' },\n\t\t/** Empty-state text when rows[] is empty. */\n\t\temptyLabel: { type: String, default: 'No data.' },\n\t\t/** Disable all cell editing globally. */\n\t\treadOnly: { type: Boolean, default: false },\n\t\t/** Render the per-row totals column. */\n\t\tshowRowTotals: { type: Boolean, default: false },\n\t\t/** Render the per-column totals row at the bottom. */\n\t\tshowColumnTotals: { type: Boolean, default: false },\n\t\t/** Label for the row-totals column header. */\n\t\trowTotalsLabel: { type: String, default: 'Total' },\n\t\t/** Label preceding the column-totals row. */\n\t\tcolumnTotalsLabel: { type: String, default: 'Total' },\n\t},\n\n\temits: ['cell-edit'],\n\tdata() {\n\t\treturn {\n\t\t\tediting: null, // { rowId, colKey }\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Grand total (sum of every numeric cell). Shown in the\n\t\t * bottom-right corner when both totals are enabled.\n\t\t *\n\t\t * @return {number}\n\t\t */\n\t\tgrandTotal() {\n\t\t\tlet total = 0\n\t\t\tfor (const row of this.rows) {\n\t\t\t\tfor (const col of this.columns) {\n\t\t\t\t\tif (col.type === 'number') {\n\t\t\t\t\t\tconst v = Number(row[col.key])\n\t\t\t\t\t\tif (Number.isFinite(v)) {\n\t\t\t\t\t\t\ttotal += v\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn total\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Whether the given (row, col) is the active edit cell.\n\t\t *\n\t\t * @param {object} row Row record.\n\t\t * @param {object} col Column definition.\n\t\t * @return {boolean}\n\t\t */\n\t\tisEditing(row, col) {\n\t\t\treturn this.editing && this.editing.rowId === row[this.rowIdKey] && this.editing.colKey === col.key\n\t\t},\n\n\t\t/**\n\t\t * Effective read-only flag for a single cell.\n\t\t *\n\t\t * @param {object} row Row record.\n\t\t * @param {object} col Column definition.\n\t\t * @return {boolean}\n\t\t */\n\t\tisCellReadOnly(row, col) {\n\t\t\treturn Boolean(col.readOnly)\n\t\t},\n\n\t\t/**\n\t\t * Read the cell value from the row.\n\t\t *\n\t\t * @param {object} row Row record.\n\t\t * @param {object} col Column definition.\n\t\t * @return {unknown} The raw value.\n\t\t */\n\t\tcellValue(row, col) {\n\t\t\treturn row[col.key]\n\t\t},\n\n\t\t/**\n\t\t * Format a cell value for display. Number columns get a\n\t\t * locale-formatted number; others go through the column's\n\t\t * `formatter` (if any) or are returned verbatim.\n\t\t *\n\t\t * @param {unknown} value The raw value.\n\t\t * @param {object} col Column definition.\n\t\t * @return {string} The display string.\n\t\t */\n\t\tformatCell(value, col) {\n\t\t\tif (value === undefined || value === null || value === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (typeof col.formatter === 'function') {\n\t\t\t\treturn col.formatter(value)\n\t\t\t}\n\t\t\tif (col.type === 'number') {\n\t\t\t\tconst n = Number(value)\n\t\t\t\tif (!Number.isFinite(n)) {\n\t\t\t\t\treturn value\n\t\t\t\t}\n\t\t\t\treturn n.toLocaleString()\n\t\t\t}\n\t\t\treturn value\n\t\t},\n\n\t\t/**\n\t\t * BEM modifier(s) for a cell — currently only the read-only\n\t\t * flag. Consumers extend via the `formatter` if they want\n\t\t * to swap to a styled component.\n\t\t *\n\t\t * @param {object} row Row record.\n\t\t * @param {object} col Column definition.\n\t\t * @return {object} Class binding.\n\t\t */\n\t\tcellClass(row, col) {\n\t\t\treturn {\n\t\t\t\t'cn-data-matrix__cell--read-only': this.readOnly || this.isCellReadOnly(row, col),\n\t\t\t\t'cn-data-matrix__cell--number': col.type === 'number',\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Start editing a cell.\n\t\t *\n\t\t * @param {object} row Row record.\n\t\t * @param {object} col Column definition.\n\t\t * @return {void}\n\t\t */\n\t\tstartEdit(row, col) {\n\t\t\tif (this.readOnly || this.isCellReadOnly(row, col)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.editing = { rowId: row[this.rowIdKey], colKey: col.key }\n\t\t\tthis.$nextTick(() => {\n\t\t\t\tif (this.$refs.activeInput && this.$refs.activeInput[0]) {\n\t\t\t\t\tthis.$refs.activeInput[0].focus()\n\t\t\t\t\tthis.$refs.activeInput[0].select()\n\t\t\t\t} else if (this.$refs.activeInput && this.$refs.activeInput.focus) {\n\t\t\t\t\tthis.$refs.activeInput.focus()\n\t\t\t\t\tthis.$refs.activeInput.select()\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Commit an edit — coerces the value to the column type and\n\t\t * emits `@cell-edit`.\n\t\t *\n\t\t * @param {object} row Row record.\n\t\t * @param {object} col Column definition.\n\t\t * @param {string} raw The raw input string.\n\t\t * @return {void}\n\t\t */\n\t\tcommitEdit(row, col, raw) {\n\t\t\tif (!this.editing) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlet value = raw\n\t\t\tif (col.type === 'number') {\n\t\t\t\tconst n = Number(raw)\n\t\t\t\tvalue = Number.isFinite(n) ? n : null\n\t\t\t}\n\t\t\tthis.editing = null\n\t\t\t/**\n\t\t\t * @event cell-edit Emitted on commit (blur or Enter).\n\t\t\t * Payload: `{rowId, colKey, value, row}`.\n\t\t\t * @type {object}\n\t\t\t */\n\t\t\tthis.$emit('cell-edit', { rowId: row[this.rowIdKey], colKey: col.key, value, row })\n\t\t},\n\n\t\t/**\n\t\t * Cancel the active edit (Esc).\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tcancelEdit() {\n\t\t\tthis.editing = null\n\t\t},\n\n\t\t/**\n\t\t * Per-row total — sum of every numeric column.\n\t\t *\n\t\t * @param {object} row Row record.\n\t\t * @return {number}\n\t\t */\n\t\trowTotal(row) {\n\t\t\tlet total = 0\n\t\t\tfor (const col of this.columns) {\n\t\t\t\tif (col.type === 'number') {\n\t\t\t\t\tconst v = Number(row[col.key])\n\t\t\t\t\tif (Number.isFinite(v)) {\n\t\t\t\t\t\ttotal += v\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn total\n\t\t},\n\n\t\t/**\n\t\t * Per-column total — aggregate over all rows using the\n\t\t * column's `aggregate` setting (`sum` by default).\n\t\t *\n\t\t * @param {object} col Column definition.\n\t\t * @return {number}\n\t\t */\n\t\tcolumnTotal(col) {\n\t\t\tif (col.type !== 'number') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst mode = col.aggregate || 'sum'\n\t\t\tif (mode === 'none') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\t// Drop empty cells (null / undefined / '') BEFORE coercing —\n\t\t\t// `Number(null)` is `0`, which would otherwise be counted and\n\t\t\t// summed as a real zero value.\n\t\t\tconst values = this.rows\n\t\t\t\t.map((r) => r[col.key])\n\t\t\t\t.filter((v) => v !== null && v !== undefined && v !== '')\n\t\t\t\t.map((v) => Number(v))\n\t\t\t\t.filter((v) => Number.isFinite(v))\n\t\t\tif (values.length === 0) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\tif (mode === 'count') {\n\t\t\t\treturn values.length\n\t\t\t}\n\t\t\tif (mode === 'avg') {\n\t\t\t\treturn values.reduce((a, b) => a + b, 0) / values.length\n\t\t\t}\n\t\t\t// default: sum\n\t\t\treturn values.reduce((a, b) => a + b, 0)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-data-matrix {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n}\n\n.cn-data-matrix__title {\n\tmargin: 0;\n\tfont-size: 1.1em;\n}\n\n.cn-data-matrix__description {\n\tmargin: 4px 0 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-data-matrix__scroller {\n\toverflow-x: auto;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n}\n\n.cn-data-matrix__table {\n\twidth: 100%;\n\tborder-collapse: collapse;\n\tfont-size: 0.95em;\n}\n\n.cn-data-matrix__col-header,\n.cn-data-matrix__row-header-corner,\n.cn-data-matrix__total-header {\n\tbackground: var(--color-background-hover);\n\ttext-align: left;\n\tpadding: 8px 10px;\n\tfont-weight: 600;\n\tborder-bottom: 1px solid var(--color-border);\n\tborder-right: 1px solid var(--color-border);\n}\n\n.cn-data-matrix__row-header {\n\tbackground: var(--color-background-hover);\n\ttext-align: left;\n\tpadding: 6px 10px;\n\tfont-weight: 500;\n\tborder-right: 1px solid var(--color-border);\n\tborder-bottom: 1px solid var(--color-border);\n\tmax-width: 200px;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-data-matrix__cell {\n\tpadding: 0;\n\tborder-right: 1px solid var(--color-border);\n\tborder-bottom: 1px solid var(--color-border);\n\tmin-width: 80px;\n}\n\n.cn-data-matrix__cell--number {\n\ttext-align: right;\n}\n\n.cn-data-matrix__cell-value {\n\tdisplay: block;\n\tpadding: 6px 10px;\n\tcursor: cell;\n\tmin-height: 28px;\n}\n\n.cn-data-matrix__cell--read-only .cn-data-matrix__cell-value {\n\tcursor: default;\n\tbackground: var(--color-background-hover);\n}\n\n.cn-data-matrix__cell-input {\n\twidth: 100%;\n\tpadding: 5px 10px;\n\tborder: 1px solid var(--color-primary-element);\n\tbackground: var(--color-main-background);\n\toutline: none;\n\tfont: inherit;\n}\n\n.cn-data-matrix__totals-row .cn-data-matrix__total-cell,\n.cn-data-matrix__total-header {\n\tbackground: var(--color-background-darker, var(--color-background-hover));\n\tfont-weight: 600;\n\ttext-align: right;\n\tpadding: 6px 10px;\n}\n\n.cn-data-matrix__totals-label {\n\tbackground: var(--color-background-darker, var(--color-background-hover));\n\tfont-weight: 600;\n\tpadding: 6px 10px;\n\ttext-align: left;\n}\n\n.cn-data-matrix__grand-total {\n\tbackground: var(--color-primary-element-light);\n}\n\n.cn-data-matrix__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-style: italic;\n\tmargin: 16px 0;\n\ttext-align: center;\n}\n</style>\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"CnDataMatrix.vue2.js","sources":["../../../../src/components/CnDataMatrix/CnDataMatrix.vue"],"sourcesContent":["<template>\n\t<div class=\"cn-data-matrix\" data-testid=\"cn-data-matrix\">\n\t\t<header v-if=\"title || description\" class=\"cn-data-matrix__header\">\n\t\t\t<h3 v-if=\"title\" class=\"cn-data-matrix__title\">\n\t\t\t\t{{ title }}\n\t\t\t</h3>\n\t\t\t<p v-if=\"description\" class=\"cn-data-matrix__description\">\n\t\t\t\t{{ description }}\n\t\t\t</p>\n\t\t</header>\n\n\t\t<div class=\"cn-data-matrix__scroller\">\n\t\t\t<table class=\"cn-data-matrix__table\">\n\t\t\t\t<thead>\n\t\t\t\t\t<tr>\n\t\t\t\t\t\t<th v-if=\"rowHeader\" class=\"cn-data-matrix__row-header-corner\">\n\t\t\t\t\t\t\t{{ rowHeader }}\n\t\t\t\t\t\t</th>\n\t\t\t\t\t\t<th v-for=\"col in columns\"\n\t\t\t\t\t\t\t:key=\"col.key\"\n\t\t\t\t\t\t\tclass=\"cn-data-matrix__col-header\"\n\t\t\t\t\t\t\t:style=\"col.width ? { width: col.width } : null\">\n\t\t\t\t\t\t\t{{ col.label || col.key }}\n\t\t\t\t\t\t</th>\n\t\t\t\t\t\t<th v-if=\"showRowTotals\" class=\"cn-data-matrix__total-header\">\n\t\t\t\t\t\t\t{{ rowTotalsLabel }}\n\t\t\t\t\t\t</th>\n\t\t\t\t\t</tr>\n\t\t\t\t</thead>\n\t\t\t\t<tbody>\n\t\t\t\t\t<tr v-for=\"row in rows\"\n\t\t\t\t\t\t:key=\"row[rowIdKey]\"\n\t\t\t\t\t\tclass=\"cn-data-matrix__row\">\n\t\t\t\t\t\t<th v-if=\"rowHeader\" class=\"cn-data-matrix__row-header\" :title=\"row[rowLabelKey]\">\n\t\t\t\t\t\t\t{{ row[rowLabelKey] }}\n\t\t\t\t\t\t</th>\n\t\t\t\t\t\t<td v-for=\"col in columns\"\n\t\t\t\t\t\t\t:key=\"col.key\"\n\t\t\t\t\t\t\tclass=\"cn-data-matrix__cell\"\n\t\t\t\t\t\t\t:class=\"cellClass(row, col)\"\n\t\t\t\t\t\t\t:data-row-id=\"row[rowIdKey]\"\n\t\t\t\t\t\t\t:data-col-key=\"col.key\">\n\t\t\t\t\t\t\t<input v-if=\"isEditing(row, col)\"\n\t\t\t\t\t\t\t\tref=\"activeInput\"\n\t\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\t\tclass=\"cn-data-matrix__cell-input\"\n\t\t\t\t\t\t\t\t:value=\"cellValue(row, col)\"\n\t\t\t\t\t\t\t\t:disabled=\"readOnly || isCellReadOnly(row, col)\"\n\t\t\t\t\t\t\t\t@blur=\"commitEdit(row, col, $event.target.value)\"\n\t\t\t\t\t\t\t\t@keydown.enter=\"commitEdit(row, col, $event.target.value)\"\n\t\t\t\t\t\t\t\t@keydown.esc=\"cancelEdit\">\n\t\t\t\t\t\t\t<span v-else\n\t\t\t\t\t\t\t\tclass=\"cn-data-matrix__cell-value\"\n\t\t\t\t\t\t\t\t@click=\"startEdit(row, col)\">\n\t\t\t\t\t\t\t\t{{ formatCell(cellValue(row, col), col) }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</td>\n\t\t\t\t\t\t<td v-if=\"showRowTotals\" class=\"cn-data-matrix__total-cell\">\n\t\t\t\t\t\t\t{{ formatCell(rowTotal(row), { type: 'number' }) }}\n\t\t\t\t\t\t</td>\n\t\t\t\t\t</tr>\n\t\t\t\t\t<tr v-if=\"showColumnTotals\" class=\"cn-data-matrix__totals-row\">\n\t\t\t\t\t\t<th v-if=\"rowHeader\" class=\"cn-data-matrix__totals-label\">\n\t\t\t\t\t\t\t{{ columnTotalsLabel }}\n\t\t\t\t\t\t</th>\n\t\t\t\t\t\t<td v-for=\"col in columns\"\n\t\t\t\t\t\t\t:key=\"col.key\"\n\t\t\t\t\t\t\tclass=\"cn-data-matrix__total-cell\">\n\t\t\t\t\t\t\t{{ formatCell(columnTotal(col), { type: 'number' }) }}\n\t\t\t\t\t\t</td>\n\t\t\t\t\t\t<td v-if=\"showRowTotals\" class=\"cn-data-matrix__total-cell cn-data-matrix__grand-total\">\n\t\t\t\t\t\t\t{{ formatCell(grandTotal, { type: 'number' }) }}\n\t\t\t\t\t\t</td>\n\t\t\t\t\t</tr>\n\t\t\t\t</tbody>\n\t\t\t</table>\n\t\t</div>\n\n\t\t<p v-if=\"rows.length === 0\" class=\"cn-data-matrix__empty\">\n\t\t\t{{ emptyLabel }}\n\t\t</p>\n\t</div>\n</template>\n\n<script>\nimport { userLocale } from '../../utils/userLocale.js'\n/**\n * CnDataMatrix — Inline-edit data grid for matrix-shaped data\n * (rows × columns). Each cell is click-to-edit; commits emit\n * `@cell-edit({ rowId, colKey, value })` so the parent can persist.\n * Optional row / column / grand totals computed from the live data\n * with the column's `aggregate` setting (`sum` by default).\n *\n * Use for gradebooks, weight matrices, allocation tables, scoring\n * rubrics — the \"spreadsheet\" shape where cells are independently\n * editable values rather than a row/object form.\n *\n * Not a full spreadsheet engine — there's no formula language, no\n * cell-range references, no charts. Consumers wanting that should\n * embed a dedicated library (e.g. handsontable) and use this for\n * the simpler cases.\n *\n * ```vue\n * <CnDataMatrix\n * title=\"Cohort gradebook\"\n * row-header=\"Student\"\n * :rows=\"students\"\n * :columns=\"[\n * { key: 'midterm', label: 'Midterm', type: 'number', aggregate: 'sum' },\n * { key: 'final', label: 'Final', type: 'number', aggregate: 'sum' },\n * { key: 'notes', label: 'Notes', type: 'string', readOnly: true },\n * ]\"\n * :show-row-totals=\"true\"\n * :show-column-totals=\"true\"\n * @cell-edit=\"onCellEdit\" />\n * ```\n */\nexport default {\n\tname: 'CnDataMatrix',\n\tprops: {\n\t\t/**\n\t\t * Row records. Each record holds an `id`, an optional row\n\t\t * label, and one field per column key.\n\t\t *\n\t\t * @type {Array<object>}\n\t\t */\n\t\trows: { type: Array, default: () => [] },\n\t\t/**\n\t\t * Column declarations.\n\t\t *\n\t\t * @type {Array<{key:string,label?:string,type?:'number'|'string',readOnly?:boolean,formatter?:(value: unknown) => string,aggregate?:'sum'|'avg'|'count'|'none',width?:string}>}\n\t\t */\n\t\tcolumns: { type: Array, default: () => [] },\n\t\t/** Field on each row carrying its id. */\n\t\trowIdKey: { type: String, default: 'id' },\n\t\t/** Field on each row carrying its label (shown in the row header). */\n\t\trowLabelKey: { type: String, default: 'label' },\n\t\t/** Header for the row-label column (empty hides it). */\n\t\trowHeader: { type: String, default: '' },\n\t\t/** Optional title. */\n\t\ttitle: { type: String, default: '' },\n\t\t/** Optional description. */\n\t\tdescription: { type: String, default: '' },\n\t\t/** Empty-state text when rows[] is empty. */\n\t\temptyLabel: { type: String, default: 'No data.' },\n\t\t/** Disable all cell editing globally. */\n\t\treadOnly: { type: Boolean, default: false },\n\t\t/** Render the per-row totals column. */\n\t\tshowRowTotals: { type: Boolean, default: false },\n\t\t/** Render the per-column totals row at the bottom. */\n\t\tshowColumnTotals: { type: Boolean, default: false },\n\t\t/** Label for the row-totals column header. */\n\t\trowTotalsLabel: { type: String, default: 'Total' },\n\t\t/** Label preceding the column-totals row. */\n\t\tcolumnTotalsLabel: { type: String, default: 'Total' },\n\t},\n\n\temits: ['cell-edit'],\n\tdata() {\n\t\treturn {\n\t\t\tediting: null, // { rowId, colKey }\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Grand total (sum of every numeric cell). Shown in the\n\t\t * bottom-right corner when both totals are enabled.\n\t\t *\n\t\t * @return {number}\n\t\t */\n\t\tgrandTotal() {\n\t\t\tlet total = 0\n\t\t\tfor (const row of this.rows) {\n\t\t\t\tfor (const col of this.columns) {\n\t\t\t\t\tif (col.type === 'number') {\n\t\t\t\t\t\tconst v = Number(row[col.key])\n\t\t\t\t\t\tif (Number.isFinite(v)) {\n\t\t\t\t\t\t\ttotal += v\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn total\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Whether the given (row, col) is the active edit cell.\n\t\t *\n\t\t * @param {object} row Row record.\n\t\t * @param {object} col Column definition.\n\t\t * @return {boolean}\n\t\t */\n\t\tisEditing(row, col) {\n\t\t\treturn this.editing && this.editing.rowId === row[this.rowIdKey] && this.editing.colKey === col.key\n\t\t},\n\n\t\t/**\n\t\t * Effective read-only flag for a single cell.\n\t\t *\n\t\t * @param {object} row Row record.\n\t\t * @param {object} col Column definition.\n\t\t * @return {boolean}\n\t\t */\n\t\tisCellReadOnly(row, col) {\n\t\t\treturn Boolean(col.readOnly)\n\t\t},\n\n\t\t/**\n\t\t * Read the cell value from the row.\n\t\t *\n\t\t * @param {object} row Row record.\n\t\t * @param {object} col Column definition.\n\t\t * @return {unknown} The raw value.\n\t\t */\n\t\tcellValue(row, col) {\n\t\t\treturn row[col.key]\n\t\t},\n\n\t\t/**\n\t\t * Format a cell value for display. Number columns get a\n\t\t * locale-formatted number; others go through the column's\n\t\t * `formatter` (if any) or are returned verbatim.\n\t\t *\n\t\t * @param {unknown} value The raw value.\n\t\t * @param {object} col Column definition.\n\t\t * @return {string} The display string.\n\t\t */\n\t\tformatCell(value, col) {\n\t\t\tif (value === undefined || value === null || value === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (typeof col.formatter === 'function') {\n\t\t\t\treturn col.formatter(value)\n\t\t\t}\n\t\t\tif (col.type === 'number') {\n\t\t\t\tconst n = Number(value)\n\t\t\t\tif (!Number.isFinite(n)) {\n\t\t\t\t\treturn value\n\t\t\t\t}\n\t\t\t\treturn n.toLocaleString(userLocale())\n\t\t\t}\n\t\t\treturn value\n\t\t},\n\n\t\t/**\n\t\t * BEM modifier(s) for a cell — currently only the read-only\n\t\t * flag. Consumers extend via the `formatter` if they want\n\t\t * to swap to a styled component.\n\t\t *\n\t\t * @param {object} row Row record.\n\t\t * @param {object} col Column definition.\n\t\t * @return {object} Class binding.\n\t\t */\n\t\tcellClass(row, col) {\n\t\t\treturn {\n\t\t\t\t'cn-data-matrix__cell--read-only': this.readOnly || this.isCellReadOnly(row, col),\n\t\t\t\t'cn-data-matrix__cell--number': col.type === 'number',\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Start editing a cell.\n\t\t *\n\t\t * @param {object} row Row record.\n\t\t * @param {object} col Column definition.\n\t\t * @return {void}\n\t\t */\n\t\tstartEdit(row, col) {\n\t\t\tif (this.readOnly || this.isCellReadOnly(row, col)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.editing = { rowId: row[this.rowIdKey], colKey: col.key }\n\t\t\tthis.$nextTick(() => {\n\t\t\t\tif (this.$refs.activeInput && this.$refs.activeInput[0]) {\n\t\t\t\t\tthis.$refs.activeInput[0].focus()\n\t\t\t\t\tthis.$refs.activeInput[0].select()\n\t\t\t\t} else if (this.$refs.activeInput && this.$refs.activeInput.focus) {\n\t\t\t\t\tthis.$refs.activeInput.focus()\n\t\t\t\t\tthis.$refs.activeInput.select()\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Commit an edit — coerces the value to the column type and\n\t\t * emits `@cell-edit`.\n\t\t *\n\t\t * @param {object} row Row record.\n\t\t * @param {object} col Column definition.\n\t\t * @param {string} raw The raw input string.\n\t\t * @return {void}\n\t\t */\n\t\tcommitEdit(row, col, raw) {\n\t\t\tif (!this.editing) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlet value = raw\n\t\t\tif (col.type === 'number') {\n\t\t\t\tconst n = Number(raw)\n\t\t\t\tvalue = Number.isFinite(n) ? n : null\n\t\t\t}\n\t\t\tthis.editing = null\n\t\t\t/**\n\t\t\t * @event cell-edit Emitted on commit (blur or Enter).\n\t\t\t * Payload: `{rowId, colKey, value, row}`.\n\t\t\t * @type {object}\n\t\t\t */\n\t\t\tthis.$emit('cell-edit', { rowId: row[this.rowIdKey], colKey: col.key, value, row })\n\t\t},\n\n\t\t/**\n\t\t * Cancel the active edit (Esc).\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tcancelEdit() {\n\t\t\tthis.editing = null\n\t\t},\n\n\t\t/**\n\t\t * Per-row total — sum of every numeric column.\n\t\t *\n\t\t * @param {object} row Row record.\n\t\t * @return {number}\n\t\t */\n\t\trowTotal(row) {\n\t\t\tlet total = 0\n\t\t\tfor (const col of this.columns) {\n\t\t\t\tif (col.type === 'number') {\n\t\t\t\t\tconst v = Number(row[col.key])\n\t\t\t\t\tif (Number.isFinite(v)) {\n\t\t\t\t\t\ttotal += v\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn total\n\t\t},\n\n\t\t/**\n\t\t * Per-column total — aggregate over all rows using the\n\t\t * column's `aggregate` setting (`sum` by default).\n\t\t *\n\t\t * @param {object} col Column definition.\n\t\t * @return {number}\n\t\t */\n\t\tcolumnTotal(col) {\n\t\t\tif (col.type !== 'number') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst mode = col.aggregate || 'sum'\n\t\t\tif (mode === 'none') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\t// Drop empty cells (null / undefined / '') BEFORE coercing —\n\t\t\t// `Number(null)` is `0`, which would otherwise be counted and\n\t\t\t// summed as a real zero value.\n\t\t\tconst values = this.rows\n\t\t\t\t.map((r) => r[col.key])\n\t\t\t\t.filter((v) => v !== null && v !== undefined && v !== '')\n\t\t\t\t.map((v) => Number(v))\n\t\t\t\t.filter((v) => Number.isFinite(v))\n\t\t\tif (values.length === 0) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\tif (mode === 'count') {\n\t\t\t\treturn values.length\n\t\t\t}\n\t\t\tif (mode === 'avg') {\n\t\t\t\treturn values.reduce((a, b) => a + b, 0) / values.length\n\t\t\t}\n\t\t\t// default: sum\n\t\t\treturn values.reduce((a, b) => a + b, 0)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-data-matrix {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n}\n\n.cn-data-matrix__title {\n\tmargin: 0;\n\tfont-size: 1.1em;\n}\n\n.cn-data-matrix__description {\n\tmargin: 4px 0 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-data-matrix__scroller {\n\toverflow-x: auto;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n}\n\n.cn-data-matrix__table {\n\twidth: 100%;\n\tborder-collapse: collapse;\n\tfont-size: 0.95em;\n}\n\n.cn-data-matrix__col-header,\n.cn-data-matrix__row-header-corner,\n.cn-data-matrix__total-header {\n\tbackground: var(--color-background-hover);\n\ttext-align: left;\n\tpadding: 8px 10px;\n\tfont-weight: 600;\n\tborder-bottom: 1px solid var(--color-border);\n\tborder-right: 1px solid var(--color-border);\n}\n\n.cn-data-matrix__row-header {\n\tbackground: var(--color-background-hover);\n\ttext-align: left;\n\tpadding: 6px 10px;\n\tfont-weight: 500;\n\tborder-right: 1px solid var(--color-border);\n\tborder-bottom: 1px solid var(--color-border);\n\tmax-width: 200px;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-data-matrix__cell {\n\tpadding: 0;\n\tborder-right: 1px solid var(--color-border);\n\tborder-bottom: 1px solid var(--color-border);\n\tmin-width: 80px;\n}\n\n.cn-data-matrix__cell--number {\n\ttext-align: right;\n}\n\n.cn-data-matrix__cell-value {\n\tdisplay: block;\n\tpadding: 6px 10px;\n\tcursor: cell;\n\tmin-height: 28px;\n}\n\n.cn-data-matrix__cell--read-only .cn-data-matrix__cell-value {\n\tcursor: default;\n\tbackground: var(--color-background-hover);\n}\n\n.cn-data-matrix__cell-input {\n\twidth: 100%;\n\tpadding: 5px 10px;\n\tborder: 1px solid var(--color-primary-element);\n\tbackground: var(--color-main-background);\n\toutline: none;\n\tfont: inherit;\n}\n\n.cn-data-matrix__totals-row .cn-data-matrix__total-cell,\n.cn-data-matrix__total-header {\n\tbackground: var(--color-background-darker, var(--color-background-hover));\n\tfont-weight: 600;\n\ttext-align: right;\n\tpadding: 6px 10px;\n}\n\n.cn-data-matrix__totals-label {\n\tbackground: var(--color-background-darker, var(--color-background-hover));\n\tfont-weight: 600;\n\tpadding: 6px 10px;\n\ttext-align: left;\n}\n\n.cn-data-matrix__grand-total {\n\tbackground: var(--color-primary-element-light);\n}\n\n.cn-data-matrix__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-style: italic;\n\tmargin: 16px 0;\n\ttext-align: center;\n}\n</style>\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CnDataMatrix.vue3.js","sources":["../../../../src/components/CnDataMatrix/CnDataMatrix.vue?vue&type=template&id=21e92577&lang.js"],"sourcesContent":["<template>\n\t<div class=\"cn-data-matrix\" data-testid=\"cn-data-matrix\">\n\t\t<header v-if=\"title || description\" class=\"cn-data-matrix__header\">\n\t\t\t<h3 v-if=\"title\" class=\"cn-data-matrix__title\">\n\t\t\t\t{{ title }}\n\t\t\t</h3>\n\t\t\t<p v-if=\"description\" class=\"cn-data-matrix__description\">\n\t\t\t\t{{ description }}\n\t\t\t</p>\n\t\t</header>\n\n\t\t<div class=\"cn-data-matrix__scroller\">\n\t\t\t<table class=\"cn-data-matrix__table\">\n\t\t\t\t<thead>\n\t\t\t\t\t<tr>\n\t\t\t\t\t\t<th v-if=\"rowHeader\" class=\"cn-data-matrix__row-header-corner\">\n\t\t\t\t\t\t\t{{ rowHeader }}\n\t\t\t\t\t\t</th>\n\t\t\t\t\t\t<th v-for=\"col in columns\"\n\t\t\t\t\t\t\t:key=\"col.key\"\n\t\t\t\t\t\t\tclass=\"cn-data-matrix__col-header\"\n\t\t\t\t\t\t\t:style=\"col.width ? { width: col.width } : null\">\n\t\t\t\t\t\t\t{{ col.label || col.key }}\n\t\t\t\t\t\t</th>\n\t\t\t\t\t\t<th v-if=\"showRowTotals\" class=\"cn-data-matrix__total-header\">\n\t\t\t\t\t\t\t{{ rowTotalsLabel }}\n\t\t\t\t\t\t</th>\n\t\t\t\t\t</tr>\n\t\t\t\t</thead>\n\t\t\t\t<tbody>\n\t\t\t\t\t<tr v-for=\"row in rows\"\n\t\t\t\t\t\t:key=\"row[rowIdKey]\"\n\t\t\t\t\t\tclass=\"cn-data-matrix__row\">\n\t\t\t\t\t\t<th v-if=\"rowHeader\" class=\"cn-data-matrix__row-header\" :title=\"row[rowLabelKey]\">\n\t\t\t\t\t\t\t{{ row[rowLabelKey] }}\n\t\t\t\t\t\t</th>\n\t\t\t\t\t\t<td v-for=\"col in columns\"\n\t\t\t\t\t\t\t:key=\"col.key\"\n\t\t\t\t\t\t\tclass=\"cn-data-matrix__cell\"\n\t\t\t\t\t\t\t:class=\"cellClass(row, col)\"\n\t\t\t\t\t\t\t:data-row-id=\"row[rowIdKey]\"\n\t\t\t\t\t\t\t:data-col-key=\"col.key\">\n\t\t\t\t\t\t\t<input v-if=\"isEditing(row, col)\"\n\t\t\t\t\t\t\t\tref=\"activeInput\"\n\t\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\t\tclass=\"cn-data-matrix__cell-input\"\n\t\t\t\t\t\t\t\t:value=\"cellValue(row, col)\"\n\t\t\t\t\t\t\t\t:disabled=\"readOnly || isCellReadOnly(row, col)\"\n\t\t\t\t\t\t\t\t@blur=\"commitEdit(row, col, $event.target.value)\"\n\t\t\t\t\t\t\t\t@keydown.enter=\"commitEdit(row, col, $event.target.value)\"\n\t\t\t\t\t\t\t\t@keydown.esc=\"cancelEdit\">\n\t\t\t\t\t\t\t<span v-else\n\t\t\t\t\t\t\t\tclass=\"cn-data-matrix__cell-value\"\n\t\t\t\t\t\t\t\t@click=\"startEdit(row, col)\">\n\t\t\t\t\t\t\t\t{{ formatCell(cellValue(row, col), col) }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</td>\n\t\t\t\t\t\t<td v-if=\"showRowTotals\" class=\"cn-data-matrix__total-cell\">\n\t\t\t\t\t\t\t{{ formatCell(rowTotal(row), { type: 'number' }) }}\n\t\t\t\t\t\t</td>\n\t\t\t\t\t</tr>\n\t\t\t\t\t<tr v-if=\"showColumnTotals\" class=\"cn-data-matrix__totals-row\">\n\t\t\t\t\t\t<th v-if=\"rowHeader\" class=\"cn-data-matrix__totals-label\">\n\t\t\t\t\t\t\t{{ columnTotalsLabel }}\n\t\t\t\t\t\t</th>\n\t\t\t\t\t\t<td v-for=\"col in columns\"\n\t\t\t\t\t\t\t:key=\"col.key\"\n\t\t\t\t\t\t\tclass=\"cn-data-matrix__total-cell\">\n\t\t\t\t\t\t\t{{ formatCell(columnTotal(col), { type: 'number' }) }}\n\t\t\t\t\t\t</td>\n\t\t\t\t\t\t<td v-if=\"showRowTotals\" class=\"cn-data-matrix__total-cell cn-data-matrix__grand-total\">\n\t\t\t\t\t\t\t{{ formatCell(grandTotal, { type: 'number' }) }}\n\t\t\t\t\t\t</td>\n\t\t\t\t\t</tr>\n\t\t\t\t</tbody>\n\t\t\t</table>\n\t\t</div>\n\n\t\t<p v-if=\"rows.length === 0\" class=\"cn-data-matrix__empty\">\n\t\t\t{{ emptyLabel }}\n\t\t</p>\n\t</div>\n</template>\n\n<script>\n/**\n * CnDataMatrix — Inline-edit data grid for matrix-shaped data\n * (rows × columns). Each cell is click-to-edit; commits emit\n * `@cell-edit({ rowId, colKey, value })` so the parent can persist.\n * Optional row / column / grand totals computed from the live data\n * with the column's `aggregate` setting (`sum` by default).\n *\n * Use for gradebooks, weight matrices, allocation tables, scoring\n * rubrics — the \"spreadsheet\" shape where cells are independently\n * editable values rather than a row/object form.\n *\n * Not a full spreadsheet engine — there's no formula language, no\n * cell-range references, no charts. Consumers wanting that should\n * embed a dedicated library (e.g. handsontable) and use this for\n * the simpler cases.\n *\n * ```vue\n * <CnDataMatrix\n * title=\"Cohort gradebook\"\n * row-header=\"Student\"\n * :rows=\"students\"\n * :columns=\"[\n * { key: 'midterm', label: 'Midterm', type: 'number', aggregate: 'sum' },\n * { key: 'final', label: 'Final', type: 'number', aggregate: 'sum' },\n * { key: 'notes', label: 'Notes', type: 'string', readOnly: true },\n * ]\"\n * :show-row-totals=\"true\"\n * :show-column-totals=\"true\"\n * @cell-edit=\"onCellEdit\" />\n * ```\n */\nexport default {\n\tname: 'CnDataMatrix',\n\tprops: {\n\t\t/**\n\t\t * Row records. Each record holds an `id`, an optional row\n\t\t * label, and one field per column key.\n\t\t *\n\t\t * @type {Array<object>}\n\t\t */\n\t\trows: { type: Array, default: () => [] },\n\t\t/**\n\t\t * Column declarations.\n\t\t *\n\t\t * @type {Array<{key:string,label?:string,type?:'number'|'string',readOnly?:boolean,formatter?:(value: unknown) => string,aggregate?:'sum'|'avg'|'count'|'none',width?:string}>}\n\t\t */\n\t\tcolumns: { type: Array, default: () => [] },\n\t\t/** Field on each row carrying its id. */\n\t\trowIdKey: { type: String, default: 'id' },\n\t\t/** Field on each row carrying its label (shown in the row header). */\n\t\trowLabelKey: { type: String, default: 'label' },\n\t\t/** Header for the row-label column (empty hides it). */\n\t\trowHeader: { type: String, default: '' },\n\t\t/** Optional title. */\n\t\ttitle: { type: String, default: '' },\n\t\t/** Optional description. */\n\t\tdescription: { type: String, default: '' },\n\t\t/** Empty-state text when rows[] is empty. */\n\t\temptyLabel: { type: String, default: 'No data.' },\n\t\t/** Disable all cell editing globally. */\n\t\treadOnly: { type: Boolean, default: false },\n\t\t/** Render the per-row totals column. */\n\t\tshowRowTotals: { type: Boolean, default: false },\n\t\t/** Render the per-column totals row at the bottom. */\n\t\tshowColumnTotals: { type: Boolean, default: false },\n\t\t/** Label for the row-totals column header. */\n\t\trowTotalsLabel: { type: String, default: 'Total' },\n\t\t/** Label preceding the column-totals row. */\n\t\tcolumnTotalsLabel: { type: String, default: 'Total' },\n\t},\n\n\temits: ['cell-edit'],\n\tdata() {\n\t\treturn {\n\t\t\tediting: null, // { rowId, colKey }\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Grand total (sum of every numeric cell). Shown in the\n\t\t * bottom-right corner when both totals are enabled.\n\t\t *\n\t\t * @return {number}\n\t\t */\n\t\tgrandTotal() {\n\t\t\tlet total = 0\n\t\t\tfor (const row of this.rows) {\n\t\t\t\tfor (const col of this.columns) {\n\t\t\t\t\tif (col.type === 'number') {\n\t\t\t\t\t\tconst v = Number(row[col.key])\n\t\t\t\t\t\tif (Number.isFinite(v)) {\n\t\t\t\t\t\t\ttotal += v\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn total\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Whether the given (row, col) is the active edit cell.\n\t\t *\n\t\t * @param {object} row Row record.\n\t\t * @param {object} col Column definition.\n\t\t * @return {boolean}\n\t\t */\n\t\tisEditing(row, col) {\n\t\t\treturn this.editing && this.editing.rowId === row[this.rowIdKey] && this.editing.colKey === col.key\n\t\t},\n\n\t\t/**\n\t\t * Effective read-only flag for a single cell.\n\t\t *\n\t\t * @param {object} row Row record.\n\t\t * @param {object} col Column definition.\n\t\t * @return {boolean}\n\t\t */\n\t\tisCellReadOnly(row, col) {\n\t\t\treturn Boolean(col.readOnly)\n\t\t},\n\n\t\t/**\n\t\t * Read the cell value from the row.\n\t\t *\n\t\t * @param {object} row Row record.\n\t\t * @param {object} col Column definition.\n\t\t * @return {unknown} The raw value.\n\t\t */\n\t\tcellValue(row, col) {\n\t\t\treturn row[col.key]\n\t\t},\n\n\t\t/**\n\t\t * Format a cell value for display. Number columns get a\n\t\t * locale-formatted number; others go through the column's\n\t\t * `formatter` (if any) or are returned verbatim.\n\t\t *\n\t\t * @param {unknown} value The raw value.\n\t\t * @param {object} col Column definition.\n\t\t * @return {string} The display string.\n\t\t */\n\t\tformatCell(value, col) {\n\t\t\tif (value === undefined || value === null || value === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (typeof col.formatter === 'function') {\n\t\t\t\treturn col.formatter(value)\n\t\t\t}\n\t\t\tif (col.type === 'number') {\n\t\t\t\tconst n = Number(value)\n\t\t\t\tif (!Number.isFinite(n)) {\n\t\t\t\t\treturn value\n\t\t\t\t}\n\t\t\t\treturn n.toLocaleString()\n\t\t\t}\n\t\t\treturn value\n\t\t},\n\n\t\t/**\n\t\t * BEM modifier(s) for a cell — currently only the read-only\n\t\t * flag. Consumers extend via the `formatter` if they want\n\t\t * to swap to a styled component.\n\t\t *\n\t\t * @param {object} row Row record.\n\t\t * @param {object} col Column definition.\n\t\t * @return {object} Class binding.\n\t\t */\n\t\tcellClass(row, col) {\n\t\t\treturn {\n\t\t\t\t'cn-data-matrix__cell--read-only': this.readOnly || this.isCellReadOnly(row, col),\n\t\t\t\t'cn-data-matrix__cell--number': col.type === 'number',\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Start editing a cell.\n\t\t *\n\t\t * @param {object} row Row record.\n\t\t * @param {object} col Column definition.\n\t\t * @return {void}\n\t\t */\n\t\tstartEdit(row, col) {\n\t\t\tif (this.readOnly || this.isCellReadOnly(row, col)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.editing = { rowId: row[this.rowIdKey], colKey: col.key }\n\t\t\tthis.$nextTick(() => {\n\t\t\t\tif (this.$refs.activeInput && this.$refs.activeInput[0]) {\n\t\t\t\t\tthis.$refs.activeInput[0].focus()\n\t\t\t\t\tthis.$refs.activeInput[0].select()\n\t\t\t\t} else if (this.$refs.activeInput && this.$refs.activeInput.focus) {\n\t\t\t\t\tthis.$refs.activeInput.focus()\n\t\t\t\t\tthis.$refs.activeInput.select()\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Commit an edit — coerces the value to the column type and\n\t\t * emits `@cell-edit`.\n\t\t *\n\t\t * @param {object} row Row record.\n\t\t * @param {object} col Column definition.\n\t\t * @param {string} raw The raw input string.\n\t\t * @return {void}\n\t\t */\n\t\tcommitEdit(row, col, raw) {\n\t\t\tif (!this.editing) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlet value = raw\n\t\t\tif (col.type === 'number') {\n\t\t\t\tconst n = Number(raw)\n\t\t\t\tvalue = Number.isFinite(n) ? n : null\n\t\t\t}\n\t\t\tthis.editing = null\n\t\t\t/**\n\t\t\t * @event cell-edit Emitted on commit (blur or Enter).\n\t\t\t * Payload: `{rowId, colKey, value, row}`.\n\t\t\t * @type {object}\n\t\t\t */\n\t\t\tthis.$emit('cell-edit', { rowId: row[this.rowIdKey], colKey: col.key, value, row })\n\t\t},\n\n\t\t/**\n\t\t * Cancel the active edit (Esc).\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tcancelEdit() {\n\t\t\tthis.editing = null\n\t\t},\n\n\t\t/**\n\t\t * Per-row total — sum of every numeric column.\n\t\t *\n\t\t * @param {object} row Row record.\n\t\t * @return {number}\n\t\t */\n\t\trowTotal(row) {\n\t\t\tlet total = 0\n\t\t\tfor (const col of this.columns) {\n\t\t\t\tif (col.type === 'number') {\n\t\t\t\t\tconst v = Number(row[col.key])\n\t\t\t\t\tif (Number.isFinite(v)) {\n\t\t\t\t\t\ttotal += v\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn total\n\t\t},\n\n\t\t/**\n\t\t * Per-column total — aggregate over all rows using the\n\t\t * column's `aggregate` setting (`sum` by default).\n\t\t *\n\t\t * @param {object} col Column definition.\n\t\t * @return {number}\n\t\t */\n\t\tcolumnTotal(col) {\n\t\t\tif (col.type !== 'number') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst mode = col.aggregate || 'sum'\n\t\t\tif (mode === 'none') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\t// Drop empty cells (null / undefined / '') BEFORE coercing —\n\t\t\t// `Number(null)` is `0`, which would otherwise be counted and\n\t\t\t// summed as a real zero value.\n\t\t\tconst values = this.rows\n\t\t\t\t.map((r) => r[col.key])\n\t\t\t\t.filter((v) => v !== null && v !== undefined && v !== '')\n\t\t\t\t.map((v) => Number(v))\n\t\t\t\t.filter((v) => Number.isFinite(v))\n\t\t\tif (values.length === 0) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\tif (mode === 'count') {\n\t\t\t\treturn values.length\n\t\t\t}\n\t\t\tif (mode === 'avg') {\n\t\t\t\treturn values.reduce((a, b) => a + b, 0) / values.length\n\t\t\t}\n\t\t\t// default: sum\n\t\t\treturn values.reduce((a, b) => a + b, 0)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-data-matrix {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n}\n\n.cn-data-matrix__title {\n\tmargin: 0;\n\tfont-size: 1.1em;\n}\n\n.cn-data-matrix__description {\n\tmargin: 4px 0 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-data-matrix__scroller {\n\toverflow-x: auto;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n}\n\n.cn-data-matrix__table {\n\twidth: 100%;\n\tborder-collapse: collapse;\n\tfont-size: 0.95em;\n}\n\n.cn-data-matrix__col-header,\n.cn-data-matrix__row-header-corner,\n.cn-data-matrix__total-header {\n\tbackground: var(--color-background-hover);\n\ttext-align: left;\n\tpadding: 8px 10px;\n\tfont-weight: 600;\n\tborder-bottom: 1px solid var(--color-border);\n\tborder-right: 1px solid var(--color-border);\n}\n\n.cn-data-matrix__row-header {\n\tbackground: var(--color-background-hover);\n\ttext-align: left;\n\tpadding: 6px 10px;\n\tfont-weight: 500;\n\tborder-right: 1px solid var(--color-border);\n\tborder-bottom: 1px solid var(--color-border);\n\tmax-width: 200px;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-data-matrix__cell {\n\tpadding: 0;\n\tborder-right: 1px solid var(--color-border);\n\tborder-bottom: 1px solid var(--color-border);\n\tmin-width: 80px;\n}\n\n.cn-data-matrix__cell--number {\n\ttext-align: right;\n}\n\n.cn-data-matrix__cell-value {\n\tdisplay: block;\n\tpadding: 6px 10px;\n\tcursor: cell;\n\tmin-height: 28px;\n}\n\n.cn-data-matrix__cell--read-only .cn-data-matrix__cell-value {\n\tcursor: default;\n\tbackground: var(--color-background-hover);\n}\n\n.cn-data-matrix__cell-input {\n\twidth: 100%;\n\tpadding: 5px 10px;\n\tborder: 1px solid var(--color-primary-element);\n\tbackground: var(--color-main-background);\n\toutline: none;\n\tfont: inherit;\n}\n\n.cn-data-matrix__totals-row .cn-data-matrix__total-cell,\n.cn-data-matrix__total-header {\n\tbackground: var(--color-background-darker, var(--color-background-hover));\n\tfont-weight: 600;\n\ttext-align: right;\n\tpadding: 6px 10px;\n}\n\n.cn-data-matrix__totals-label {\n\tbackground: var(--color-background-darker, var(--color-background-hover));\n\tfont-weight: 600;\n\tpadding: 6px 10px;\n\ttext-align: left;\n}\n\n.cn-data-matrix__grand-total {\n\tbackground: var(--color-primary-element-light);\n}\n\n.cn-data-matrix__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-style: italic;\n\tmargin: 16px 0;\n\ttext-align: center;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_toDisplayString","_createElementVNode","_Fragment","_renderList"],"mappings":"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"}
1
+ {"version":3,"file":"CnDataMatrix.vue3.js","sources":["../../../../src/components/CnDataMatrix/CnDataMatrix.vue?vue&type=template&id=b800caca&lang.js"],"sourcesContent":["<template>\n\t<div class=\"cn-data-matrix\" data-testid=\"cn-data-matrix\">\n\t\t<header v-if=\"title || description\" class=\"cn-data-matrix__header\">\n\t\t\t<h3 v-if=\"title\" class=\"cn-data-matrix__title\">\n\t\t\t\t{{ title }}\n\t\t\t</h3>\n\t\t\t<p v-if=\"description\" class=\"cn-data-matrix__description\">\n\t\t\t\t{{ description }}\n\t\t\t</p>\n\t\t</header>\n\n\t\t<div class=\"cn-data-matrix__scroller\">\n\t\t\t<table class=\"cn-data-matrix__table\">\n\t\t\t\t<thead>\n\t\t\t\t\t<tr>\n\t\t\t\t\t\t<th v-if=\"rowHeader\" class=\"cn-data-matrix__row-header-corner\">\n\t\t\t\t\t\t\t{{ rowHeader }}\n\t\t\t\t\t\t</th>\n\t\t\t\t\t\t<th v-for=\"col in columns\"\n\t\t\t\t\t\t\t:key=\"col.key\"\n\t\t\t\t\t\t\tclass=\"cn-data-matrix__col-header\"\n\t\t\t\t\t\t\t:style=\"col.width ? { width: col.width } : null\">\n\t\t\t\t\t\t\t{{ col.label || col.key }}\n\t\t\t\t\t\t</th>\n\t\t\t\t\t\t<th v-if=\"showRowTotals\" class=\"cn-data-matrix__total-header\">\n\t\t\t\t\t\t\t{{ rowTotalsLabel }}\n\t\t\t\t\t\t</th>\n\t\t\t\t\t</tr>\n\t\t\t\t</thead>\n\t\t\t\t<tbody>\n\t\t\t\t\t<tr v-for=\"row in rows\"\n\t\t\t\t\t\t:key=\"row[rowIdKey]\"\n\t\t\t\t\t\tclass=\"cn-data-matrix__row\">\n\t\t\t\t\t\t<th v-if=\"rowHeader\" class=\"cn-data-matrix__row-header\" :title=\"row[rowLabelKey]\">\n\t\t\t\t\t\t\t{{ row[rowLabelKey] }}\n\t\t\t\t\t\t</th>\n\t\t\t\t\t\t<td v-for=\"col in columns\"\n\t\t\t\t\t\t\t:key=\"col.key\"\n\t\t\t\t\t\t\tclass=\"cn-data-matrix__cell\"\n\t\t\t\t\t\t\t:class=\"cellClass(row, col)\"\n\t\t\t\t\t\t\t:data-row-id=\"row[rowIdKey]\"\n\t\t\t\t\t\t\t:data-col-key=\"col.key\">\n\t\t\t\t\t\t\t<input v-if=\"isEditing(row, col)\"\n\t\t\t\t\t\t\t\tref=\"activeInput\"\n\t\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\t\tclass=\"cn-data-matrix__cell-input\"\n\t\t\t\t\t\t\t\t:value=\"cellValue(row, col)\"\n\t\t\t\t\t\t\t\t:disabled=\"readOnly || isCellReadOnly(row, col)\"\n\t\t\t\t\t\t\t\t@blur=\"commitEdit(row, col, $event.target.value)\"\n\t\t\t\t\t\t\t\t@keydown.enter=\"commitEdit(row, col, $event.target.value)\"\n\t\t\t\t\t\t\t\t@keydown.esc=\"cancelEdit\">\n\t\t\t\t\t\t\t<span v-else\n\t\t\t\t\t\t\t\tclass=\"cn-data-matrix__cell-value\"\n\t\t\t\t\t\t\t\t@click=\"startEdit(row, col)\">\n\t\t\t\t\t\t\t\t{{ formatCell(cellValue(row, col), col) }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</td>\n\t\t\t\t\t\t<td v-if=\"showRowTotals\" class=\"cn-data-matrix__total-cell\">\n\t\t\t\t\t\t\t{{ formatCell(rowTotal(row), { type: 'number' }) }}\n\t\t\t\t\t\t</td>\n\t\t\t\t\t</tr>\n\t\t\t\t\t<tr v-if=\"showColumnTotals\" class=\"cn-data-matrix__totals-row\">\n\t\t\t\t\t\t<th v-if=\"rowHeader\" class=\"cn-data-matrix__totals-label\">\n\t\t\t\t\t\t\t{{ columnTotalsLabel }}\n\t\t\t\t\t\t</th>\n\t\t\t\t\t\t<td v-for=\"col in columns\"\n\t\t\t\t\t\t\t:key=\"col.key\"\n\t\t\t\t\t\t\tclass=\"cn-data-matrix__total-cell\">\n\t\t\t\t\t\t\t{{ formatCell(columnTotal(col), { type: 'number' }) }}\n\t\t\t\t\t\t</td>\n\t\t\t\t\t\t<td v-if=\"showRowTotals\" class=\"cn-data-matrix__total-cell cn-data-matrix__grand-total\">\n\t\t\t\t\t\t\t{{ formatCell(grandTotal, { type: 'number' }) }}\n\t\t\t\t\t\t</td>\n\t\t\t\t\t</tr>\n\t\t\t\t</tbody>\n\t\t\t</table>\n\t\t</div>\n\n\t\t<p v-if=\"rows.length === 0\" class=\"cn-data-matrix__empty\">\n\t\t\t{{ emptyLabel }}\n\t\t</p>\n\t</div>\n</template>\n\n<script>\nimport { userLocale } from '../../utils/userLocale.js'\n/**\n * CnDataMatrix — Inline-edit data grid for matrix-shaped data\n * (rows × columns). Each cell is click-to-edit; commits emit\n * `@cell-edit({ rowId, colKey, value })` so the parent can persist.\n * Optional row / column / grand totals computed from the live data\n * with the column's `aggregate` setting (`sum` by default).\n *\n * Use for gradebooks, weight matrices, allocation tables, scoring\n * rubrics — the \"spreadsheet\" shape where cells are independently\n * editable values rather than a row/object form.\n *\n * Not a full spreadsheet engine — there's no formula language, no\n * cell-range references, no charts. Consumers wanting that should\n * embed a dedicated library (e.g. handsontable) and use this for\n * the simpler cases.\n *\n * ```vue\n * <CnDataMatrix\n * title=\"Cohort gradebook\"\n * row-header=\"Student\"\n * :rows=\"students\"\n * :columns=\"[\n * { key: 'midterm', label: 'Midterm', type: 'number', aggregate: 'sum' },\n * { key: 'final', label: 'Final', type: 'number', aggregate: 'sum' },\n * { key: 'notes', label: 'Notes', type: 'string', readOnly: true },\n * ]\"\n * :show-row-totals=\"true\"\n * :show-column-totals=\"true\"\n * @cell-edit=\"onCellEdit\" />\n * ```\n */\nexport default {\n\tname: 'CnDataMatrix',\n\tprops: {\n\t\t/**\n\t\t * Row records. Each record holds an `id`, an optional row\n\t\t * label, and one field per column key.\n\t\t *\n\t\t * @type {Array<object>}\n\t\t */\n\t\trows: { type: Array, default: () => [] },\n\t\t/**\n\t\t * Column declarations.\n\t\t *\n\t\t * @type {Array<{key:string,label?:string,type?:'number'|'string',readOnly?:boolean,formatter?:(value: unknown) => string,aggregate?:'sum'|'avg'|'count'|'none',width?:string}>}\n\t\t */\n\t\tcolumns: { type: Array, default: () => [] },\n\t\t/** Field on each row carrying its id. */\n\t\trowIdKey: { type: String, default: 'id' },\n\t\t/** Field on each row carrying its label (shown in the row header). */\n\t\trowLabelKey: { type: String, default: 'label' },\n\t\t/** Header for the row-label column (empty hides it). */\n\t\trowHeader: { type: String, default: '' },\n\t\t/** Optional title. */\n\t\ttitle: { type: String, default: '' },\n\t\t/** Optional description. */\n\t\tdescription: { type: String, default: '' },\n\t\t/** Empty-state text when rows[] is empty. */\n\t\temptyLabel: { type: String, default: 'No data.' },\n\t\t/** Disable all cell editing globally. */\n\t\treadOnly: { type: Boolean, default: false },\n\t\t/** Render the per-row totals column. */\n\t\tshowRowTotals: { type: Boolean, default: false },\n\t\t/** Render the per-column totals row at the bottom. */\n\t\tshowColumnTotals: { type: Boolean, default: false },\n\t\t/** Label for the row-totals column header. */\n\t\trowTotalsLabel: { type: String, default: 'Total' },\n\t\t/** Label preceding the column-totals row. */\n\t\tcolumnTotalsLabel: { type: String, default: 'Total' },\n\t},\n\n\temits: ['cell-edit'],\n\tdata() {\n\t\treturn {\n\t\t\tediting: null, // { rowId, colKey }\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Grand total (sum of every numeric cell). Shown in the\n\t\t * bottom-right corner when both totals are enabled.\n\t\t *\n\t\t * @return {number}\n\t\t */\n\t\tgrandTotal() {\n\t\t\tlet total = 0\n\t\t\tfor (const row of this.rows) {\n\t\t\t\tfor (const col of this.columns) {\n\t\t\t\t\tif (col.type === 'number') {\n\t\t\t\t\t\tconst v = Number(row[col.key])\n\t\t\t\t\t\tif (Number.isFinite(v)) {\n\t\t\t\t\t\t\ttotal += v\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn total\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Whether the given (row, col) is the active edit cell.\n\t\t *\n\t\t * @param {object} row Row record.\n\t\t * @param {object} col Column definition.\n\t\t * @return {boolean}\n\t\t */\n\t\tisEditing(row, col) {\n\t\t\treturn this.editing && this.editing.rowId === row[this.rowIdKey] && this.editing.colKey === col.key\n\t\t},\n\n\t\t/**\n\t\t * Effective read-only flag for a single cell.\n\t\t *\n\t\t * @param {object} row Row record.\n\t\t * @param {object} col Column definition.\n\t\t * @return {boolean}\n\t\t */\n\t\tisCellReadOnly(row, col) {\n\t\t\treturn Boolean(col.readOnly)\n\t\t},\n\n\t\t/**\n\t\t * Read the cell value from the row.\n\t\t *\n\t\t * @param {object} row Row record.\n\t\t * @param {object} col Column definition.\n\t\t * @return {unknown} The raw value.\n\t\t */\n\t\tcellValue(row, col) {\n\t\t\treturn row[col.key]\n\t\t},\n\n\t\t/**\n\t\t * Format a cell value for display. Number columns get a\n\t\t * locale-formatted number; others go through the column's\n\t\t * `formatter` (if any) or are returned verbatim.\n\t\t *\n\t\t * @param {unknown} value The raw value.\n\t\t * @param {object} col Column definition.\n\t\t * @return {string} The display string.\n\t\t */\n\t\tformatCell(value, col) {\n\t\t\tif (value === undefined || value === null || value === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (typeof col.formatter === 'function') {\n\t\t\t\treturn col.formatter(value)\n\t\t\t}\n\t\t\tif (col.type === 'number') {\n\t\t\t\tconst n = Number(value)\n\t\t\t\tif (!Number.isFinite(n)) {\n\t\t\t\t\treturn value\n\t\t\t\t}\n\t\t\t\treturn n.toLocaleString(userLocale())\n\t\t\t}\n\t\t\treturn value\n\t\t},\n\n\t\t/**\n\t\t * BEM modifier(s) for a cell — currently only the read-only\n\t\t * flag. Consumers extend via the `formatter` if they want\n\t\t * to swap to a styled component.\n\t\t *\n\t\t * @param {object} row Row record.\n\t\t * @param {object} col Column definition.\n\t\t * @return {object} Class binding.\n\t\t */\n\t\tcellClass(row, col) {\n\t\t\treturn {\n\t\t\t\t'cn-data-matrix__cell--read-only': this.readOnly || this.isCellReadOnly(row, col),\n\t\t\t\t'cn-data-matrix__cell--number': col.type === 'number',\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Start editing a cell.\n\t\t *\n\t\t * @param {object} row Row record.\n\t\t * @param {object} col Column definition.\n\t\t * @return {void}\n\t\t */\n\t\tstartEdit(row, col) {\n\t\t\tif (this.readOnly || this.isCellReadOnly(row, col)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.editing = { rowId: row[this.rowIdKey], colKey: col.key }\n\t\t\tthis.$nextTick(() => {\n\t\t\t\tif (this.$refs.activeInput && this.$refs.activeInput[0]) {\n\t\t\t\t\tthis.$refs.activeInput[0].focus()\n\t\t\t\t\tthis.$refs.activeInput[0].select()\n\t\t\t\t} else if (this.$refs.activeInput && this.$refs.activeInput.focus) {\n\t\t\t\t\tthis.$refs.activeInput.focus()\n\t\t\t\t\tthis.$refs.activeInput.select()\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Commit an edit — coerces the value to the column type and\n\t\t * emits `@cell-edit`.\n\t\t *\n\t\t * @param {object} row Row record.\n\t\t * @param {object} col Column definition.\n\t\t * @param {string} raw The raw input string.\n\t\t * @return {void}\n\t\t */\n\t\tcommitEdit(row, col, raw) {\n\t\t\tif (!this.editing) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlet value = raw\n\t\t\tif (col.type === 'number') {\n\t\t\t\tconst n = Number(raw)\n\t\t\t\tvalue = Number.isFinite(n) ? n : null\n\t\t\t}\n\t\t\tthis.editing = null\n\t\t\t/**\n\t\t\t * @event cell-edit Emitted on commit (blur or Enter).\n\t\t\t * Payload: `{rowId, colKey, value, row}`.\n\t\t\t * @type {object}\n\t\t\t */\n\t\t\tthis.$emit('cell-edit', { rowId: row[this.rowIdKey], colKey: col.key, value, row })\n\t\t},\n\n\t\t/**\n\t\t * Cancel the active edit (Esc).\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tcancelEdit() {\n\t\t\tthis.editing = null\n\t\t},\n\n\t\t/**\n\t\t * Per-row total — sum of every numeric column.\n\t\t *\n\t\t * @param {object} row Row record.\n\t\t * @return {number}\n\t\t */\n\t\trowTotal(row) {\n\t\t\tlet total = 0\n\t\t\tfor (const col of this.columns) {\n\t\t\t\tif (col.type === 'number') {\n\t\t\t\t\tconst v = Number(row[col.key])\n\t\t\t\t\tif (Number.isFinite(v)) {\n\t\t\t\t\t\ttotal += v\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn total\n\t\t},\n\n\t\t/**\n\t\t * Per-column total — aggregate over all rows using the\n\t\t * column's `aggregate` setting (`sum` by default).\n\t\t *\n\t\t * @param {object} col Column definition.\n\t\t * @return {number}\n\t\t */\n\t\tcolumnTotal(col) {\n\t\t\tif (col.type !== 'number') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst mode = col.aggregate || 'sum'\n\t\t\tif (mode === 'none') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\t// Drop empty cells (null / undefined / '') BEFORE coercing —\n\t\t\t// `Number(null)` is `0`, which would otherwise be counted and\n\t\t\t// summed as a real zero value.\n\t\t\tconst values = this.rows\n\t\t\t\t.map((r) => r[col.key])\n\t\t\t\t.filter((v) => v !== null && v !== undefined && v !== '')\n\t\t\t\t.map((v) => Number(v))\n\t\t\t\t.filter((v) => Number.isFinite(v))\n\t\t\tif (values.length === 0) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\tif (mode === 'count') {\n\t\t\t\treturn values.length\n\t\t\t}\n\t\t\tif (mode === 'avg') {\n\t\t\t\treturn values.reduce((a, b) => a + b, 0) / values.length\n\t\t\t}\n\t\t\t// default: sum\n\t\t\treturn values.reduce((a, b) => a + b, 0)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-data-matrix {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n}\n\n.cn-data-matrix__title {\n\tmargin: 0;\n\tfont-size: 1.1em;\n}\n\n.cn-data-matrix__description {\n\tmargin: 4px 0 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-data-matrix__scroller {\n\toverflow-x: auto;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n}\n\n.cn-data-matrix__table {\n\twidth: 100%;\n\tborder-collapse: collapse;\n\tfont-size: 0.95em;\n}\n\n.cn-data-matrix__col-header,\n.cn-data-matrix__row-header-corner,\n.cn-data-matrix__total-header {\n\tbackground: var(--color-background-hover);\n\ttext-align: left;\n\tpadding: 8px 10px;\n\tfont-weight: 600;\n\tborder-bottom: 1px solid var(--color-border);\n\tborder-right: 1px solid var(--color-border);\n}\n\n.cn-data-matrix__row-header {\n\tbackground: var(--color-background-hover);\n\ttext-align: left;\n\tpadding: 6px 10px;\n\tfont-weight: 500;\n\tborder-right: 1px solid var(--color-border);\n\tborder-bottom: 1px solid var(--color-border);\n\tmax-width: 200px;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-data-matrix__cell {\n\tpadding: 0;\n\tborder-right: 1px solid var(--color-border);\n\tborder-bottom: 1px solid var(--color-border);\n\tmin-width: 80px;\n}\n\n.cn-data-matrix__cell--number {\n\ttext-align: right;\n}\n\n.cn-data-matrix__cell-value {\n\tdisplay: block;\n\tpadding: 6px 10px;\n\tcursor: cell;\n\tmin-height: 28px;\n}\n\n.cn-data-matrix__cell--read-only .cn-data-matrix__cell-value {\n\tcursor: default;\n\tbackground: var(--color-background-hover);\n}\n\n.cn-data-matrix__cell-input {\n\twidth: 100%;\n\tpadding: 5px 10px;\n\tborder: 1px solid var(--color-primary-element);\n\tbackground: var(--color-main-background);\n\toutline: none;\n\tfont: inherit;\n}\n\n.cn-data-matrix__totals-row .cn-data-matrix__total-cell,\n.cn-data-matrix__total-header {\n\tbackground: var(--color-background-darker, var(--color-background-hover));\n\tfont-weight: 600;\n\ttext-align: right;\n\tpadding: 6px 10px;\n}\n\n.cn-data-matrix__totals-label {\n\tbackground: var(--color-background-darker, var(--color-background-hover));\n\tfont-weight: 600;\n\tpadding: 6px 10px;\n\ttext-align: left;\n}\n\n.cn-data-matrix__grand-total {\n\tbackground: var(--color-primary-element-light);\n}\n\n.cn-data-matrix__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-style: italic;\n\tmargin: 16px 0;\n\ttext-align: center;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_toDisplayString","_createElementVNode","_Fragment","_renderList"],"mappings":"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"}
@@ -5,6 +5,7 @@ import { useObjectStore } from '../../store/useObjectStore.js';
5
5
  import { resolveObjectOpType } from '../../utils/actionsDispatcher.js';
6
6
  import { normalizeHeaderFields, isEmptyChipValue, pickRefLabel, variantForColor, isPastDate } from '../../utils/headerFieldChips.js';
7
7
  import { schemaRefSlug } from '../../utils/schemaRefSlug.js';
8
+ import { userLocale } from '../../utils/userLocale.js';
8
9
 
9
10
  const DAY_MS = 86400000;
10
11
 
@@ -186,7 +187,7 @@ var script = {
186
187
  try {
187
188
  return new Intl.RelativeTimeFormat(this.locale(), { numeric: 'auto' }).format(days, 'day')
188
189
  } catch {
189
- return new Date(time).toLocaleDateString()
190
+ return new Date(time).toLocaleDateString(userLocale())
190
191
  }
191
192
  },
192
193
 
@@ -1 +1 @@
1
- {"version":3,"file":"CnDetailHeaderChips.vue2.js","sources":["../../../../src/components/CnDetailPage/CnDetailHeaderChips.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<!--\n CnDetailHeaderChips — the row of field chips under a detail page's title\n (manifest `config.headerFields`). Internal to CnDetailPage.\n\n An empty value renders no chip, and a row with no chips renders nothing. A\n reference shows the referenced object's label and falls back to the raw id\n in mono when it does not resolve. Each chip reads \"Title: value\" to a screen\n reader, and the row prints as plain text.\n\n @spec openspec/changes/detail-header-field-chips/specs/detail-page-header/spec.md\n-->\n\n<template>\n\t<ul\n\t\tv-if=\"chips.length > 0\"\n\t\tclass=\"cn-detail-header-chips\"\n\t\tdata-testid=\"cn-detail-header-chips\">\n\t\t<li\n\t\t\tv-for=\"chip in chips\"\n\t\t\t:key=\"chip.key\"\n\t\t\tclass=\"cn-detail-header-chips__chip\"\n\t\t\t:class=\"[`cn-detail-header-chips__chip--${chip.format}`, `cn-detail-header-chips__chip--${chip.variant}`]\"\n\t\t\t:data-testid=\"`cn-detail-header-chip-${chip.key}`\">\n\t\t\t<span class=\"hidden-visually\">{{ chip.title }}: </span>\n\t\t\t<CnStatusBadge\n\t\t\t\tv-if=\"chip.format === 'badge'\"\n\t\t\t\t:label=\"chip.text\"\n\t\t\t\t:variant=\"chip.variant\"\n\t\t\t\tsize=\"small\" />\n\t\t\t<NcUserBubble\n\t\t\t\tv-else-if=\"chip.format === 'user' && chip.userId\"\n\t\t\t\t:user=\"chip.userId\"\n\t\t\t\t:displayName=\"chip.text\"\n\t\t\t\t:showUserStatus=\"false\" />\n\t\t\t<time\n\t\t\t\tv-else-if=\"chip.format === 'date'\"\n\t\t\t\tclass=\"cn-detail-header-chips__value\"\n\t\t\t\t:datetime=\"chip.iso\"\n\t\t\t\t:title=\"chip.fullDate\">{{ chip.text }}</time>\n\t\t\t<span\n\t\t\t\tv-else\n\t\t\t\tclass=\"cn-detail-header-chips__value\"\n\t\t\t\t:class=\"{ 'cn-detail-header-chips__value--mono': chip.mono }\">{{ chip.text }}</span>\n\t\t</li>\n\t</ul>\n</template>\n\n<script>\nimport { getCanonicalLocale } from '@nextcloud/l10n'\nimport { NcUserBubble } from '@nextcloud/vue'\nimport CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'\nimport { useObjectStore } from '../../store/useObjectStore.js'\nimport { resolveObjectOpType } from '../../utils/actionsDispatcher.js'\nimport { isEmptyChipValue, isPastDate, normalizeHeaderFields, pickRefLabel, variantForColor } from '../../utils/headerFieldChips.js'\nimport { schemaRefSlug } from '../../utils/schemaRefSlug.js'\n\nconst DAY_MS = 86400000\n\n/**\n * CnDetailHeaderChips — the chips row of `CnDetailPage`'s header.\n */\nexport default {\n\tname: 'CnDetailHeaderChips',\n\n\tcomponents: { CnStatusBadge, NcUserBubble },\n\n\tprops: {\n\t\t/**\n\t\t * The declared header fields (strings or `{ key, format, labelField, colorField, warnWhenPast }`).\n\t\t *\n\t\t * @type {Array<string|object>}\n\t\t */\n\t\tfields: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * The object the chips read their values from.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tobject: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * The JSON Schema of the object, for property titles and `$ref` targets.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tschema: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The OpenRegister register slug a reference resolves in. */\n\t\tregister: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t// Resolved referenced objects, by \"<schema slug>:<id>\".\n\t\t\trefs: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** The normalised entries. */\n\t\tentries() {\n\t\t\treturn normalizeHeaderFields(this.fields)\n\t\t},\n\n\t\t/** The chips that have a value to show, in declared order. */\n\t\tchips() {\n\t\t\tconst obj = this.object\n\t\t\tif (!obj) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst out = []\n\t\t\tfor (const entry of this.entries) {\n\t\t\t\tconst chip = this.buildChip(entry, obj)\n\t\t\t\tif (chip) {\n\t\t\t\t\tout.push(chip)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\t},\n\n\twatch: {\n\t\tobject: { handler: 'resolveRefs', immediate: true },\n\t\tschema: 'resolveRefs',\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * The schema property definition of a field.\n\t\t *\n\t\t * @param {string} key The field key.\n\t\t * @return {object|null} The property or null.\n\t\t */\n\t\tpropertyOf(key) {\n\t\t\tconst props = this.schema && this.schema.properties\n\t\t\treturn (props && props[key]) || null\n\t\t},\n\n\t\t/**\n\t\t * The referenced schema slug of a property, or '' when it is not a reference.\n\t\t *\n\t\t * @param {object|null} prop The schema property.\n\t\t * @return {string} The slug.\n\t\t */\n\t\trefSlugOf(prop) {\n\t\t\tconst ref = prop && (prop.$ref || (prop.items && prop.items.$ref))\n\t\t\treturn ref ? String(schemaRefSlug(ref) || '') : ''\n\t\t},\n\n\t\t/**\n\t\t * Build one chip, or null when its value is empty.\n\t\t *\n\t\t * @param {object} entry The normalised entry.\n\t\t * @param {object} obj The object.\n\t\t * @return {object|null} The chip view model.\n\t\t */\n\t\tbuildChip(entry, obj) {\n\t\t\tconst raw = obj[entry.key]\n\t\t\tif (isEmptyChipValue(raw)) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst prop = this.propertyOf(entry.key)\n\t\t\tconst slug = this.refSlugOf(prop)\n\t\t\tconst title = (prop && typeof prop.title === 'string' && prop.title) || entry.key\n\t\t\tconst chip = { key: entry.key, title, format: entry.format, variant: 'default', mono: entry.format === 'mono', text: '', userId: '' }\n\n\t\t\t// A reference: an expanded object reads straight off, an id comes from the resolved cache.\n\t\t\tconst isRefValue = (raw && typeof raw === 'object' && !Array.isArray(raw)) || slug !== ''\n\t\t\tif (isRefValue) {\n\t\t\t\tconst refObj = (raw && typeof raw === 'object') ? raw : this.refs[`${slug}:${raw}`]\n\t\t\t\tconst label = pickRefLabel(refObj, entry.labelField)\n\t\t\t\tconst idText = (raw && typeof raw === 'object') ? String(raw.id ?? raw.uuid ?? '') : String(raw)\n\t\t\t\tchip.text = label || idText\n\t\t\t\tchip.mono = label === ''\n\t\t\t\tchip.userId = (refObj && (refObj.uid || refObj.id)) ? String(refObj.uid || refObj.id) : idText\n\t\t\t\tif (entry.format === 'badge' && entry.colorField && refObj) {\n\t\t\t\t\tchip.variant = variantForColor(refObj[entry.colorField])\n\t\t\t\t}\n\t\t\t\treturn chip.text === '' ? null : chip\n\t\t\t}\n\n\t\t\tchip.text = Array.isArray(raw) ? raw.join(', ') : String(raw)\n\t\t\tchip.userId = String(raw)\n\t\t\tif (entry.format === 'date') {\n\t\t\t\tconst time = new Date(raw).getTime()\n\t\t\t\tif (Number.isNaN(time)) {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\t\t\t\tchip.iso = new Date(time).toISOString()\n\t\t\t\tchip.fullDate = new Date(time).toLocaleString(this.locale())\n\t\t\t\tchip.text = this.relativeDate(time)\n\t\t\t\tif (entry.warnWhenPast && isPastDate(raw)) {\n\t\t\t\t\tchip.variant = 'error'\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn chip\n\t\t},\n\n\t\t/**\n\t\t * The locale for date formatting.\n\t\t *\n\t\t * @return {string} A BCP 47 locale.\n\t\t */\n\t\tlocale() {\n\t\t\ttry {\n\t\t\t\treturn getCanonicalLocale()\n\t\t\t} catch {\n\t\t\t\treturn 'en'\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * A date as relative text (\"in 3 days\", \"yesterday\").\n\t\t *\n\t\t * @param {number} time The time in ms.\n\t\t * @return {string} The relative text.\n\t\t */\n\t\trelativeDate(time) {\n\t\t\tconst startOfDay = (ms) => new Date(ms).setHours(0, 0, 0, 0)\n\t\t\tconst days = Math.round((startOfDay(time) - startOfDay(Date.now())) / DAY_MS)\n\t\t\ttry {\n\t\t\t\treturn new Intl.RelativeTimeFormat(this.locale(), { numeric: 'auto' }).format(days, 'day')\n\t\t\t} catch {\n\t\t\t\treturn new Date(time).toLocaleDateString()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Resolve the referenced objects behind id-valued reference fields. A\n\t\t * failed lookup leaves the id showing in mono.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync resolveRefs() {\n\t\t\tif (!this.object || !this.register) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlet store = null\n\t\t\ttry {\n\t\t\t\tstore = useObjectStore()\n\t\t\t} catch {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!store) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tawait Promise.all(this.entries.map(async (entry) => {\n\t\t\t\tconst raw = this.object[entry.key]\n\t\t\t\tconst slug = this.refSlugOf(this.propertyOf(entry.key))\n\t\t\t\tif (!slug || isEmptyChipValue(raw) || typeof raw === 'object') {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst cacheKey = `${slug}:${raw}`\n\t\t\t\tif (this.refs[cacheKey]) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tconst type = resolveObjectOpType(store, { register: this.register, schema: slug }, { exactSchema: true })\n\t\t\t\t\tconst cached = store.objects && store.objects[type] && store.objects[type][raw]\n\t\t\t\t\tconst found = cached || await store.fetchObject(type, String(raw))\n\t\t\t\t\tif (found) {\n\t\t\t\t\t\tthis.refs = { ...this.refs, [cacheKey]: found }\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t// The id stays visible in mono.\n\t\t\t\t}\n\t\t\t}))\n\t\t},\n\t},\n}\n</script>\n\n<style>\n.cn-detail-header-chips {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 6px;\n\tmargin: 6px 0 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.cn-detail-header-chips__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tmax-width: 100%;\n\tpadding: 2px 10px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-pill, 16px);\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-main-text);\n\tfont-size: 0.9em;\n}\n\n.cn-detail-header-chips__chip--error {\n\tborder-color: var(--color-error);\n\tcolor: var(--color-error-text, var(--color-error));\n}\n\n.cn-detail-header-chips__chip--badge {\n\tpadding: 0;\n\tborder: none;\n\tbackground: transparent;\n}\n\n.cn-detail-header-chips__value--mono {\n\tfont-family: var(--font-family-monospace, ui-monospace, monospace);\n}\n\n/* Printed, the row is plain text: no pill, border or background. */\n@media print {\n\t.cn-detail-header-chips__chip {\n\t\tpadding: 0;\n\t\tborder: none;\n\t\tbackground: transparent;\n\t\tcolor: inherit;\n\t}\n\n\t.cn-detail-header-chips__chip:not(:last-child)::after {\n\t\tcontent: ' ·';\n\t}\n}\n</style>\n"],"names":["CnStatusBadge"],"mappings":"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"}
1
+ {"version":3,"file":"CnDetailHeaderChips.vue2.js","sources":["../../../../src/components/CnDetailPage/CnDetailHeaderChips.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<!--\n CnDetailHeaderChips — the row of field chips under a detail page's title\n (manifest `config.headerFields`). Internal to CnDetailPage.\n\n An empty value renders no chip, and a row with no chips renders nothing. A\n reference shows the referenced object's label and falls back to the raw id\n in mono when it does not resolve. Each chip reads \"Title: value\" to a screen\n reader, and the row prints as plain text.\n\n @spec openspec/changes/detail-header-field-chips/specs/detail-page-header/spec.md\n-->\n\n<template>\n\t<ul\n\t\tv-if=\"chips.length > 0\"\n\t\tclass=\"cn-detail-header-chips\"\n\t\tdata-testid=\"cn-detail-header-chips\">\n\t\t<li\n\t\t\tv-for=\"chip in chips\"\n\t\t\t:key=\"chip.key\"\n\t\t\tclass=\"cn-detail-header-chips__chip\"\n\t\t\t:class=\"[`cn-detail-header-chips__chip--${chip.format}`, `cn-detail-header-chips__chip--${chip.variant}`]\"\n\t\t\t:data-testid=\"`cn-detail-header-chip-${chip.key}`\">\n\t\t\t<span class=\"hidden-visually\">{{ chip.title }}: </span>\n\t\t\t<CnStatusBadge\n\t\t\t\tv-if=\"chip.format === 'badge'\"\n\t\t\t\t:label=\"chip.text\"\n\t\t\t\t:variant=\"chip.variant\"\n\t\t\t\tsize=\"small\" />\n\t\t\t<NcUserBubble\n\t\t\t\tv-else-if=\"chip.format === 'user' && chip.userId\"\n\t\t\t\t:user=\"chip.userId\"\n\t\t\t\t:displayName=\"chip.text\"\n\t\t\t\t:showUserStatus=\"false\" />\n\t\t\t<time\n\t\t\t\tv-else-if=\"chip.format === 'date'\"\n\t\t\t\tclass=\"cn-detail-header-chips__value\"\n\t\t\t\t:datetime=\"chip.iso\"\n\t\t\t\t:title=\"chip.fullDate\">{{ chip.text }}</time>\n\t\t\t<span\n\t\t\t\tv-else\n\t\t\t\tclass=\"cn-detail-header-chips__value\"\n\t\t\t\t:class=\"{ 'cn-detail-header-chips__value--mono': chip.mono }\">{{ chip.text }}</span>\n\t\t</li>\n\t</ul>\n</template>\n\n<script>\nimport { getCanonicalLocale } from '@nextcloud/l10n'\nimport { NcUserBubble } from '@nextcloud/vue'\nimport CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'\nimport { useObjectStore } from '../../store/useObjectStore.js'\nimport { resolveObjectOpType } from '../../utils/actionsDispatcher.js'\nimport { isEmptyChipValue, isPastDate, normalizeHeaderFields, pickRefLabel, variantForColor } from '../../utils/headerFieldChips.js'\nimport { schemaRefSlug } from '../../utils/schemaRefSlug.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\nconst DAY_MS = 86400000\n\n/**\n * CnDetailHeaderChips — the chips row of `CnDetailPage`'s header.\n */\nexport default {\n\tname: 'CnDetailHeaderChips',\n\n\tcomponents: { CnStatusBadge, NcUserBubble },\n\n\tprops: {\n\t\t/**\n\t\t * The declared header fields (strings or `{ key, format, labelField, colorField, warnWhenPast }`).\n\t\t *\n\t\t * @type {Array<string|object>}\n\t\t */\n\t\tfields: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * The object the chips read their values from.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tobject: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * The JSON Schema of the object, for property titles and `$ref` targets.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tschema: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The OpenRegister register slug a reference resolves in. */\n\t\tregister: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t// Resolved referenced objects, by \"<schema slug>:<id>\".\n\t\t\trefs: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** The normalised entries. */\n\t\tentries() {\n\t\t\treturn normalizeHeaderFields(this.fields)\n\t\t},\n\n\t\t/** The chips that have a value to show, in declared order. */\n\t\tchips() {\n\t\t\tconst obj = this.object\n\t\t\tif (!obj) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst out = []\n\t\t\tfor (const entry of this.entries) {\n\t\t\t\tconst chip = this.buildChip(entry, obj)\n\t\t\t\tif (chip) {\n\t\t\t\t\tout.push(chip)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\t},\n\n\twatch: {\n\t\tobject: { handler: 'resolveRefs', immediate: true },\n\t\tschema: 'resolveRefs',\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * The schema property definition of a field.\n\t\t *\n\t\t * @param {string} key The field key.\n\t\t * @return {object|null} The property or null.\n\t\t */\n\t\tpropertyOf(key) {\n\t\t\tconst props = this.schema && this.schema.properties\n\t\t\treturn (props && props[key]) || null\n\t\t},\n\n\t\t/**\n\t\t * The referenced schema slug of a property, or '' when it is not a reference.\n\t\t *\n\t\t * @param {object|null} prop The schema property.\n\t\t * @return {string} The slug.\n\t\t */\n\t\trefSlugOf(prop) {\n\t\t\tconst ref = prop && (prop.$ref || (prop.items && prop.items.$ref))\n\t\t\treturn ref ? String(schemaRefSlug(ref) || '') : ''\n\t\t},\n\n\t\t/**\n\t\t * Build one chip, or null when its value is empty.\n\t\t *\n\t\t * @param {object} entry The normalised entry.\n\t\t * @param {object} obj The object.\n\t\t * @return {object|null} The chip view model.\n\t\t */\n\t\tbuildChip(entry, obj) {\n\t\t\tconst raw = obj[entry.key]\n\t\t\tif (isEmptyChipValue(raw)) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst prop = this.propertyOf(entry.key)\n\t\t\tconst slug = this.refSlugOf(prop)\n\t\t\tconst title = (prop && typeof prop.title === 'string' && prop.title) || entry.key\n\t\t\tconst chip = { key: entry.key, title, format: entry.format, variant: 'default', mono: entry.format === 'mono', text: '', userId: '' }\n\n\t\t\t// A reference: an expanded object reads straight off, an id comes from the resolved cache.\n\t\t\tconst isRefValue = (raw && typeof raw === 'object' && !Array.isArray(raw)) || slug !== ''\n\t\t\tif (isRefValue) {\n\t\t\t\tconst refObj = (raw && typeof raw === 'object') ? raw : this.refs[`${slug}:${raw}`]\n\t\t\t\tconst label = pickRefLabel(refObj, entry.labelField)\n\t\t\t\tconst idText = (raw && typeof raw === 'object') ? String(raw.id ?? raw.uuid ?? '') : String(raw)\n\t\t\t\tchip.text = label || idText\n\t\t\t\tchip.mono = label === ''\n\t\t\t\tchip.userId = (refObj && (refObj.uid || refObj.id)) ? String(refObj.uid || refObj.id) : idText\n\t\t\t\tif (entry.format === 'badge' && entry.colorField && refObj) {\n\t\t\t\t\tchip.variant = variantForColor(refObj[entry.colorField])\n\t\t\t\t}\n\t\t\t\treturn chip.text === '' ? null : chip\n\t\t\t}\n\n\t\t\tchip.text = Array.isArray(raw) ? raw.join(', ') : String(raw)\n\t\t\tchip.userId = String(raw)\n\t\t\tif (entry.format === 'date') {\n\t\t\t\tconst time = new Date(raw).getTime()\n\t\t\t\tif (Number.isNaN(time)) {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\t\t\t\tchip.iso = new Date(time).toISOString()\n\t\t\t\tchip.fullDate = new Date(time).toLocaleString(this.locale())\n\t\t\t\tchip.text = this.relativeDate(time)\n\t\t\t\tif (entry.warnWhenPast && isPastDate(raw)) {\n\t\t\t\t\tchip.variant = 'error'\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn chip\n\t\t},\n\n\t\t/**\n\t\t * The locale for date formatting.\n\t\t *\n\t\t * @return {string} A BCP 47 locale.\n\t\t */\n\t\tlocale() {\n\t\t\ttry {\n\t\t\t\treturn getCanonicalLocale()\n\t\t\t} catch {\n\t\t\t\treturn 'en'\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * A date as relative text (\"in 3 days\", \"yesterday\").\n\t\t *\n\t\t * @param {number} time The time in ms.\n\t\t * @return {string} The relative text.\n\t\t */\n\t\trelativeDate(time) {\n\t\t\tconst startOfDay = (ms) => new Date(ms).setHours(0, 0, 0, 0)\n\t\t\tconst days = Math.round((startOfDay(time) - startOfDay(Date.now())) / DAY_MS)\n\t\t\ttry {\n\t\t\t\treturn new Intl.RelativeTimeFormat(this.locale(), { numeric: 'auto' }).format(days, 'day')\n\t\t\t} catch {\n\t\t\t\treturn new Date(time).toLocaleDateString(userLocale())\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Resolve the referenced objects behind id-valued reference fields. A\n\t\t * failed lookup leaves the id showing in mono.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync resolveRefs() {\n\t\t\tif (!this.object || !this.register) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlet store = null\n\t\t\ttry {\n\t\t\t\tstore = useObjectStore()\n\t\t\t} catch {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!store) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tawait Promise.all(this.entries.map(async (entry) => {\n\t\t\t\tconst raw = this.object[entry.key]\n\t\t\t\tconst slug = this.refSlugOf(this.propertyOf(entry.key))\n\t\t\t\tif (!slug || isEmptyChipValue(raw) || typeof raw === 'object') {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst cacheKey = `${slug}:${raw}`\n\t\t\t\tif (this.refs[cacheKey]) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tconst type = resolveObjectOpType(store, { register: this.register, schema: slug }, { exactSchema: true })\n\t\t\t\t\tconst cached = store.objects && store.objects[type] && store.objects[type][raw]\n\t\t\t\t\tconst found = cached || await store.fetchObject(type, String(raw))\n\t\t\t\t\tif (found) {\n\t\t\t\t\t\tthis.refs = { ...this.refs, [cacheKey]: found }\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t// The id stays visible in mono.\n\t\t\t\t}\n\t\t\t}))\n\t\t},\n\t},\n}\n</script>\n\n<style>\n.cn-detail-header-chips {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 6px;\n\tmargin: 6px 0 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.cn-detail-header-chips__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tmax-width: 100%;\n\tpadding: 2px 10px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-pill, 16px);\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-main-text);\n\tfont-size: 0.9em;\n}\n\n.cn-detail-header-chips__chip--error {\n\tborder-color: var(--color-error);\n\tcolor: var(--color-error-text, var(--color-error));\n}\n\n.cn-detail-header-chips__chip--badge {\n\tpadding: 0;\n\tborder: none;\n\tbackground: transparent;\n}\n\n.cn-detail-header-chips__value--mono {\n\tfont-family: var(--font-family-monospace, ui-monospace, monospace);\n}\n\n/* Printed, the row is plain text: no pill, border or background. */\n@media print {\n\t.cn-detail-header-chips__chip {\n\t\tpadding: 0;\n\t\tborder: none;\n\t\tbackground: transparent;\n\t\tcolor: inherit;\n\t}\n\n\t.cn-detail-header-chips__chip:not(:last-child)::after {\n\t\tcontent: ' ·';\n\t}\n}\n</style>\n"],"names":["CnStatusBadge"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CnDetailHeaderChips.vue3.js","sources":["../../../../src/components/CnDetailPage/CnDetailHeaderChips.vue?vue&type=template&id=d7f01c18&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<!--\n CnDetailHeaderChips — the row of field chips under a detail page's title\n (manifest `config.headerFields`). Internal to CnDetailPage.\n\n An empty value renders no chip, and a row with no chips renders nothing. A\n reference shows the referenced object's label and falls back to the raw id\n in mono when it does not resolve. Each chip reads \"Title: value\" to a screen\n reader, and the row prints as plain text.\n\n @spec openspec/changes/detail-header-field-chips/specs/detail-page-header/spec.md\n-->\n\n<template>\n\t<ul\n\t\tv-if=\"chips.length > 0\"\n\t\tclass=\"cn-detail-header-chips\"\n\t\tdata-testid=\"cn-detail-header-chips\">\n\t\t<li\n\t\t\tv-for=\"chip in chips\"\n\t\t\t:key=\"chip.key\"\n\t\t\tclass=\"cn-detail-header-chips__chip\"\n\t\t\t:class=\"[`cn-detail-header-chips__chip--${chip.format}`, `cn-detail-header-chips__chip--${chip.variant}`]\"\n\t\t\t:data-testid=\"`cn-detail-header-chip-${chip.key}`\">\n\t\t\t<span class=\"hidden-visually\">{{ chip.title }}: </span>\n\t\t\t<CnStatusBadge\n\t\t\t\tv-if=\"chip.format === 'badge'\"\n\t\t\t\t:label=\"chip.text\"\n\t\t\t\t:variant=\"chip.variant\"\n\t\t\t\tsize=\"small\" />\n\t\t\t<NcUserBubble\n\t\t\t\tv-else-if=\"chip.format === 'user' && chip.userId\"\n\t\t\t\t:user=\"chip.userId\"\n\t\t\t\t:displayName=\"chip.text\"\n\t\t\t\t:showUserStatus=\"false\" />\n\t\t\t<time\n\t\t\t\tv-else-if=\"chip.format === 'date'\"\n\t\t\t\tclass=\"cn-detail-header-chips__value\"\n\t\t\t\t:datetime=\"chip.iso\"\n\t\t\t\t:title=\"chip.fullDate\">{{ chip.text }}</time>\n\t\t\t<span\n\t\t\t\tv-else\n\t\t\t\tclass=\"cn-detail-header-chips__value\"\n\t\t\t\t:class=\"{ 'cn-detail-header-chips__value--mono': chip.mono }\">{{ chip.text }}</span>\n\t\t</li>\n\t</ul>\n</template>\n\n<script>\nimport { getCanonicalLocale } from '@nextcloud/l10n'\nimport { NcUserBubble } from '@nextcloud/vue'\nimport CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'\nimport { useObjectStore } from '../../store/useObjectStore.js'\nimport { resolveObjectOpType } from '../../utils/actionsDispatcher.js'\nimport { isEmptyChipValue, isPastDate, normalizeHeaderFields, pickRefLabel, variantForColor } from '../../utils/headerFieldChips.js'\nimport { schemaRefSlug } from '../../utils/schemaRefSlug.js'\n\nconst DAY_MS = 86400000\n\n/**\n * CnDetailHeaderChips — the chips row of `CnDetailPage`'s header.\n */\nexport default {\n\tname: 'CnDetailHeaderChips',\n\n\tcomponents: { CnStatusBadge, NcUserBubble },\n\n\tprops: {\n\t\t/**\n\t\t * The declared header fields (strings or `{ key, format, labelField, colorField, warnWhenPast }`).\n\t\t *\n\t\t * @type {Array<string|object>}\n\t\t */\n\t\tfields: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * The object the chips read their values from.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tobject: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * The JSON Schema of the object, for property titles and `$ref` targets.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tschema: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The OpenRegister register slug a reference resolves in. */\n\t\tregister: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t// Resolved referenced objects, by \"<schema slug>:<id>\".\n\t\t\trefs: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** The normalised entries. */\n\t\tentries() {\n\t\t\treturn normalizeHeaderFields(this.fields)\n\t\t},\n\n\t\t/** The chips that have a value to show, in declared order. */\n\t\tchips() {\n\t\t\tconst obj = this.object\n\t\t\tif (!obj) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst out = []\n\t\t\tfor (const entry of this.entries) {\n\t\t\t\tconst chip = this.buildChip(entry, obj)\n\t\t\t\tif (chip) {\n\t\t\t\t\tout.push(chip)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\t},\n\n\twatch: {\n\t\tobject: { handler: 'resolveRefs', immediate: true },\n\t\tschema: 'resolveRefs',\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * The schema property definition of a field.\n\t\t *\n\t\t * @param {string} key The field key.\n\t\t * @return {object|null} The property or null.\n\t\t */\n\t\tpropertyOf(key) {\n\t\t\tconst props = this.schema && this.schema.properties\n\t\t\treturn (props && props[key]) || null\n\t\t},\n\n\t\t/**\n\t\t * The referenced schema slug of a property, or '' when it is not a reference.\n\t\t *\n\t\t * @param {object|null} prop The schema property.\n\t\t * @return {string} The slug.\n\t\t */\n\t\trefSlugOf(prop) {\n\t\t\tconst ref = prop && (prop.$ref || (prop.items && prop.items.$ref))\n\t\t\treturn ref ? String(schemaRefSlug(ref) || '') : ''\n\t\t},\n\n\t\t/**\n\t\t * Build one chip, or null when its value is empty.\n\t\t *\n\t\t * @param {object} entry The normalised entry.\n\t\t * @param {object} obj The object.\n\t\t * @return {object|null} The chip view model.\n\t\t */\n\t\tbuildChip(entry, obj) {\n\t\t\tconst raw = obj[entry.key]\n\t\t\tif (isEmptyChipValue(raw)) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst prop = this.propertyOf(entry.key)\n\t\t\tconst slug = this.refSlugOf(prop)\n\t\t\tconst title = (prop && typeof prop.title === 'string' && prop.title) || entry.key\n\t\t\tconst chip = { key: entry.key, title, format: entry.format, variant: 'default', mono: entry.format === 'mono', text: '', userId: '' }\n\n\t\t\t// A reference: an expanded object reads straight off, an id comes from the resolved cache.\n\t\t\tconst isRefValue = (raw && typeof raw === 'object' && !Array.isArray(raw)) || slug !== ''\n\t\t\tif (isRefValue) {\n\t\t\t\tconst refObj = (raw && typeof raw === 'object') ? raw : this.refs[`${slug}:${raw}`]\n\t\t\t\tconst label = pickRefLabel(refObj, entry.labelField)\n\t\t\t\tconst idText = (raw && typeof raw === 'object') ? String(raw.id ?? raw.uuid ?? '') : String(raw)\n\t\t\t\tchip.text = label || idText\n\t\t\t\tchip.mono = label === ''\n\t\t\t\tchip.userId = (refObj && (refObj.uid || refObj.id)) ? String(refObj.uid || refObj.id) : idText\n\t\t\t\tif (entry.format === 'badge' && entry.colorField && refObj) {\n\t\t\t\t\tchip.variant = variantForColor(refObj[entry.colorField])\n\t\t\t\t}\n\t\t\t\treturn chip.text === '' ? null : chip\n\t\t\t}\n\n\t\t\tchip.text = Array.isArray(raw) ? raw.join(', ') : String(raw)\n\t\t\tchip.userId = String(raw)\n\t\t\tif (entry.format === 'date') {\n\t\t\t\tconst time = new Date(raw).getTime()\n\t\t\t\tif (Number.isNaN(time)) {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\t\t\t\tchip.iso = new Date(time).toISOString()\n\t\t\t\tchip.fullDate = new Date(time).toLocaleString(this.locale())\n\t\t\t\tchip.text = this.relativeDate(time)\n\t\t\t\tif (entry.warnWhenPast && isPastDate(raw)) {\n\t\t\t\t\tchip.variant = 'error'\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn chip\n\t\t},\n\n\t\t/**\n\t\t * The locale for date formatting.\n\t\t *\n\t\t * @return {string} A BCP 47 locale.\n\t\t */\n\t\tlocale() {\n\t\t\ttry {\n\t\t\t\treturn getCanonicalLocale()\n\t\t\t} catch {\n\t\t\t\treturn 'en'\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * A date as relative text (\"in 3 days\", \"yesterday\").\n\t\t *\n\t\t * @param {number} time The time in ms.\n\t\t * @return {string} The relative text.\n\t\t */\n\t\trelativeDate(time) {\n\t\t\tconst startOfDay = (ms) => new Date(ms).setHours(0, 0, 0, 0)\n\t\t\tconst days = Math.round((startOfDay(time) - startOfDay(Date.now())) / DAY_MS)\n\t\t\ttry {\n\t\t\t\treturn new Intl.RelativeTimeFormat(this.locale(), { numeric: 'auto' }).format(days, 'day')\n\t\t\t} catch {\n\t\t\t\treturn new Date(time).toLocaleDateString()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Resolve the referenced objects behind id-valued reference fields. A\n\t\t * failed lookup leaves the id showing in mono.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync resolveRefs() {\n\t\t\tif (!this.object || !this.register) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlet store = null\n\t\t\ttry {\n\t\t\t\tstore = useObjectStore()\n\t\t\t} catch {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!store) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tawait Promise.all(this.entries.map(async (entry) => {\n\t\t\t\tconst raw = this.object[entry.key]\n\t\t\t\tconst slug = this.refSlugOf(this.propertyOf(entry.key))\n\t\t\t\tif (!slug || isEmptyChipValue(raw) || typeof raw === 'object') {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst cacheKey = `${slug}:${raw}`\n\t\t\t\tif (this.refs[cacheKey]) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tconst type = resolveObjectOpType(store, { register: this.register, schema: slug }, { exactSchema: true })\n\t\t\t\t\tconst cached = store.objects && store.objects[type] && store.objects[type][raw]\n\t\t\t\t\tconst found = cached || await store.fetchObject(type, String(raw))\n\t\t\t\t\tif (found) {\n\t\t\t\t\t\tthis.refs = { ...this.refs, [cacheKey]: found }\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t// The id stays visible in mono.\n\t\t\t\t}\n\t\t\t}))\n\t\t},\n\t},\n}\n</script>\n\n<style>\n.cn-detail-header-chips {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 6px;\n\tmargin: 6px 0 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.cn-detail-header-chips__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tmax-width: 100%;\n\tpadding: 2px 10px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-pill, 16px);\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-main-text);\n\tfont-size: 0.9em;\n}\n\n.cn-detail-header-chips__chip--error {\n\tborder-color: var(--color-error);\n\tcolor: var(--color-error-text, var(--color-error));\n}\n\n.cn-detail-header-chips__chip--badge {\n\tpadding: 0;\n\tborder: none;\n\tbackground: transparent;\n}\n\n.cn-detail-header-chips__value--mono {\n\tfont-family: var(--font-family-monospace, ui-monospace, monospace);\n}\n\n/* Printed, the row is plain text: no pill, border or background. */\n@media print {\n\t.cn-detail-header-chips__chip {\n\t\tpadding: 0;\n\t\tborder: none;\n\t\tbackground: transparent;\n\t\tcolor: inherit;\n\t}\n\n\t.cn-detail-header-chips__chip:not(:last-child)::after {\n\t\tcontent: ' ·';\n\t}\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_normalizeClass","_toDisplayString","_createBlock"],"mappings":";;;;OAqCO,EAAC,wBAAmB;eACd,EAAC,wBAAe;;;AAOpB,MAAA,UAAA,GAAA,EAAA,KAAA,EAAA,iBAAA,GAAA;;;;;;;AATD,EAAA,OAAA,CAAA,QAAA,CAAA,KAAK,CAAC,MAAM,GAAA,CAAA;AADhB,OAAAA,SAAA,EAAA,EAAAC,kBAAA,OAAA;wEAKc,QAAK,CAAA,KAAA,EAAA,CAAb,IAAI,KAAA;;YACV,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,KAAI,EAAAC,cAAA,CAAA,CAAC,8BAAc,EACsB,CAAA,CAAA,8BAAA,EAAA,IAAG,CAAA,MAAA,CAAA,CAAA,EAAA,CAAA,8BAAA,EAAA,IAAA,CAAA,OAAA,CAAA,CAAA,CAAA,CAAA,CAAA;YAC5C,aAAG,EAAA,CAAA,sBAAA,EAAA,IAAA,CAAA,GAAA,CAAA,CAAA;;+BACE,kBAAA,EAAAC,eAAA,CAAA,IAAA,CAAA,KAAA,IAAA,IAAA,EAAA,CAAA,YAAA;AAEC,YAAA,CAAA,IAAI,CAAC,MAAM,KAAA,OAAA;8BADjBC;;kBAEC,KAAC,EAAA,IAAA,CAAA,IAAA;kBACD,OAAA,EAAA,IAAA,CAAA,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"CnDetailHeaderChips.vue3.js","sources":["../../../../src/components/CnDetailPage/CnDetailHeaderChips.vue?vue&type=template&id=4f8ee522&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<!--\n CnDetailHeaderChips — the row of field chips under a detail page's title\n (manifest `config.headerFields`). Internal to CnDetailPage.\n\n An empty value renders no chip, and a row with no chips renders nothing. A\n reference shows the referenced object's label and falls back to the raw id\n in mono when it does not resolve. Each chip reads \"Title: value\" to a screen\n reader, and the row prints as plain text.\n\n @spec openspec/changes/detail-header-field-chips/specs/detail-page-header/spec.md\n-->\n\n<template>\n\t<ul\n\t\tv-if=\"chips.length > 0\"\n\t\tclass=\"cn-detail-header-chips\"\n\t\tdata-testid=\"cn-detail-header-chips\">\n\t\t<li\n\t\t\tv-for=\"chip in chips\"\n\t\t\t:key=\"chip.key\"\n\t\t\tclass=\"cn-detail-header-chips__chip\"\n\t\t\t:class=\"[`cn-detail-header-chips__chip--${chip.format}`, `cn-detail-header-chips__chip--${chip.variant}`]\"\n\t\t\t:data-testid=\"`cn-detail-header-chip-${chip.key}`\">\n\t\t\t<span class=\"hidden-visually\">{{ chip.title }}: </span>\n\t\t\t<CnStatusBadge\n\t\t\t\tv-if=\"chip.format === 'badge'\"\n\t\t\t\t:label=\"chip.text\"\n\t\t\t\t:variant=\"chip.variant\"\n\t\t\t\tsize=\"small\" />\n\t\t\t<NcUserBubble\n\t\t\t\tv-else-if=\"chip.format === 'user' && chip.userId\"\n\t\t\t\t:user=\"chip.userId\"\n\t\t\t\t:displayName=\"chip.text\"\n\t\t\t\t:showUserStatus=\"false\" />\n\t\t\t<time\n\t\t\t\tv-else-if=\"chip.format === 'date'\"\n\t\t\t\tclass=\"cn-detail-header-chips__value\"\n\t\t\t\t:datetime=\"chip.iso\"\n\t\t\t\t:title=\"chip.fullDate\">{{ chip.text }}</time>\n\t\t\t<span\n\t\t\t\tv-else\n\t\t\t\tclass=\"cn-detail-header-chips__value\"\n\t\t\t\t:class=\"{ 'cn-detail-header-chips__value--mono': chip.mono }\">{{ chip.text }}</span>\n\t\t</li>\n\t</ul>\n</template>\n\n<script>\nimport { getCanonicalLocale } from '@nextcloud/l10n'\nimport { NcUserBubble } from '@nextcloud/vue'\nimport CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'\nimport { useObjectStore } from '../../store/useObjectStore.js'\nimport { resolveObjectOpType } from '../../utils/actionsDispatcher.js'\nimport { isEmptyChipValue, isPastDate, normalizeHeaderFields, pickRefLabel, variantForColor } from '../../utils/headerFieldChips.js'\nimport { schemaRefSlug } from '../../utils/schemaRefSlug.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\nconst DAY_MS = 86400000\n\n/**\n * CnDetailHeaderChips — the chips row of `CnDetailPage`'s header.\n */\nexport default {\n\tname: 'CnDetailHeaderChips',\n\n\tcomponents: { CnStatusBadge, NcUserBubble },\n\n\tprops: {\n\t\t/**\n\t\t * The declared header fields (strings or `{ key, format, labelField, colorField, warnWhenPast }`).\n\t\t *\n\t\t * @type {Array<string|object>}\n\t\t */\n\t\tfields: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * The object the chips read their values from.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tobject: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * The JSON Schema of the object, for property titles and `$ref` targets.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tschema: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The OpenRegister register slug a reference resolves in. */\n\t\tregister: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t// Resolved referenced objects, by \"<schema slug>:<id>\".\n\t\t\trefs: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** The normalised entries. */\n\t\tentries() {\n\t\t\treturn normalizeHeaderFields(this.fields)\n\t\t},\n\n\t\t/** The chips that have a value to show, in declared order. */\n\t\tchips() {\n\t\t\tconst obj = this.object\n\t\t\tif (!obj) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst out = []\n\t\t\tfor (const entry of this.entries) {\n\t\t\t\tconst chip = this.buildChip(entry, obj)\n\t\t\t\tif (chip) {\n\t\t\t\t\tout.push(chip)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\t},\n\n\twatch: {\n\t\tobject: { handler: 'resolveRefs', immediate: true },\n\t\tschema: 'resolveRefs',\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * The schema property definition of a field.\n\t\t *\n\t\t * @param {string} key The field key.\n\t\t * @return {object|null} The property or null.\n\t\t */\n\t\tpropertyOf(key) {\n\t\t\tconst props = this.schema && this.schema.properties\n\t\t\treturn (props && props[key]) || null\n\t\t},\n\n\t\t/**\n\t\t * The referenced schema slug of a property, or '' when it is not a reference.\n\t\t *\n\t\t * @param {object|null} prop The schema property.\n\t\t * @return {string} The slug.\n\t\t */\n\t\trefSlugOf(prop) {\n\t\t\tconst ref = prop && (prop.$ref || (prop.items && prop.items.$ref))\n\t\t\treturn ref ? String(schemaRefSlug(ref) || '') : ''\n\t\t},\n\n\t\t/**\n\t\t * Build one chip, or null when its value is empty.\n\t\t *\n\t\t * @param {object} entry The normalised entry.\n\t\t * @param {object} obj The object.\n\t\t * @return {object|null} The chip view model.\n\t\t */\n\t\tbuildChip(entry, obj) {\n\t\t\tconst raw = obj[entry.key]\n\t\t\tif (isEmptyChipValue(raw)) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst prop = this.propertyOf(entry.key)\n\t\t\tconst slug = this.refSlugOf(prop)\n\t\t\tconst title = (prop && typeof prop.title === 'string' && prop.title) || entry.key\n\t\t\tconst chip = { key: entry.key, title, format: entry.format, variant: 'default', mono: entry.format === 'mono', text: '', userId: '' }\n\n\t\t\t// A reference: an expanded object reads straight off, an id comes from the resolved cache.\n\t\t\tconst isRefValue = (raw && typeof raw === 'object' && !Array.isArray(raw)) || slug !== ''\n\t\t\tif (isRefValue) {\n\t\t\t\tconst refObj = (raw && typeof raw === 'object') ? raw : this.refs[`${slug}:${raw}`]\n\t\t\t\tconst label = pickRefLabel(refObj, entry.labelField)\n\t\t\t\tconst idText = (raw && typeof raw === 'object') ? String(raw.id ?? raw.uuid ?? '') : String(raw)\n\t\t\t\tchip.text = label || idText\n\t\t\t\tchip.mono = label === ''\n\t\t\t\tchip.userId = (refObj && (refObj.uid || refObj.id)) ? String(refObj.uid || refObj.id) : idText\n\t\t\t\tif (entry.format === 'badge' && entry.colorField && refObj) {\n\t\t\t\t\tchip.variant = variantForColor(refObj[entry.colorField])\n\t\t\t\t}\n\t\t\t\treturn chip.text === '' ? null : chip\n\t\t\t}\n\n\t\t\tchip.text = Array.isArray(raw) ? raw.join(', ') : String(raw)\n\t\t\tchip.userId = String(raw)\n\t\t\tif (entry.format === 'date') {\n\t\t\t\tconst time = new Date(raw).getTime()\n\t\t\t\tif (Number.isNaN(time)) {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\t\t\t\tchip.iso = new Date(time).toISOString()\n\t\t\t\tchip.fullDate = new Date(time).toLocaleString(this.locale())\n\t\t\t\tchip.text = this.relativeDate(time)\n\t\t\t\tif (entry.warnWhenPast && isPastDate(raw)) {\n\t\t\t\t\tchip.variant = 'error'\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn chip\n\t\t},\n\n\t\t/**\n\t\t * The locale for date formatting.\n\t\t *\n\t\t * @return {string} A BCP 47 locale.\n\t\t */\n\t\tlocale() {\n\t\t\ttry {\n\t\t\t\treturn getCanonicalLocale()\n\t\t\t} catch {\n\t\t\t\treturn 'en'\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * A date as relative text (\"in 3 days\", \"yesterday\").\n\t\t *\n\t\t * @param {number} time The time in ms.\n\t\t * @return {string} The relative text.\n\t\t */\n\t\trelativeDate(time) {\n\t\t\tconst startOfDay = (ms) => new Date(ms).setHours(0, 0, 0, 0)\n\t\t\tconst days = Math.round((startOfDay(time) - startOfDay(Date.now())) / DAY_MS)\n\t\t\ttry {\n\t\t\t\treturn new Intl.RelativeTimeFormat(this.locale(), { numeric: 'auto' }).format(days, 'day')\n\t\t\t} catch {\n\t\t\t\treturn new Date(time).toLocaleDateString(userLocale())\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Resolve the referenced objects behind id-valued reference fields. A\n\t\t * failed lookup leaves the id showing in mono.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync resolveRefs() {\n\t\t\tif (!this.object || !this.register) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlet store = null\n\t\t\ttry {\n\t\t\t\tstore = useObjectStore()\n\t\t\t} catch {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!store) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tawait Promise.all(this.entries.map(async (entry) => {\n\t\t\t\tconst raw = this.object[entry.key]\n\t\t\t\tconst slug = this.refSlugOf(this.propertyOf(entry.key))\n\t\t\t\tif (!slug || isEmptyChipValue(raw) || typeof raw === 'object') {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst cacheKey = `${slug}:${raw}`\n\t\t\t\tif (this.refs[cacheKey]) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tconst type = resolveObjectOpType(store, { register: this.register, schema: slug }, { exactSchema: true })\n\t\t\t\t\tconst cached = store.objects && store.objects[type] && store.objects[type][raw]\n\t\t\t\t\tconst found = cached || await store.fetchObject(type, String(raw))\n\t\t\t\t\tif (found) {\n\t\t\t\t\t\tthis.refs = { ...this.refs, [cacheKey]: found }\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t// The id stays visible in mono.\n\t\t\t\t}\n\t\t\t}))\n\t\t},\n\t},\n}\n</script>\n\n<style>\n.cn-detail-header-chips {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 6px;\n\tmargin: 6px 0 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.cn-detail-header-chips__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tmax-width: 100%;\n\tpadding: 2px 10px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-pill, 16px);\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-main-text);\n\tfont-size: 0.9em;\n}\n\n.cn-detail-header-chips__chip--error {\n\tborder-color: var(--color-error);\n\tcolor: var(--color-error-text, var(--color-error));\n}\n\n.cn-detail-header-chips__chip--badge {\n\tpadding: 0;\n\tborder: none;\n\tbackground: transparent;\n}\n\n.cn-detail-header-chips__value--mono {\n\tfont-family: var(--font-family-monospace, ui-monospace, monospace);\n}\n\n/* Printed, the row is plain text: no pill, border or background. */\n@media print {\n\t.cn-detail-header-chips__chip {\n\t\tpadding: 0;\n\t\tborder: none;\n\t\tbackground: transparent;\n\t\tcolor: inherit;\n\t}\n\n\t.cn-detail-header-chips__chip:not(:last-child)::after {\n\t\tcontent: ' ·';\n\t}\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_normalizeClass","_toDisplayString","_createBlock"],"mappings":";;;;OAqCO,EAAC,wBAAmB;eACd,EAAC,wBAAe;;;AAOpB,MAAA,UAAA,GAAA,EAAA,KAAA,EAAA,iBAAA,GAAA;;;;;;;AATD,EAAA,OAAA,CAAA,QAAA,CAAA,KAAK,CAAC,MAAM,GAAA,CAAA;AADhB,OAAAA,SAAA,EAAA,EAAAC,kBAAA,OAAA;wEAKc,QAAK,CAAA,KAAA,EAAA,CAAb,IAAI,KAAA;;YACV,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,KAAI,EAAAC,cAAA,CAAA,CAAC,8BAAc,EACsB,CAAA,CAAA,8BAAA,EAAA,IAAG,CAAA,MAAA,CAAA,CAAA,EAAA,CAAA,8BAAA,EAAA,IAAA,CAAA,OAAA,CAAA,CAAA,CAAA,CAAA,CAAA;YAC5C,aAAG,EAAA,CAAA,sBAAA,EAAA,IAAA,CAAA,GAAA,CAAA,CAAA;;+BACE,kBAAA,EAAAC,eAAA,CAAA,IAAA,CAAA,KAAA,IAAA,IAAA,EAAA,CAAA,YAAA;AAEC,YAAA,CAAA,IAAI,CAAC,MAAM,KAAA,OAAA;8BADjBC;;kBAEC,KAAC,EAAA,IAAA,CAAA,IAAA;kBACD,OAAA,EAAA,IAAA,CAAA,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -2,7 +2,7 @@ import script from './CnEmailPicker.vue2.js';
2
2
  import { render } from './CnEmailPicker.vue3.js';
3
3
 
4
4
  script.render = render;
5
- script.__scopeId = "data-v-d228f484";
5
+ script.__scopeId = "data-v-25ee69f0";
6
6
  const __sfc_main = script;
7
7
 
8
8
  export { __sfc_main as default };
@@ -2,6 +2,7 @@ import { translate } from '@nextcloud/l10n';
2
2
  import { NcButton, NcDialog, NcEmptyContent, NcLoadingIcon, NcNoteCard, NcTextField } from '@nextcloud/vue';
3
3
  import EmailOutline from 'vue-material-design-icons/EmailOutline.vue';
4
4
  import FolderOutline from 'vue-material-design-icons/FolderOutline.vue';
5
+ import { userLocale } from '../../utils/userLocale.js';
5
6
  import { prefixUrl, buildHeaders } from '../../utils/headers.js';
6
7
 
7
8
  var script = {
@@ -236,7 +237,7 @@ var script = {
236
237
  if (Number.isNaN(d.getTime())) {
237
238
  return String(raw)
238
239
  }
239
- return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })
240
+ return d.toLocaleDateString(userLocale(), { year: 'numeric', month: 'short', day: 'numeric' })
240
241
  },
241
242
  },
242
243
  };
@@ -1 +1 @@
1
- {"version":3,"file":"CnEmailPicker.vue2.js","sources":["../../../../src/components/CnEmailPicker/CnEmailPicker.vue"],"sourcesContent":["<!--\n CnEmailPicker — multi-step modal for picking an existing NC Mail\n message to link to the parent OR object.\n\n Flow (three steps + confirm):\n 1. Choose an account — GET /api/integrations/email/accounts\n 2. Choose a mailbox — GET /api/integrations/email/accounts/{id}/mailboxes\n 3. Browse messages — GET /api/integrations/email/accounts/{id}/messages?mailbox=\n (cursor pagination + client-side search filter)\n 4. Confirm — emit `link` with `{ mailAccountId, messageId, messageUid }`\n\n All API calls are wrapped in best-effort try/catch so a transient\n Mail failure surfaces a user-visible inline error rather than a modal\n crash. The modal stays open across errors so the user can retry\n without losing context.\n\n ADR-004: modal lives in its own .vue file under\n `src/components/CnEmailPicker/`. Picker UX is dialog-shaped so this\n variant uses NcDialog (matches CnDeckCardPicker).\n\n ADR-019: drives the `email` integration leaf's \"link existing\"\n surface; emits `link` so the parent (CnEmailTab) can POST the\n selection to the OR endpoint.\n\n SPDX-License-Identifier: EUPL-1.2\n SPDX-FileCopyrightText: 2026 Conduction B.V.\n-->\n<template>\n\t<NcDialog\n\t\t:name=\"dialogTitle\"\n\t\tsize=\"normal\"\n\t\tdata-testid=\"cn-email-picker\"\n\t\t@closing=\"onClose\">\n\t\t<div class=\"cn-email-picker\">\n\t\t\t<ol class=\"cn-email-picker__steps\">\n\t\t\t\t<li :class=\"stepClass(1)\">\n\t\t\t\t\t{{ t('nextcloud-vue', '1. Account') }}\n\t\t\t\t</li>\n\t\t\t\t<li :class=\"stepClass(2)\">\n\t\t\t\t\t{{ t('nextcloud-vue', '2. Mailbox') }}\n\t\t\t\t</li>\n\t\t\t\t<li :class=\"stepClass(3)\">\n\t\t\t\t\t{{ t('nextcloud-vue', '3. Message') }}\n\t\t\t\t</li>\n\t\t\t</ol>\n\n\t\t\t<NcNoteCard v-if=\"error\" type=\"error\" class=\"cn-email-picker__error\">\n\t\t\t\t{{ error }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<section v-if=\"step === 1\" class=\"cn-email-picker__panel\">\n\t\t\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t\t\t<NcEmptyContent\n\t\t\t\t\tv-else-if=\"accounts.length === 0\"\n\t\t\t\t\t:name=\"t('nextcloud-vue', 'No mail accounts')\"\n\t\t\t\t\t:description=\"t('nextcloud-vue', 'NC Mail has no accounts configured for you yet.')\" />\n\t\t\t\t<ul v-else class=\"cn-email-picker__list\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"account in accounts\"\n\t\t\t\t\t\t:key=\"account.id\"\n\t\t\t\t\t\tclass=\"cn-email-picker__row\"\n\t\t\t\t\t\t:class=\"{ 'cn-email-picker__row--selected': selectedAccountId === account.id }\">\n\t\t\t\t\t\t<button type=\"button\" class=\"cn-email-picker__row-button\" @click=\"pickAccount(account)\">\n\t\t\t\t\t\t\t<EmailOutline :size=\"20\" />\n\t\t\t\t\t\t\t<span class=\"cn-email-picker__row-label\">\n\t\t\t\t\t\t\t\t<span class=\"cn-email-picker__row-primary\">{{ account.label }}</span>\n\t\t\t\t\t\t\t\t<span v-if=\"account.email && account.email !== account.label\" class=\"cn-email-picker__row-secondary\">{{ account.email }}</span>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</section>\n\n\t\t\t<section v-else-if=\"step === 2\" class=\"cn-email-picker__panel\">\n\t\t\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t\t\t<NcEmptyContent\n\t\t\t\t\tv-else-if=\"mailboxes.length === 0\"\n\t\t\t\t\t:name=\"t('nextcloud-vue', 'No mailboxes')\"\n\t\t\t\t\t:description=\"t('nextcloud-vue', 'This account has no mailboxes to browse.')\" />\n\t\t\t\t<ul v-else class=\"cn-email-picker__list\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"mb in mailboxes\"\n\t\t\t\t\t\t:key=\"mb.id\"\n\t\t\t\t\t\tclass=\"cn-email-picker__row\"\n\t\t\t\t\t\t:class=\"{ 'cn-email-picker__row--selected': selectedMailbox === mb.name }\">\n\t\t\t\t\t\t<button type=\"button\" class=\"cn-email-picker__row-button\" @click=\"pickMailbox(mb)\">\n\t\t\t\t\t\t\t<FolderOutline :size=\"20\" />\n\t\t\t\t\t\t\t<span class=\"cn-email-picker__row-label\">\n\t\t\t\t\t\t\t\t<span class=\"cn-email-picker__row-primary\">{{ mb.displayName }}</span>\n\t\t\t\t\t\t\t\t<span v-if=\"mb.name !== mb.displayName\" class=\"cn-email-picker__row-secondary\">{{ mb.name }}</span>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</section>\n\n\t\t\t<section v-else-if=\"step === 3\" class=\"cn-email-picker__panel\">\n\t\t\t\t<NcTextField\n\t\t\t\t\tv-model=\"filterText\"\n\t\t\t\t\t:label=\"t('nextcloud-vue', 'Filter messages')\"\n\t\t\t\t\tclass=\"cn-email-picker__filter\" />\n\n\t\t\t\t<NcLoadingIcon v-if=\"loading && messages.length === 0\" />\n\t\t\t\t<NcEmptyContent\n\t\t\t\t\tv-else-if=\"filteredMessages.length === 0\"\n\t\t\t\t\t:name=\"t('nextcloud-vue', 'No messages')\"\n\t\t\t\t\t:description=\"t('nextcloud-vue', 'This mailbox has no messages matching your filter.')\" />\n\t\t\t\t<ul v-else class=\"cn-email-picker__list cn-email-picker__list--messages\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"msg in filteredMessages\"\n\t\t\t\t\t\t:key=\"msg.id\"\n\t\t\t\t\t\tclass=\"cn-email-picker__row\"\n\t\t\t\t\t\t:class=\"{ 'cn-email-picker__row--selected': selectedMessageId === msg.id }\">\n\t\t\t\t\t\t<button type=\"button\" class=\"cn-email-picker__row-button cn-email-picker__row-button--message\" @click=\"pickMessage(msg)\">\n\t\t\t\t\t\t\t<EmailOutline :size=\"18\" />\n\t\t\t\t\t\t\t<span class=\"cn-email-picker__message\">\n\t\t\t\t\t\t\t\t<span class=\"cn-email-picker__message-subject\">{{ msg.subject || t('nextcloud-vue', '(no subject)') }}</span>\n\t\t\t\t\t\t\t\t<span class=\"cn-email-picker__message-meta\">\n\t\t\t\t\t\t\t\t\t<span class=\"cn-email-picker__message-sender\">{{ msg.sender || t('nextcloud-vue', 'Unknown sender') }}</span>\n\t\t\t\t\t\t\t\t\t<span v-if=\"msg.date\" class=\"cn-email-picker__message-date\">{{ formatDate(msg.date) }}</span>\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\n\t\t\t\t<NcButton\n\t\t\t\t\tv-if=\"nextCursor !== null\"\n\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t:disabled=\"loadingMore\"\n\t\t\t\t\tclass=\"cn-email-picker__load-more\"\n\t\t\t\t\t@click=\"loadMoreMessages\">\n\t\t\t\t\t<template v-if=\"loadingMore\" #icon>\n\t\t\t\t\t\t<NcLoadingIcon :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ t('nextcloud-vue', 'Load more') }}\n\t\t\t\t</NcButton>\n\t\t\t</section>\n\t\t</div>\n\n\t\t<template #actions>\n\t\t\t<NcButton v-if=\"step > 1\" @click=\"goBack\">\n\t\t\t\t{{ t('nextcloud-vue', 'Back') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton @click=\"onClose\">\n\t\t\t\t{{ t('nextcloud-vue', 'Cancel') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton\n\t\t\t\tv-if=\"step === 3\"\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:disabled=\"!selectedMessageId\"\n\t\t\t\t@click=\"confirm\">\n\t\t\t\t{{ t('nextcloud-vue', 'Link message') }}\n\t\t\t</NcButton>\n\t\t</template>\n\t</NcDialog>\n</template>\n\n<script>\n/**\n * SPDX-License-Identifier: EUPL-1.2\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n *\n * CnEmailPicker — pick an existing Mail message via account, mailbox,\n * message. Emits `link` with `{ mailAccountId, messageId, messageUid }`.\n *\n * @see ADR-019 (pluggable integrations) and ADR-022 (sidebar tabs)\n */\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcDialog, NcEmptyContent, NcLoadingIcon, NcNoteCard, NcTextField } from '@nextcloud/vue'\nimport EmailOutline from 'vue-material-design-icons/EmailOutline.vue'\nimport FolderOutline from 'vue-material-design-icons/FolderOutline.vue'\nimport { buildHeaders, prefixUrl } from '../../utils/index.js'\n\nexport default {\n\tname: 'CnEmailPicker',\n\n\tcomponents: { NcButton, NcDialog, NcEmptyContent, NcLoadingIcon, NcNoteCard, NcTextField, EmailOutline, FolderOutline },\n\n\tprops: {\n\t\t/** Base API URL for OR. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Pre-translated dialog title. */\n\t\tdialogTitle: { type: String, default: () => t('nextcloud-vue', 'Link an existing email') },\n\t\t/** Page size for the message list (load-more pagination). */\n\t\tpageSize: { type: Number, default: 50 },\n\t},\n\n\temits: ['close', 'link'],\n\n\tdata() {\n\t\treturn {\n\t\t\tstep: 1,\n\t\t\tloading: false,\n\t\t\tloadingMore: false,\n\t\t\terror: '',\n\t\t\taccounts: [],\n\t\t\tmailboxes: [],\n\t\t\tmessages: [],\n\t\t\tselectedAccountId: null,\n\t\t\tselectedMailbox: null,\n\t\t\tselectedMessageId: null,\n\t\t\tselectedMessageUid: null,\n\t\t\tnextCursor: null,\n\t\t\tfilterText: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tfilteredMessages() {\n\t\t\tconst needle = this.filterText.trim().toLowerCase()\n\t\t\tif (needle === '') {\n\t\t\t\treturn this.messages\n\t\t\t}\n\t\t\treturn this.messages.filter((msg) => {\n\t\t\t\tconst subj = (msg.subject || '').toLowerCase()\n\t\t\t\tconst sender = (msg.sender || '').toLowerCase()\n\t\t\t\treturn subj.includes(needle) || sender.includes(needle)\n\t\t\t})\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.fetchAccounts()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\tstepClass(step) {\n\t\t\treturn {\n\t\t\t\t'cn-email-picker__step': true,\n\t\t\t\t'cn-email-picker__step--active': this.step === step,\n\t\t\t\t'cn-email-picker__step--done': this.step > step,\n\t\t\t}\n\t\t},\n\n\t\tasync fetchAccounts() {\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.apiBase}/integrations/email/accounts`), {\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthis.accounts = data.results || []\n\t\t\t\t} else if (response.status === 501) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Nextcloud Mail is not installed.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load mail accounts.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnEmailPicker] fetch accounts failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load mail accounts.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchMailboxes(accountId) {\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.apiBase}/integrations/email/accounts/${accountId}/mailboxes`), {\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthis.mailboxes = data.results || []\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load mailboxes.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnEmailPicker] fetch mailboxes failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load mailboxes.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchMessages(accountId, mailbox, append = false) {\n\t\t\tif (append === true) {\n\t\t\t\tthis.loadingMore = true\n\t\t\t} else {\n\t\t\t\tthis.loading = true\n\t\t\t\tthis.messages = []\n\t\t\t\tthis.nextCursor = null\n\t\t\t}\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst params = new URLSearchParams({\n\t\t\t\t\tmailbox,\n\t\t\t\t\tlimit: String(this.pageSize),\n\t\t\t\t})\n\t\t\t\tif (append === true && this.nextCursor !== null) {\n\t\t\t\t\tparams.set('cursor', String(this.nextCursor))\n\t\t\t\t}\n\t\t\t\tconst response = await fetch(\n\t\t\t\t\tprefixUrl(`${this.apiBase}/integrations/email/accounts/${accountId}/messages?${params.toString()}`),\n\t\t\t\t\t{ headers: buildHeaders() },\n\t\t\t\t)\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tconst items = Array.isArray(data.items) ? data.items : []\n\t\t\t\t\tthis.messages = append === true ? [...this.messages, ...items] : items\n\t\t\t\t\tthis.nextCursor = data.nextCursor !== undefined ? data.nextCursor : null\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load messages.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnEmailPicker] fetch messages failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load messages.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t\tthis.loadingMore = false\n\t\t\t}\n\t\t},\n\n\t\tpickAccount(account) {\n\t\t\tthis.selectedAccountId = account.id\n\t\t\tthis.selectedMailbox = null\n\t\t\tthis.selectedMessageId = null\n\t\t\tthis.selectedMessageUid = null\n\t\t\tthis.mailboxes = []\n\t\t\tthis.messages = []\n\t\t\tthis.step = 2\n\t\t\tthis.fetchMailboxes(account.id)\n\t\t},\n\n\t\tpickMailbox(mailbox) {\n\t\t\tthis.selectedMailbox = mailbox.name\n\t\t\tthis.selectedMessageId = null\n\t\t\tthis.selectedMessageUid = null\n\t\t\tthis.messages = []\n\t\t\tthis.filterText = ''\n\t\t\tthis.step = 3\n\t\t\tthis.fetchMessages(this.selectedAccountId, mailbox.name)\n\t\t},\n\n\t\tpickMessage(msg) {\n\t\t\tthis.selectedMessageId = msg.id\n\t\t\tthis.selectedMessageUid = msg.uid || ''\n\t\t},\n\n\t\tloadMoreMessages() {\n\t\t\tif (this.nextCursor === null || this.selectedAccountId === null || this.selectedMailbox === null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.fetchMessages(this.selectedAccountId, this.selectedMailbox, true)\n\t\t},\n\n\t\tgoBack() {\n\t\t\tif (this.step === 3) {\n\t\t\t\tthis.selectedMessageId = null\n\t\t\t\tthis.selectedMessageUid = null\n\t\t\t\tthis.messages = []\n\t\t\t\tthis.nextCursor = null\n\t\t\t\tthis.filterText = ''\n\t\t\t\tthis.step = 2\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.step === 2) {\n\t\t\t\tthis.selectedMailbox = null\n\t\t\t\tthis.mailboxes = []\n\t\t\t\tthis.step = 1\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Dismiss the dialog.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonClose() {\n\t\t\t/**\n\t\t\t * @event close Emitted when the dialog should be closed (cancel or close button).\n\t\t\t */\n\t\t\tthis.$emit('close')\n\t\t},\n\n\t\tconfirm() {\n\t\t\tif (this.selectedMessageId === null || this.selectedAccountId === null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t/**\n\t\t\t * @event link Emitted when the user confirms the selection. Payload: `{ mailAccountId, messageId, messageUid }`.\n\t\t\t */\n\t\t\tthis.$emit('link', {\n\t\t\t\tmailAccountId: this.selectedAccountId,\n\t\t\t\tmessageId: String(this.selectedMessageId),\n\t\t\t\tmessageUid: String(this.selectedMessageUid || ''),\n\t\t\t})\n\t\t},\n\n\t\tformatDate(raw) {\n\t\t\tif (!raw) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst d = new Date(raw)\n\t\t\tif (Number.isNaN(d.getTime())) {\n\t\t\t\treturn String(raw)\n\t\t\t}\n\t\t\treturn d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-email-picker {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\tpadding: 4px 0;\n}\n\n.cn-email-picker__steps {\n\tdisplay: flex;\n\tgap: 16px;\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0 4px 8px;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-email-picker__step {\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-email-picker__step--active {\n\tcolor: var(--color-main-text);\n\tfont-weight: 600;\n}\n\n.cn-email-picker__step--done {\n\tcolor: var(--color-success, #46ba61);\n}\n\n.cn-email-picker__error {\n\tmargin: 4px 0;\n}\n\n.cn-email-picker__panel {\n\tmin-height: 180px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n}\n\n.cn-email-picker__filter {\n\twidth: 100%;\n}\n\n.cn-email-picker__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\tmax-height: 360px;\n\toverflow-y: auto;\n}\n\n.cn-email-picker__list--messages {\n\tmax-height: 320px;\n}\n\n.cn-email-picker__row {\n\tborder-radius: var(--border-radius);\n}\n\n.cn-email-picker__row--selected {\n\tbackground: var(--color-primary-element-light);\n}\n\n.cn-email-picker__row-button {\n\tdisplay: flex;\n\twidth: 100%;\n\talign-items: center;\n\tgap: 10px;\n\tpadding: 8px 10px;\n\tbackground: transparent;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tcolor: var(--color-main-text);\n\tcursor: pointer;\n\ttext-align: left;\n}\n\n.cn-email-picker__row-button:hover {\n\tbackground: var(--color-background-hover);\n}\n\n.cn-email-picker__row--selected .cn-email-picker__row-button {\n\tborder-color: var(--color-primary-element);\n}\n\n.cn-email-picker__row-button--message {\n\talign-items: flex-start;\n}\n\n.cn-email-picker__row-label {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tflex: 1 1 auto;\n\toverflow: hidden;\n}\n\n.cn-email-picker__row-primary {\n\tfont-weight: 500;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-email-picker__row-secondary {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-email-picker__message {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tflex: 1 1 auto;\n\toverflow: hidden;\n}\n\n.cn-email-picker__message-subject {\n\tfont-weight: 500;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-email-picker__message-meta {\n\tdisplay: flex;\n\tgap: 8px;\n\tjustify-content: space-between;\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-email-picker__message-sender {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-email-picker__message-date {\n\tflex-shrink: 0;\n}\n\n.cn-email-picker__load-more {\n\tmargin-top: 4px;\n}\n</style>\n"],"names":["t"],"mappings":"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"}
1
+ {"version":3,"file":"CnEmailPicker.vue2.js","sources":["../../../../src/components/CnEmailPicker/CnEmailPicker.vue"],"sourcesContent":["<!--\n CnEmailPicker — multi-step modal for picking an existing NC Mail\n message to link to the parent OR object.\n\n Flow (three steps + confirm):\n 1. Choose an account — GET /api/integrations/email/accounts\n 2. Choose a mailbox — GET /api/integrations/email/accounts/{id}/mailboxes\n 3. Browse messages — GET /api/integrations/email/accounts/{id}/messages?mailbox=\n (cursor pagination + client-side search filter)\n 4. Confirm — emit `link` with `{ mailAccountId, messageId, messageUid }`\n\n All API calls are wrapped in best-effort try/catch so a transient\n Mail failure surfaces a user-visible inline error rather than a modal\n crash. The modal stays open across errors so the user can retry\n without losing context.\n\n ADR-004: modal lives in its own .vue file under\n `src/components/CnEmailPicker/`. Picker UX is dialog-shaped so this\n variant uses NcDialog (matches CnDeckCardPicker).\n\n ADR-019: drives the `email` integration leaf's \"link existing\"\n surface; emits `link` so the parent (CnEmailTab) can POST the\n selection to the OR endpoint.\n\n SPDX-License-Identifier: EUPL-1.2\n SPDX-FileCopyrightText: 2026 Conduction B.V.\n-->\n<template>\n\t<NcDialog\n\t\t:name=\"dialogTitle\"\n\t\tsize=\"normal\"\n\t\tdata-testid=\"cn-email-picker\"\n\t\t@closing=\"onClose\">\n\t\t<div class=\"cn-email-picker\">\n\t\t\t<ol class=\"cn-email-picker__steps\">\n\t\t\t\t<li :class=\"stepClass(1)\">\n\t\t\t\t\t{{ t('nextcloud-vue', '1. Account') }}\n\t\t\t\t</li>\n\t\t\t\t<li :class=\"stepClass(2)\">\n\t\t\t\t\t{{ t('nextcloud-vue', '2. Mailbox') }}\n\t\t\t\t</li>\n\t\t\t\t<li :class=\"stepClass(3)\">\n\t\t\t\t\t{{ t('nextcloud-vue', '3. Message') }}\n\t\t\t\t</li>\n\t\t\t</ol>\n\n\t\t\t<NcNoteCard v-if=\"error\" type=\"error\" class=\"cn-email-picker__error\">\n\t\t\t\t{{ error }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<section v-if=\"step === 1\" class=\"cn-email-picker__panel\">\n\t\t\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t\t\t<NcEmptyContent\n\t\t\t\t\tv-else-if=\"accounts.length === 0\"\n\t\t\t\t\t:name=\"t('nextcloud-vue', 'No mail accounts')\"\n\t\t\t\t\t:description=\"t('nextcloud-vue', 'NC Mail has no accounts configured for you yet.')\" />\n\t\t\t\t<ul v-else class=\"cn-email-picker__list\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"account in accounts\"\n\t\t\t\t\t\t:key=\"account.id\"\n\t\t\t\t\t\tclass=\"cn-email-picker__row\"\n\t\t\t\t\t\t:class=\"{ 'cn-email-picker__row--selected': selectedAccountId === account.id }\">\n\t\t\t\t\t\t<button type=\"button\" class=\"cn-email-picker__row-button\" @click=\"pickAccount(account)\">\n\t\t\t\t\t\t\t<EmailOutline :size=\"20\" />\n\t\t\t\t\t\t\t<span class=\"cn-email-picker__row-label\">\n\t\t\t\t\t\t\t\t<span class=\"cn-email-picker__row-primary\">{{ account.label }}</span>\n\t\t\t\t\t\t\t\t<span v-if=\"account.email && account.email !== account.label\" class=\"cn-email-picker__row-secondary\">{{ account.email }}</span>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</section>\n\n\t\t\t<section v-else-if=\"step === 2\" class=\"cn-email-picker__panel\">\n\t\t\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t\t\t<NcEmptyContent\n\t\t\t\t\tv-else-if=\"mailboxes.length === 0\"\n\t\t\t\t\t:name=\"t('nextcloud-vue', 'No mailboxes')\"\n\t\t\t\t\t:description=\"t('nextcloud-vue', 'This account has no mailboxes to browse.')\" />\n\t\t\t\t<ul v-else class=\"cn-email-picker__list\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"mb in mailboxes\"\n\t\t\t\t\t\t:key=\"mb.id\"\n\t\t\t\t\t\tclass=\"cn-email-picker__row\"\n\t\t\t\t\t\t:class=\"{ 'cn-email-picker__row--selected': selectedMailbox === mb.name }\">\n\t\t\t\t\t\t<button type=\"button\" class=\"cn-email-picker__row-button\" @click=\"pickMailbox(mb)\">\n\t\t\t\t\t\t\t<FolderOutline :size=\"20\" />\n\t\t\t\t\t\t\t<span class=\"cn-email-picker__row-label\">\n\t\t\t\t\t\t\t\t<span class=\"cn-email-picker__row-primary\">{{ mb.displayName }}</span>\n\t\t\t\t\t\t\t\t<span v-if=\"mb.name !== mb.displayName\" class=\"cn-email-picker__row-secondary\">{{ mb.name }}</span>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</section>\n\n\t\t\t<section v-else-if=\"step === 3\" class=\"cn-email-picker__panel\">\n\t\t\t\t<NcTextField\n\t\t\t\t\tv-model=\"filterText\"\n\t\t\t\t\t:label=\"t('nextcloud-vue', 'Filter messages')\"\n\t\t\t\t\tclass=\"cn-email-picker__filter\" />\n\n\t\t\t\t<NcLoadingIcon v-if=\"loading && messages.length === 0\" />\n\t\t\t\t<NcEmptyContent\n\t\t\t\t\tv-else-if=\"filteredMessages.length === 0\"\n\t\t\t\t\t:name=\"t('nextcloud-vue', 'No messages')\"\n\t\t\t\t\t:description=\"t('nextcloud-vue', 'This mailbox has no messages matching your filter.')\" />\n\t\t\t\t<ul v-else class=\"cn-email-picker__list cn-email-picker__list--messages\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"msg in filteredMessages\"\n\t\t\t\t\t\t:key=\"msg.id\"\n\t\t\t\t\t\tclass=\"cn-email-picker__row\"\n\t\t\t\t\t\t:class=\"{ 'cn-email-picker__row--selected': selectedMessageId === msg.id }\">\n\t\t\t\t\t\t<button type=\"button\" class=\"cn-email-picker__row-button cn-email-picker__row-button--message\" @click=\"pickMessage(msg)\">\n\t\t\t\t\t\t\t<EmailOutline :size=\"18\" />\n\t\t\t\t\t\t\t<span class=\"cn-email-picker__message\">\n\t\t\t\t\t\t\t\t<span class=\"cn-email-picker__message-subject\">{{ msg.subject || t('nextcloud-vue', '(no subject)') }}</span>\n\t\t\t\t\t\t\t\t<span class=\"cn-email-picker__message-meta\">\n\t\t\t\t\t\t\t\t\t<span class=\"cn-email-picker__message-sender\">{{ msg.sender || t('nextcloud-vue', 'Unknown sender') }}</span>\n\t\t\t\t\t\t\t\t\t<span v-if=\"msg.date\" class=\"cn-email-picker__message-date\">{{ formatDate(msg.date) }}</span>\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\n\t\t\t\t<NcButton\n\t\t\t\t\tv-if=\"nextCursor !== null\"\n\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t:disabled=\"loadingMore\"\n\t\t\t\t\tclass=\"cn-email-picker__load-more\"\n\t\t\t\t\t@click=\"loadMoreMessages\">\n\t\t\t\t\t<template v-if=\"loadingMore\" #icon>\n\t\t\t\t\t\t<NcLoadingIcon :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ t('nextcloud-vue', 'Load more') }}\n\t\t\t\t</NcButton>\n\t\t\t</section>\n\t\t</div>\n\n\t\t<template #actions>\n\t\t\t<NcButton v-if=\"step > 1\" @click=\"goBack\">\n\t\t\t\t{{ t('nextcloud-vue', 'Back') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton @click=\"onClose\">\n\t\t\t\t{{ t('nextcloud-vue', 'Cancel') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton\n\t\t\t\tv-if=\"step === 3\"\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:disabled=\"!selectedMessageId\"\n\t\t\t\t@click=\"confirm\">\n\t\t\t\t{{ t('nextcloud-vue', 'Link message') }}\n\t\t\t</NcButton>\n\t\t</template>\n\t</NcDialog>\n</template>\n\n<script>\n/**\n * SPDX-License-Identifier: EUPL-1.2\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n *\n * CnEmailPicker — pick an existing Mail message via account, mailbox,\n * message. Emits `link` with `{ mailAccountId, messageId, messageUid }`.\n *\n * @see ADR-019 (pluggable integrations) and ADR-022 (sidebar tabs)\n */\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcDialog, NcEmptyContent, NcLoadingIcon, NcNoteCard, NcTextField } from '@nextcloud/vue'\nimport EmailOutline from 'vue-material-design-icons/EmailOutline.vue'\nimport FolderOutline from 'vue-material-design-icons/FolderOutline.vue'\nimport { buildHeaders, prefixUrl } from '../../utils/index.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\nexport default {\n\tname: 'CnEmailPicker',\n\n\tcomponents: { NcButton, NcDialog, NcEmptyContent, NcLoadingIcon, NcNoteCard, NcTextField, EmailOutline, FolderOutline },\n\n\tprops: {\n\t\t/** Base API URL for OR. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Pre-translated dialog title. */\n\t\tdialogTitle: { type: String, default: () => t('nextcloud-vue', 'Link an existing email') },\n\t\t/** Page size for the message list (load-more pagination). */\n\t\tpageSize: { type: Number, default: 50 },\n\t},\n\n\temits: ['close', 'link'],\n\n\tdata() {\n\t\treturn {\n\t\t\tstep: 1,\n\t\t\tloading: false,\n\t\t\tloadingMore: false,\n\t\t\terror: '',\n\t\t\taccounts: [],\n\t\t\tmailboxes: [],\n\t\t\tmessages: [],\n\t\t\tselectedAccountId: null,\n\t\t\tselectedMailbox: null,\n\t\t\tselectedMessageId: null,\n\t\t\tselectedMessageUid: null,\n\t\t\tnextCursor: null,\n\t\t\tfilterText: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tfilteredMessages() {\n\t\t\tconst needle = this.filterText.trim().toLowerCase()\n\t\t\tif (needle === '') {\n\t\t\t\treturn this.messages\n\t\t\t}\n\t\t\treturn this.messages.filter((msg) => {\n\t\t\t\tconst subj = (msg.subject || '').toLowerCase()\n\t\t\t\tconst sender = (msg.sender || '').toLowerCase()\n\t\t\t\treturn subj.includes(needle) || sender.includes(needle)\n\t\t\t})\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.fetchAccounts()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\tstepClass(step) {\n\t\t\treturn {\n\t\t\t\t'cn-email-picker__step': true,\n\t\t\t\t'cn-email-picker__step--active': this.step === step,\n\t\t\t\t'cn-email-picker__step--done': this.step > step,\n\t\t\t}\n\t\t},\n\n\t\tasync fetchAccounts() {\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.apiBase}/integrations/email/accounts`), {\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthis.accounts = data.results || []\n\t\t\t\t} else if (response.status === 501) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Nextcloud Mail is not installed.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load mail accounts.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnEmailPicker] fetch accounts failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load mail accounts.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchMailboxes(accountId) {\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.apiBase}/integrations/email/accounts/${accountId}/mailboxes`), {\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthis.mailboxes = data.results || []\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load mailboxes.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnEmailPicker] fetch mailboxes failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load mailboxes.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchMessages(accountId, mailbox, append = false) {\n\t\t\tif (append === true) {\n\t\t\t\tthis.loadingMore = true\n\t\t\t} else {\n\t\t\t\tthis.loading = true\n\t\t\t\tthis.messages = []\n\t\t\t\tthis.nextCursor = null\n\t\t\t}\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst params = new URLSearchParams({\n\t\t\t\t\tmailbox,\n\t\t\t\t\tlimit: String(this.pageSize),\n\t\t\t\t})\n\t\t\t\tif (append === true && this.nextCursor !== null) {\n\t\t\t\t\tparams.set('cursor', String(this.nextCursor))\n\t\t\t\t}\n\t\t\t\tconst response = await fetch(\n\t\t\t\t\tprefixUrl(`${this.apiBase}/integrations/email/accounts/${accountId}/messages?${params.toString()}`),\n\t\t\t\t\t{ headers: buildHeaders() },\n\t\t\t\t)\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tconst items = Array.isArray(data.items) ? data.items : []\n\t\t\t\t\tthis.messages = append === true ? [...this.messages, ...items] : items\n\t\t\t\t\tthis.nextCursor = data.nextCursor !== undefined ? data.nextCursor : null\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load messages.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnEmailPicker] fetch messages failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load messages.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t\tthis.loadingMore = false\n\t\t\t}\n\t\t},\n\n\t\tpickAccount(account) {\n\t\t\tthis.selectedAccountId = account.id\n\t\t\tthis.selectedMailbox = null\n\t\t\tthis.selectedMessageId = null\n\t\t\tthis.selectedMessageUid = null\n\t\t\tthis.mailboxes = []\n\t\t\tthis.messages = []\n\t\t\tthis.step = 2\n\t\t\tthis.fetchMailboxes(account.id)\n\t\t},\n\n\t\tpickMailbox(mailbox) {\n\t\t\tthis.selectedMailbox = mailbox.name\n\t\t\tthis.selectedMessageId = null\n\t\t\tthis.selectedMessageUid = null\n\t\t\tthis.messages = []\n\t\t\tthis.filterText = ''\n\t\t\tthis.step = 3\n\t\t\tthis.fetchMessages(this.selectedAccountId, mailbox.name)\n\t\t},\n\n\t\tpickMessage(msg) {\n\t\t\tthis.selectedMessageId = msg.id\n\t\t\tthis.selectedMessageUid = msg.uid || ''\n\t\t},\n\n\t\tloadMoreMessages() {\n\t\t\tif (this.nextCursor === null || this.selectedAccountId === null || this.selectedMailbox === null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.fetchMessages(this.selectedAccountId, this.selectedMailbox, true)\n\t\t},\n\n\t\tgoBack() {\n\t\t\tif (this.step === 3) {\n\t\t\t\tthis.selectedMessageId = null\n\t\t\t\tthis.selectedMessageUid = null\n\t\t\t\tthis.messages = []\n\t\t\t\tthis.nextCursor = null\n\t\t\t\tthis.filterText = ''\n\t\t\t\tthis.step = 2\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.step === 2) {\n\t\t\t\tthis.selectedMailbox = null\n\t\t\t\tthis.mailboxes = []\n\t\t\t\tthis.step = 1\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Dismiss the dialog.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonClose() {\n\t\t\t/**\n\t\t\t * @event close Emitted when the dialog should be closed (cancel or close button).\n\t\t\t */\n\t\t\tthis.$emit('close')\n\t\t},\n\n\t\tconfirm() {\n\t\t\tif (this.selectedMessageId === null || this.selectedAccountId === null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t/**\n\t\t\t * @event link Emitted when the user confirms the selection. Payload: `{ mailAccountId, messageId, messageUid }`.\n\t\t\t */\n\t\t\tthis.$emit('link', {\n\t\t\t\tmailAccountId: this.selectedAccountId,\n\t\t\t\tmessageId: String(this.selectedMessageId),\n\t\t\t\tmessageUid: String(this.selectedMessageUid || ''),\n\t\t\t})\n\t\t},\n\n\t\tformatDate(raw) {\n\t\t\tif (!raw) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst d = new Date(raw)\n\t\t\tif (Number.isNaN(d.getTime())) {\n\t\t\t\treturn String(raw)\n\t\t\t}\n\t\t\treturn d.toLocaleDateString(userLocale(), { year: 'numeric', month: 'short', day: 'numeric' })\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-email-picker {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\tpadding: 4px 0;\n}\n\n.cn-email-picker__steps {\n\tdisplay: flex;\n\tgap: 16px;\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0 4px 8px;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-email-picker__step {\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-email-picker__step--active {\n\tcolor: var(--color-main-text);\n\tfont-weight: 600;\n}\n\n.cn-email-picker__step--done {\n\tcolor: var(--color-success, #46ba61);\n}\n\n.cn-email-picker__error {\n\tmargin: 4px 0;\n}\n\n.cn-email-picker__panel {\n\tmin-height: 180px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n}\n\n.cn-email-picker__filter {\n\twidth: 100%;\n}\n\n.cn-email-picker__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\tmax-height: 360px;\n\toverflow-y: auto;\n}\n\n.cn-email-picker__list--messages {\n\tmax-height: 320px;\n}\n\n.cn-email-picker__row {\n\tborder-radius: var(--border-radius);\n}\n\n.cn-email-picker__row--selected {\n\tbackground: var(--color-primary-element-light);\n}\n\n.cn-email-picker__row-button {\n\tdisplay: flex;\n\twidth: 100%;\n\talign-items: center;\n\tgap: 10px;\n\tpadding: 8px 10px;\n\tbackground: transparent;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tcolor: var(--color-main-text);\n\tcursor: pointer;\n\ttext-align: left;\n}\n\n.cn-email-picker__row-button:hover {\n\tbackground: var(--color-background-hover);\n}\n\n.cn-email-picker__row--selected .cn-email-picker__row-button {\n\tborder-color: var(--color-primary-element);\n}\n\n.cn-email-picker__row-button--message {\n\talign-items: flex-start;\n}\n\n.cn-email-picker__row-label {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tflex: 1 1 auto;\n\toverflow: hidden;\n}\n\n.cn-email-picker__row-primary {\n\tfont-weight: 500;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-email-picker__row-secondary {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-email-picker__message {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tflex: 1 1 auto;\n\toverflow: hidden;\n}\n\n.cn-email-picker__message-subject {\n\tfont-weight: 500;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-email-picker__message-meta {\n\tdisplay: flex;\n\tgap: 8px;\n\tjustify-content: space-between;\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-email-picker__message-sender {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-email-picker__message-date {\n\tflex-shrink: 0;\n}\n\n.cn-email-picker__load-more {\n\tmargin-top: 4px;\n}\n</style>\n"],"names":["t"],"mappings":"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"}