@conduction/nextcloud-vue 2.56.0-beta.36 → 2.56.0-beta.38
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.
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +131 -3
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +54 -18
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
- package/dist/esm/components/CnAdvancedFormDialog/CnMetadataTab.vue.js +1 -1
- package/dist/esm/components/CnAdvancedFormDialog/CnMetadataTab.vue2.js +3 -2
- package/dist/esm/components/CnAdvancedFormDialog/CnMetadataTab.vue2.js.map +1 -1
- package/dist/esm/components/CnAdvancedFormDialog/CnMetadataTab.vue3.js.map +1 -1
- package/dist/esm/components/CnAdvancedFormDialog/CnPropertyValueCell.vue.js +1 -1
- package/dist/esm/components/CnAdvancedFormDialog/CnPropertyValueCell.vue2.js +5 -4
- package/dist/esm/components/CnAdvancedFormDialog/CnPropertyValueCell.vue2.js.map +1 -1
- package/dist/esm/components/CnAdvancedFormDialog/CnPropertyValueCell.vue3.js.map +1 -1
- package/dist/esm/components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue.js +1 -1
- package/dist/esm/components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue2.js +2 -1
- package/dist/esm/components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue2.js.map +1 -1
- package/dist/esm/components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue3.js.map +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue.js +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +2 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue.js +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +376 -6
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +146 -71
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
- package/dist/esm/components/CnCalendarEventPicker/CnCalendarEventPicker.vue.js +1 -1
- package/dist/esm/components/CnCalendarEventPicker/CnCalendarEventPicker.vue2.js +2 -1
- package/dist/esm/components/CnCalendarEventPicker/CnCalendarEventPicker.vue2.js.map +1 -1
- package/dist/esm/components/CnCalendarEventPicker/CnCalendarEventPicker.vue3.js.map +1 -1
- package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue.js +1 -1
- package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue2.js +2 -1
- package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +6 -5
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnCollectivePagePicker/CnCollectivePagePicker.vue.js +1 -1
- package/dist/esm/components/CnCollectivePagePicker/CnCollectivePagePicker.vue2.js +2 -1
- package/dist/esm/components/CnCollectivePagePicker/CnCollectivePagePicker.vue2.js.map +1 -1
- package/dist/esm/components/CnCollectivePagePicker/CnCollectivePagePicker.vue3.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js +2 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue.js +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js +2 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +47 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +22 -12
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataMatrix/CnDataMatrix.vue.js +1 -1
- package/dist/esm/components/CnDataMatrix/CnDataMatrix.vue2.js +3 -1
- package/dist/esm/components/CnDataMatrix/CnDataMatrix.vue2.js.map +1 -1
- package/dist/esm/components/CnDataMatrix/CnDataMatrix.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailHeaderChips.vue2.js +2 -1
- package/dist/esm/components/CnDetailPage/CnDetailHeaderChips.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailHeaderChips.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +23 -4
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +3 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnEmailPicker/CnEmailPicker.vue.js +1 -1
- package/dist/esm/components/CnEmailPicker/CnEmailPicker.vue2.js +2 -1
- package/dist/esm/components/CnEmailPicker/CnEmailPicker.vue2.js.map +1 -1
- package/dist/esm/components/CnEmailPicker/CnEmailPicker.vue3.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +18 -5
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFederationStatus/CnFederationStatus.vue.js +1 -1
- package/dist/esm/components/CnFederationStatus/CnFederationStatus.vue2.js +3 -1
- package/dist/esm/components/CnFederationStatus/CnFederationStatus.vue2.js.map +1 -1
- package/dist/esm/components/CnFederationStatus/CnFederationStatus.vue3.js.map +1 -1
- package/dist/esm/components/CnFileManager/CnFileManager.vue.js +1 -1
- package/dist/esm/components/CnFileManager/CnFileManager.vue2.js +3 -1
- package/dist/esm/components/CnFileManager/CnFileManager.vue2.js.map +1 -1
- package/dist/esm/components/CnFileManager/CnFileManager.vue3.js.map +1 -1
- package/dist/esm/components/CnFilePreview/CnFilePreview.vue.js +1 -1
- package/dist/esm/components/CnFilePreview/CnFilePreview.vue2.js +2 -1
- package/dist/esm/components/CnFilePreview/CnFilePreview.vue2.js.map +1 -1
- package/dist/esm/components/CnFilePreview/CnFilePreview.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +49 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +3 -3
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue.js +1 -1
- package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue2.js +2 -1
- package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue2.js.map +1 -1
- package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +571 -77
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +191 -82
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js +11 -2
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
- package/dist/esm/components/CnNotesCard/CnNotesCard.vue.js +1 -1
- package/dist/esm/components/CnNotesCard/CnNotesCard.vue2.js +2 -1
- package/dist/esm/components/CnNotesCard/CnNotesCard.vue2.js.map +1 -1
- package/dist/esm/components/CnNotesCard/CnNotesCard.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectCalendar/CnObjectCalendar.vue.js +1 -1
- package/dist/esm/components/CnObjectCalendar/CnObjectCalendar.vue2.js +3 -2
- package/dist/esm/components/CnObjectCalendar/CnObjectCalendar.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectCalendar/CnObjectCalendar.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +28 -0
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js +82 -75
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue2.js +5 -4
- package/dist/esm/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnAuditTrailTab.vue.js +1 -1
- package/dist/esm/components/CnObjectSidebar/CnAuditTrailTab.vue2.js +2 -1
- package/dist/esm/components/CnObjectSidebar/CnAuditTrailTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnAuditTrailTab.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFlowTasksPanel.vue.js +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFlowTasksPanel.vue2.js +2 -1
- package/dist/esm/components/CnObjectSidebar/CnFlowTasksPanel.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFlowTasksPanel.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue.js +1 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js +2 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnTasksTab.vue.js +1 -1
- package/dist/esm/components/CnObjectSidebar/CnTasksTab.vue2.js +2 -1
- package/dist/esm/components/CnObjectSidebar/CnTasksTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnTasksTab.vue3.js.map +1 -1
- package/dist/esm/components/CnPropertySourceField/CnPropertySourceField.vue.js +1 -1
- package/dist/esm/components/CnPropertySourceField/CnPropertySourceField.vue2.js +2 -1
- package/dist/esm/components/CnPropertySourceField/CnPropertySourceField.vue2.js.map +1 -1
- package/dist/esm/components/CnPropertySourceField/CnPropertySourceField.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +70 -2
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue.js +1 -1
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js +67 -5
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js.map +1 -1
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js +181 -124
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js.map +1 -1
- package/dist/esm/components/CnRoadmapItem/CnRoadmapItem.vue.js +1 -1
- package/dist/esm/components/CnRoadmapItem/CnRoadmapItem.vue2.js +2 -1
- package/dist/esm/components/CnRoadmapItem/CnRoadmapItem.vue2.js.map +1 -1
- package/dist/esm/components/CnRoadmapItem/CnRoadmapItem.vue3.js.map +1 -1
- package/dist/esm/components/CnSchemaFormDialog/CnSchemaFormDialog.vue2.js +2 -0
- package/dist/esm/components/CnSchemaFormDialog/CnSchemaFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnSchemaFormDialog/CnSchemaFormDialog.vue3.js +2 -2
- package/dist/esm/components/CnSchemaFormDialog/CnSchemaFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue.js +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js +5 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue.js +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +134 -10
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +6 -2
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue.js +1 -1
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js +2 -1
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js.map +1 -1
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue3.js.map +1 -1
- package/dist/esm/components/CnSummaryAggregates/CnSummaryAggregates.vue.js +1 -1
- package/dist/esm/components/CnSummaryAggregates/CnSummaryAggregates.vue2.js +2 -1
- package/dist/esm/components/CnSummaryAggregates/CnSummaryAggregates.vue2.js.map +1 -1
- package/dist/esm/components/CnSummaryAggregates/CnSummaryAggregates.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +6 -3
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTasksCard/CnTasksCard.vue.js +1 -1
- package/dist/esm/components/CnTasksCard/CnTasksCard.vue2.js +2 -1
- package/dist/esm/components/CnTasksCard/CnTasksCard.vue2.js.map +1 -1
- package/dist/esm/components/CnTasksCard/CnTasksCard.vue3.js.map +1 -1
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js +1 -1
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js +2 -1
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js +1 -1
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js +140 -7
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTranslatedBadge/CnTranslatedBadge.vue.js +1 -1
- package/dist/esm/components/CnTranslatedBadge/CnTranslatedBadge.vue2.js +2 -1
- package/dist/esm/components/CnTranslatedBadge/CnTranslatedBadge.vue2.js.map +1 -1
- package/dist/esm/components/CnVersionHistory/CnVersionHistory.vue.js +1 -1
- package/dist/esm/components/CnVersionHistory/CnVersionHistory.vue2.js +2 -1
- package/dist/esm/components/CnVersionHistory/CnVersionHistory.vue2.js.map +1 -1
- package/dist/esm/components/CnVersionHistory/CnVersionHistory.vue3.js.map +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue.js +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js +37 -9
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js.map +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js +6 -5
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue2.js +53 -1
- package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue3.js +3 -2
- package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue.js +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +135 -0
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue.js +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +19 -0
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js +37 -19
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
- package/dist/esm/composables/useListView.js +21 -0
- package/dist/esm/composables/useListView.js.map +1 -1
- package/dist/esm/dialogs/CnNoteHistoryDialog.vue.js +1 -1
- package/dist/esm/dialogs/CnNoteHistoryDialog.vue2.js +2 -1
- package/dist/esm/dialogs/CnNoteHistoryDialog.vue2.js.map +1 -1
- package/dist/esm/dialogs/CnNoteHistoryDialog.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/activity/CnActivityCard.vue.js +1 -1
- package/dist/esm/integrations/builtin/activity/CnActivityCard.vue2.js +3 -2
- package/dist/esm/integrations/builtin/activity/CnActivityCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/activity/CnActivityCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/activity/CnActivityTab.vue.js +1 -1
- package/dist/esm/integrations/builtin/activity/CnActivityTab.vue2.js +4 -3
- package/dist/esm/integrations/builtin/activity/CnActivityTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/activity/CnActivityTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue.js +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js +3 -2
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue.js +1 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue.js +1 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue2.js +6 -5
- package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/contactmoment/CnContactmomentCard.vue.js +1 -1
- package/dist/esm/integrations/builtin/contactmoment/CnContactmomentCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/contactmoment/CnContactmomentCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/contactmoment/CnContactmomentCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue.js +1 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue.js +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/email/CnEmailCard.vue.js +1 -1
- package/dist/esm/integrations/builtin/email/CnEmailCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/email/CnEmailCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/email/CnEmailCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue.js +1 -1
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionTab.vue.js +1 -1
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue.js +1 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js.map +1 -1
- package/dist/esm/l10n/en.json.js +57 -2
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +78 -2
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +1541 -1166
- package/dist/esm/store/useObjectStore.js +23 -4
- package/dist/esm/store/useObjectStore.js.map +1 -1
- package/dist/esm/utils/boardColumns.js +58 -2
- package/dist/esm/utils/boardColumns.js.map +1 -1
- package/dist/esm/utils/boardReferenceColumns.js +330 -0
- package/dist/esm/utils/boardReferenceColumns.js.map +1 -0
- package/dist/esm/utils/buildManifest.js +4 -1
- package/dist/esm/utils/buildManifest.js.map +1 -1
- package/dist/esm/utils/schema.js +5 -4
- package/dist/esm/utils/schema.js.map +1 -1
- package/dist/esm/utils/userLocale.js +30 -0
- package/dist/esm/utils/userLocale.js.map +1 -0
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/esm/utils/widgetDispatch.js +8 -2
- package/dist/esm/utils/widgetDispatch.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +30291 -27618
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +1541 -1166
- package/l10n/en.json +57 -2
- package/l10n/nl.json +78 -2
- package/package.json +1 -1
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +160 -8
- package/src/components/CnAdvancedFormDialog/CnMetadataTab.vue +3 -2
- package/src/components/CnAdvancedFormDialog/CnPropertyValueCell.vue +5 -4
- package/src/components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue +2 -1
- package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +2 -1
- package/src/components/CnBoardView/CnBoardView.vue +433 -12
- package/src/components/CnCalendarEventPicker/CnCalendarEventPicker.vue +2 -1
- package/src/components/CnCalendarWidget/CnCalendarWidget.vue +2 -1
- package/src/components/CnChartWidget/CnChartWidget.vue +6 -5
- package/src/components/CnCollectivePagePicker/CnCollectivePagePicker.vue +2 -1
- package/src/components/CnConversationThread/CnConversationThread.vue +2 -1
- package/src/components/CnCountdownWidget/CnCountdownWidget.vue +2 -1
- package/src/components/CnDashboardPage/CnDashboardPage.vue +59 -3
- package/src/components/CnDataMatrix/CnDataMatrix.vue +2 -1
- package/src/components/CnDetailPage/CnDetailHeaderChips.vue +2 -1
- package/src/components/CnDetailPage/CnDetailPage.md +1 -1
- package/src/components/CnDetailPage/CnDetailPage.vue +23 -4
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +3 -1
- package/src/components/CnEmailPicker/CnEmailPicker.vue +2 -1
- package/src/components/CnFactsWidget/CnFactsWidget.vue +18 -5
- package/src/components/CnFederationStatus/CnFederationStatus.vue +3 -1
- package/src/components/CnFileManager/CnFileManager.vue +2 -1
- package/src/components/CnFilePreview/CnFilePreview.vue +2 -1
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +54 -6
- package/src/components/CnFollowToggle/CnFollowToggle.vue +2 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +610 -25
- package/src/components/CnIndexPage/useSelfFetchList.js +10 -1
- package/src/components/CnNotesCard/CnNotesCard.vue +2 -1
- package/src/components/CnObjectCalendar/CnObjectCalendar.vue +3 -2
- package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +36 -0
- package/src/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue +5 -4
- package/src/components/CnObjectSidebar/CnAuditTrailTab.vue +2 -1
- package/src/components/CnObjectSidebar/CnFlowTasksPanel.vue +2 -1
- package/src/components/CnObjectSidebar/CnNotesTab.vue +2 -1
- package/src/components/CnObjectSidebar/CnTasksTab.vue +2 -1
- package/src/components/CnPropertySourceField/CnPropertySourceField.vue +2 -1
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +70 -2
- package/src/components/CnRegisterMapping/CnRegisterMapping.md +5 -0
- package/src/components/CnRegisterMapping/CnRegisterMapping.vue +231 -96
- package/src/components/CnRoadmapItem/CnRoadmapItem.vue +2 -1
- package/src/components/CnSchemaFormDialog/CnSchemaFormDialog.vue +4 -2
- package/src/components/CnSettingsPage/CnSettingsPage.vue +5 -1
- package/src/components/CnStatWidget/CnStatWidget.vue +136 -11
- package/src/components/CnStatsBlock/CnStatsBlock.vue +2 -1
- package/src/components/CnSummaryAggregates/CnSummaryAggregates.vue +2 -1
- package/src/components/CnTabsWidget/CnTabsWidget.vue +6 -3
- package/src/components/CnTasksCard/CnTasksCard.vue +2 -1
- package/src/components/CnTimelineWidget/CnTimelineWidget.vue +2 -1
- package/src/components/CnToggleTableWidget/CnToggleTableWidget.vue +140 -7
- package/src/components/CnTranslatedBadge/CnTranslatedBadge.vue +2 -1
- package/src/components/CnVersionHistory/CnVersionHistory.vue +2 -1
- package/src/components/CnVersionInfoCard/CnVersionInfoCard.md +2 -1
- package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +42 -13
- package/src/components/CnWidgetCardGrid/CnWidgetCardGrid.vue +55 -2
- package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +135 -0
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.md +10 -0
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +44 -1
- package/src/composables/useListView.js +21 -0
- package/src/css/board.css +105 -4
- package/src/css/dialog.css +4 -2
- package/src/css/index-page.css +49 -0
- package/src/css/look-board.css +77 -7
- package/src/dialogs/CnNoteHistoryDialog.vue +2 -1
- package/src/integrations/builtin/activity/CnActivityCard.vue +3 -2
- package/src/integrations/builtin/activity/CnActivityTab.vue +4 -3
- package/src/integrations/builtin/analytics/CnAnalyticsTab.vue +3 -2
- package/src/integrations/builtin/calendar/CnCalendarCard.vue +2 -1
- package/src/integrations/builtin/calendar/CnCalendarTab.vue +6 -5
- package/src/integrations/builtin/contactmoment/CnContactmomentCard.vue +2 -1
- package/src/integrations/builtin/cospend/CnCospendTab.vue +2 -1
- package/src/integrations/builtin/deck/CnDeckTab.vue +2 -1
- package/src/integrations/builtin/email/CnEmailCard.vue +2 -1
- package/src/integrations/builtin/field-inspection/CnFieldInspectionCard.vue +2 -1
- package/src/integrations/builtin/field-inspection/CnFieldInspectionTab.vue +2 -1
- package/src/integrations/builtin/photos/CnPhotosTab.vue +2 -1
- package/src/schemas/app-manifest-v2.schema.json +116 -5
- package/src/store/useObjectStore.js +23 -4
- package/src/utils/boardColumns.js +57 -1
- package/src/utils/boardReferenceColumns.js +326 -0
- package/src/utils/buildManifest.js +4 -1
- package/src/utils/schema.js +5 -4
- package/src/utils/userLocale.js +26 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
- package/src/utils/widgetDispatch.js +8 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnStatsBlock.vue2.js","sources":["../../../../src/components/CnStatsBlock/CnStatsBlock.vue"],"sourcesContent":["<!--\n - CnStatsBlock renders the CANONICAL KPI card (src/css/kpi-card.css) — the\n - same markup and the same stylesheet CnStatWidget renders, so OpenCatalogi's\n - KPIs and dossiq's are one design rather than two. This component's own\n - `cn-stats-block*` classes stay on the same elements for backwards\n - compatibility with app CSS that targets them; the LOOK comes from the\n - shared sheet.\n -\n - The comment lives OUT here rather than inside <template>: a comment node\n - beside the root element makes the component multi-root in Vue 3, and a\n - multi-root component has no root to put class attributes on — `rootClasses`\n - silently stopped reaching the DOM.\n-->\n<template>\n\t<component\n\t\t:is=\"componentTag\"\n\t\tclass=\"cn-kpi-card cn-stats-block\"\n\t\t:class=\"rootClasses\"\n\t\tv-bind=\"componentAttrs\"\n\t\t@click=\"onClick\">\n\t\t<!-- Icon -->\n\t\t<div v-if=\"hasIcon && !isStacked && !isBoardLook\" class=\"cn-kpi-card__icon cn-stats-block__icon\" :class=\"iconClasses\">\n\t\t\t<slot name=\"icon\">\n\t\t\t\t<component :is=\"icon\" v-if=\"icon\" :size=\"iconSize\" />\n\t\t\t</slot>\n\t\t</div>\n\n\t\t<!-- Content -->\n\t\t<div class=\"cn-kpi-card__body cn-stats-block__content\">\n\t\t\t<div class=\"cn-stats-block__header\">\n\t\t\t\t<!-- Board look: an 18px glyph before the label, only on a link tile. -->\n\t\t\t\t<span\n\t\t\t\t\tv-if=\"isBoardLook && isInteractive && hasIcon\"\n\t\t\t\t\tclass=\"cn-kpi-card__glyph cn-stats-block__glyph\"\n\t\t\t\t\tdata-testid=\"cn-stats-block-glyph\"\n\t\t\t\t\taria-hidden=\"true\">\n\t\t\t\t\t<slot name=\"icon\">\n\t\t\t\t\t\t<component :is=\"icon\" v-if=\"icon\" :size=\"18\" />\n\t\t\t\t\t</slot>\n\t\t\t\t</span>\n\t\t\t\t<h4 class=\"cn-kpi-card__title\" :title=\"title || undefined\">\n\t\t\t\t\t{{ title ? tl(title) : t('nextcloud-vue', 'Objects') }}\n\t\t\t\t</h4>\n\t\t\t</div>\n\n\t\t\t<div v-if=\"hasError\" class=\"cn-stats-block__count cn-stats-block__count--error\">\n\t\t\t\t<span class=\"cn-stats-block__count-value\">—</span>\n\t\t\t\t<span class=\"cn-stats-block__count-label\">{{ errorLabel }}</span>\n\t\t\t</div>\n\t\t\t<div v-else-if=\"hasValueSlot || count > 0 || (showZeroCount && count === 0)\" class=\"cn-kpi-card__value-row cn-stats-block__count\">\n\t\t\t\t<span class=\"cn-kpi-card__value cn-stats-block__count-value\">\n\t\t\t\t\t<!-- @slot Override the prominently-displayed value — render a pre-formatted string (currency, percent, a \"—\" placeholder, …). `count` stays the raw number; this is presentation only. Defaults to the localized count. -->\n\t\t\t\t\t<!-- @binding {number} count The raw numeric count. -->\n\t\t\t\t\t<!-- @binding {string} formatted The default localized count string. -->\n\t\t\t\t\t<slot name=\"value\" :count=\"count\" :formatted=\"formattedCount\">{{ formattedCount }}</slot>\n\t\t\t\t</span>\n\t\t\t\t<span class=\"cn-kpi-card__label cn-stats-block__count-label\">{{ countLabel ? tl(countLabel) : countLabel }}</span>\n\t\t\t</div>\n\t\t\t<div v-else-if=\"loading\" class=\"cn-stats-block__loading\">\n\t\t\t\t<NcLoadingIcon :size=\"16\" />\n\t\t\t\t{{ loadingLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-stats-block__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\n\t\t\t<!-- Breakdown details -->\n\t\t\t<div\n\t\t\t\tv-if=\"hasError === false && breakdown && (hasValueSlot || count > 0 || showZeroCount)\"\n\t\t\t\tclass=\"cn-stats-block__breakdown\">\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"(value, key) in breakdown\"\n\t\t\t\t\t:key=\"key\"\n\t\t\t\t\tclass=\"cn-stats-block__breakdown-item\">\n\t\t\t\t\t<span class=\"cn-stats-block__breakdown-label\">{{ formatBreakdownLabel(key) }}</span>\n\t\t\t\t\t<span\n\t\t\t\t\t\tclass=\"cn-stats-block__breakdown-value\"\n\t\t\t\t\t\t:class=\"'cn-stats-block__breakdown-value--' + key\">\n\t\t\t\t\t\t{{ value }}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</component>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { normalizeLook } from '../../composables/useLook.js'\n\n// The canonical KPI look, shared with CnStatWidget. Imported here so the\n// card is styled even when the consuming app has not pulled in the\n// library's global css/index.css.\nimport '../../css/kpi-card.css'\n\n/**\n * CnStatsBlock — Statistics display card with icon, count, and optional breakdown.\n *\n * Renders the canonical KPI card: a large circular icon beside a left-aligned\n * number, drawing no box of its own because the wrapper around it already\n * draws one. Supports colour variants, icons and clickable state. Use in a\n * CnKpiGrid for responsive dashboard layouts.\n *\n * Basic (canonical horizontal card)\n * ```vue\n * <CnStatsBlock title=\"Cases\" :count=\"42\" count-label=\"open cases\" />\n * ```\n *\n * With icon and variant\n * ```vue\n * <CnStatsBlock\n * title=\"Open Cases\"\n * :count=\"42\"\n * :icon=\"BriefcaseOutline\"\n * variant=\"primary\"\n * clickable\n * @click=\"goToCases\" />\n * ```\n *\n * Stacked and boxed — for a block mounted with no wrapper around it\n * ```vue\n * <CnStatsBlock title=\"Cases\" :count=\"42\" vertical filled />\n * ```\n *\n * With route-based navigation (renders as <router-link>)\n * ```vue\n * <CnStatsBlock\n * title=\"Open Cases\"\n * :count=\"42\"\n * :icon=\"BriefcaseOutline\"\n * variant=\"primary\"\n * :route=\"{ name: 'Cases', query: { status: 'open' } }\" />\n * ```\n *\n * With breakdown\n * ```vue\n * <CnStatsBlock\n * title=\"Cases\"\n * :count=\"42\"\n * :breakdown=\"{ total: 100, invalid: 3, deleted: 5, published: 92 }\" />\n * ```\n *\n * Custom icon slot\n * ```vue\n * <CnStatsBlock title=\"Files\" :count=\"128\">\n * <template #icon>\n * <FileDocumentOutline :size=\"24\" />\n * </template>\n * </CnStatsBlock>\n * ```\n */\nexport default {\n\tname: 'CnStatsBlock',\n\n\tcomponents: {\n\t\tNcLoadingIcon,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * The look the app is drawn in, provided by CnAppRoot.\n\t\t *\n\t\t * @spec openspec/changes/screens-dashboard-parity/specs/dashboard-page/spec.md#requirement-the-board-kpi-tile\n\t\t */\n\t\tcnLook: { default: 'nextcloud' },\n\t\t/**\n\t\t * The host's label lookup (CnAppRoot provides it); identity without one, so a\n\t\t * label that is not a key renders as written.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** Block title */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The main count number to display prominently */\n\t\tcount: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0,\n\t\t},\n\n\t\t/** Label displayed next to the count */\n\t\tcountLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'objects'),\n\t\t},\n\n\t\t/** Detailed breakdown object (key-value pairs) */\n\t\tbreakdown: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Whether data is currently loading */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Text shown while loading */\n\t\tloadingLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Loading…'),\n\t\t},\n\n\t\t/** Text shown when count is 0 */\n\t\temptyLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'No items found'),\n\t\t},\n\n\t\t/**\n\t\t * The tile could not load its number. Anything truthy counts — pass the\n\t\t * caught error itself, or just `true`.\n\t\t *\n\t\t * The tile then shows a dash and `errorLabel` INSTEAD of a count, and\n\t\t * tints itself `error`. This takes precedence over `count`, `loading`\n\t\t * and `emptyLabel` deliberately: a stale or defaulted number rendered\n\t\t * during a failure is the exact thing this prop exists to stop. A\n\t\t * dashboard showing 0 because the backend is down is worse than one\n\t\t * showing nothing, because 0 is a number a reader will believe.\n\t\t *\n\t\t * @type {boolean|string|Error|null}\n\t\t */\n\t\terror: {\n\t\t\t// TYPE ORDER IS SEMANTIC HERE. Vue casts an empty value to `true`\n\t\t\t// when Boolean appears BEFORE String in the union, so\n\t\t\t// `[Boolean, String]` would read `error=\"\"` as an error. A caller\n\t\t\t// clearing its message back to '' is reporting RECOVERY, and that\n\t\t\t// would have pinned the tile in its error state after the fetch\n\t\t\t// succeeded again. String first keeps '' falsy.\n\t\t\t// eslint-disable-next-line vue/prefer-prop-type-boolean-first -- Boolean first would make `error=\"\"` truthy, which is the recovery signal.\n\t\t\ttype: [String, Boolean, Object],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Text shown in place of the count when `error` is set. */\n\t\terrorLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Unavailable'),\n\t\t},\n\n\t\t/** Icon component (e.g., imported MDI icon) */\n\t\ticon: {\n\t\t\ttype: [Object, Function],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Icon size in pixels */\n\t\ticonSize: {\n\t\t\ttype: Number,\n\t\t\tdefault: 24,\n\t\t},\n\n\t\t/** Color variant: 'default', 'primary', 'success', 'warning', 'error' */\n\t\tvariant: {\n\t\t\ttype: String,\n\t\t\tdefault: 'default',\n\t\t\tvalidator: (v) => ['default', 'primary', 'success', 'warning', 'error'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Stack the icon above a centred number instead of placing it beside\n\t\t * one. The canonical KPI card is horizontal, so this is the opt-out;\n\t\t * `horizontal` below is kept only for callers that already pass it.\n\t\t */\n\t\tvertical: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * `stacked` is the board's tile, as CnStatWidget `content.layout:\n\t\t * \"stacked\"`: no icon circle, a 14px muted title, the number at\n\t\t * 34px/700 in the text colour. Empty (the default) keeps the KPI card.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-stats-block-can-take-the-stacked-board-look\n\t\t */\n\t\tlayout: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Lay the icon left of the content. No longer needed — this is the\n\t\t * canonical card's own layout — and kept so existing callers that pass\n\t\t * `horizontal` keep working. Pass `vertical` to stack instead.\n\t\t *\n\t\t * @deprecated since 2.25.0, the horizontal layout is the default.\n\t\t */\n\t\thorizontal: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Draw the card's own grey box. Off by default: a stats block is\n\t\t * normally rendered inside a wrapper that already draws a card, and a\n\t\t * second box reads as a card inside a card. Turn it on for a block\n\t\t * mounted with no wrapper around it.\n\t\t */\n\t\tfilled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Whether the card is clickable */\n\t\tclickable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Whether to display 0 as a count value instead of the empty label */\n\t\tshowZeroCount: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Vue Router location object for declarative navigation.\n\t\t * When set, the card renders as a <router-link> and clickable styles are implied.\n\t\t * { name: 'Cases', query: { status: 'open' } }\n\t\t * { path: '/catalogi' }\n\t\t */\n\t\troute: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['click'],\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the app takes the board look.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisBoardLook() {\n\t\t\treturn normalizeLook(this.cnLook) === 'board'\n\t\t},\n\n\t\thasIcon() {\n\t\t\treturn this.icon !== null || this.$slots.icon || this.$slots.icon\n\t\t},\n\n\t\t/**\n\t\t * Whether a consumer provided the `value` slot. When set, the value\n\t\t * area always renders (the slot decides what to show, even at count 0).\n\t\t */\n\t\thasValueSlot() {\n\t\t\treturn !!this.$slots.value || !!this.$slots.value\n\t\t},\n\n\t\t/**\n\t\t * Whether the tile is in its error state.\n\t\t *\n\t\t * Anything truthy counts, so a caller can pass the caught error itself\n\t\t * rather than converting it to a boolean first. An empty string is NOT\n\t\t * an error — that is a caller who cleared the message, not one\n\t\t * reporting a failure.\n\t\t *\n\t\t * @return {boolean} True when the tile should show `errorLabel`.\n\t\t */\n\t\thasError() {\n\t\t\treturn Boolean(this.error)\n\t\t},\n\n\t\tformattedCount() {\n\t\t\treturn this.count.toLocaleString()\n\t\t},\n\n\t\t/**\n\t\t * Whether the card is interactive (clickable or has a route).\n\t\t * Used for applying hover/focus styles.\n\t\t */\n\t\tisInteractive() {\n\t\t\treturn !!this.route || this.clickable\n\t\t},\n\n\t\t/**\n\t\t * Determines which HTML element or component to render.\n\t\t * - route set → 'router-link' (SPA navigation)\n\t\t * - clickable → 'a' (app handles click via event)\n\t\t * - neither → 'div' (static display)\n\t\t */\n\t\tcomponentTag() {\n\t\t\tif (this.route) {\n\t\t\t\treturn 'router-link'\n\t\t\t}\n\t\t\tif (this.clickable) {\n\t\t\t\treturn 'a'\n\t\t\t}\n\t\t\treturn 'div'\n\t\t},\n\n\t\t/**\n\t\t * Dynamic attributes for the root element based on rendering mode.\n\t\t */\n\t\tcomponentAttrs() {\n\t\t\tif (this.route) {\n\t\t\t\treturn { to: this.route, tabindex: '0' }\n\t\t\t}\n\t\t\tif (this.clickable) {\n\t\t\t\treturn { href: '#', role: 'button', tabindex: '0' }\n\t\t\t}\n\t\t\treturn {}\n\t\t},\n\n\t\t/**\n\t\t * Whether the block takes the stacked board look (`layout`).\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-stats-block-can-take-the-stacked-board-look\n\t\t * @return {boolean}\n\t\t */\n\t\tisStacked() {\n\t\t\treturn this.layout === 'stacked'\n\t\t},\n\n\t\trootClasses() {\n\t\t\treturn {\n\t\t\t\t// Canonical + legacy, in pairs: the canonical class carries the\n\t\t\t\t// look, the legacy one keeps existing app CSS matching.\n\t\t\t\t// The canonical card is already horizontal, so the legacy pair\n\t\t\t\t// is emitted for app CSS but carries no look of its own.\n\t\t\t\t'cn-kpi-card--horizontal': !this.isStacked && (this.horizontal || !this.vertical),\n\t\t\t\t'cn-kpi-card--stacked': this.isStacked,\n\t\t\t\t'cn-kpi-card--board': this.isBoardLook,\n\t\t\t\t'cn-stats-block--horizontal': this.horizontal || !this.vertical,\n\t\t\t\t'cn-kpi-card--vertical': this.vertical,\n\t\t\t\t'cn-stats-block--vertical': this.vertical,\n\t\t\t\t'cn-kpi-card--filled': this.filled,\n\t\t\t\t'cn-kpi-card--clickable': this.isInteractive,\n\t\t\t\t'cn-stats-block--clickable': this.isInteractive,\n\t\t\t\t'cn-kpi-card--error': this.hasError,\n\t\t\t\t[`cn-kpi-card--${this.variant}`]: this.variant !== 'default' && this.hasError === false,\n\t\t\t\t// An errored tile tints itself. Requiring the consumer to also\n\t\t\t\t// pass `variant=\"error\"` would mean every caller remembering\n\t\t\t\t// two props to express one state, and forgetting the second is\n\t\t\t\t// invisible — a tile that reads \"Unavailable\" in the default\n\t\t\t\t// colour looks like ordinary content.\n\t\t\t\t'cn-stats-block--error': this.hasError,\n\t\t\t\t[`cn-stats-block--${this.variant}`]: this.variant !== 'default' && this.hasError === false,\n\t\t\t}\n\t\t},\n\n\t\ticonClasses() {\n\t\t\treturn {\n\t\t\t\t[`cn-stats-block__icon--${this.variant}`]: this.variant !== 'default',\n\t\t\t}\n\t\t},\n\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * A manifest string through the host's label lookup (`cnTranslate`), so a key\n\t\t * such as \"Mine\" reads in the user's language.\n\t\t *\n\t\t * @param {string} text The text as written in the manifest.\n\t\t * @return {string} The translated text.\n\t\t */\n\t\ttl(text) {\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text\n\t\t},\n\n\t\tformatBreakdownLabel(key) {\n\t\t\treturn key.charAt(0).toUpperCase() + key.slice(1) + ':'\n\t\t},\n\n\t\tonClick(event) {\n\t\t\t// When route is set, router-link handles navigation — do not emit click\n\t\t\tif (this.route) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.clickable) {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tthis.$emit('click', event)\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/*\n * The card's own look — box, body stack, icon circle, title, value, variants,\n * hover — is the CANONICAL KPI card in src/css/kpi-card.css, shared with\n * CnStatWidget. The rules that lived here duplicated it in different numbers\n * (a 1.2rem count row, a 40px icon, hardcoded rgba() tints that ignored a\n * re-themed palette), and that duplication IS how the two KPI looks drifted.\n * What is left below is this component's own extras: the breakdown block, the\n * loading line and the empty line.\n *\n * Those extras are secondary KPI text, so they read the shared\n * `--cn-kpi-label-*` scale rather than carrying sizes of their own — an empty\n * state that is a different size from the label it replaces is the same drift\n * in miniature.\n */\n\n.cn-stats-block__header {\n\tmax-width: 100%;\n\tmin-width: 0;\n}\n\n.cn-stats-block__loading {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: flex-start;\n\tgap: 6px;\n\tfont-size: var(--cn-kpi-label-size, 13px);\n\tcolor: var(--cn-kpi-label-color, var(--color-text-maxcontrast));\n}\n\n.cn-stats-block__empty {\n\ttext-align: start;\n\tfont-size: var(--cn-kpi-label-size, 13px);\n\tcolor: var(--cn-kpi-label-color, var(--color-text-maxcontrast));\n\tfont-style: italic;\n}\n\n.cn-stats-block__breakdown {\n\tmargin-top: 4px;\n\tpadding: 8px;\n\tfont-size: var(--cn-kpi-label-size, 13px);\n\tbackground: var(--color-background-hover);\n\tborder-radius: var(--border-radius);\n\tborder: 1px solid var(--color-border);\n\twidth: 100%;\n\ttext-align: start;\n}\n\n.cn-stats-block__breakdown-item {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: center;\n\tgap: 8px;\n\tmargin-bottom: 4px;\n}\n\n.cn-stats-block__breakdown-item:last-child {\n\tmargin-bottom: 0;\n}\n\n.cn-stats-block__breakdown-label {\n\tfont-weight: 500;\n\tcolor: var(--color-main-text);\n}\n\n/* The value takes the card's accent, so a breakdown reads as part of the tile\n it sits in rather than as a table that happened to land there. It used to\n carry a `--color-background-hover` chip — on a `--color-background-hover`\n breakdown box, which is to say an invisible chip drawn at the cost of two\n extra declarations and a differently-shaped hit area. The per-key modifiers\n below still win: `invalid`, `deleted` and `published` mean something\n specific and outrank the tile's own colour. */\n.cn-stats-block__breakdown-value {\n\tfont-weight: var(--cn-kpi-title-weight, 600);\n\tcolor: var(--cn-kpi-accent, var(--color-primary-element));\n}\n\n.cn-stats-block__breakdown-value--invalid { color: var(--color-element-warning); }\n\n.cn-stats-block__breakdown-value--deleted { color: var(--color-element-error); }\n\n.cn-stats-block__breakdown-value--published { color: var(--color-element-success); }\n</style>\n"],"names":["t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnStatsBlock.vue2.js","sources":["../../../../src/components/CnStatsBlock/CnStatsBlock.vue"],"sourcesContent":["<!--\n - CnStatsBlock renders the CANONICAL KPI card (src/css/kpi-card.css) — the\n - same markup and the same stylesheet CnStatWidget renders, so OpenCatalogi's\n - KPIs and dossiq's are one design rather than two. This component's own\n - `cn-stats-block*` classes stay on the same elements for backwards\n - compatibility with app CSS that targets them; the LOOK comes from the\n - shared sheet.\n -\n - The comment lives OUT here rather than inside <template>: a comment node\n - beside the root element makes the component multi-root in Vue 3, and a\n - multi-root component has no root to put class attributes on — `rootClasses`\n - silently stopped reaching the DOM.\n-->\n<template>\n\t<component\n\t\t:is=\"componentTag\"\n\t\tclass=\"cn-kpi-card cn-stats-block\"\n\t\t:class=\"rootClasses\"\n\t\tv-bind=\"componentAttrs\"\n\t\t@click=\"onClick\">\n\t\t<!-- Icon -->\n\t\t<div v-if=\"hasIcon && !isStacked && !isBoardLook\" class=\"cn-kpi-card__icon cn-stats-block__icon\" :class=\"iconClasses\">\n\t\t\t<slot name=\"icon\">\n\t\t\t\t<component :is=\"icon\" v-if=\"icon\" :size=\"iconSize\" />\n\t\t\t</slot>\n\t\t</div>\n\n\t\t<!-- Content -->\n\t\t<div class=\"cn-kpi-card__body cn-stats-block__content\">\n\t\t\t<div class=\"cn-stats-block__header\">\n\t\t\t\t<!-- Board look: an 18px glyph before the label, only on a link tile. -->\n\t\t\t\t<span\n\t\t\t\t\tv-if=\"isBoardLook && isInteractive && hasIcon\"\n\t\t\t\t\tclass=\"cn-kpi-card__glyph cn-stats-block__glyph\"\n\t\t\t\t\tdata-testid=\"cn-stats-block-glyph\"\n\t\t\t\t\taria-hidden=\"true\">\n\t\t\t\t\t<slot name=\"icon\">\n\t\t\t\t\t\t<component :is=\"icon\" v-if=\"icon\" :size=\"18\" />\n\t\t\t\t\t</slot>\n\t\t\t\t</span>\n\t\t\t\t<h4 class=\"cn-kpi-card__title\" :title=\"title || undefined\">\n\t\t\t\t\t{{ title ? tl(title) : t('nextcloud-vue', 'Objects') }}\n\t\t\t\t</h4>\n\t\t\t</div>\n\n\t\t\t<div v-if=\"hasError\" class=\"cn-stats-block__count cn-stats-block__count--error\">\n\t\t\t\t<span class=\"cn-stats-block__count-value\">—</span>\n\t\t\t\t<span class=\"cn-stats-block__count-label\">{{ errorLabel }}</span>\n\t\t\t</div>\n\t\t\t<div v-else-if=\"hasValueSlot || count > 0 || (showZeroCount && count === 0)\" class=\"cn-kpi-card__value-row cn-stats-block__count\">\n\t\t\t\t<span class=\"cn-kpi-card__value cn-stats-block__count-value\">\n\t\t\t\t\t<!-- @slot Override the prominently-displayed value — render a pre-formatted string (currency, percent, a \"—\" placeholder, …). `count` stays the raw number; this is presentation only. Defaults to the localized count. -->\n\t\t\t\t\t<!-- @binding {number} count The raw numeric count. -->\n\t\t\t\t\t<!-- @binding {string} formatted The default localized count string. -->\n\t\t\t\t\t<slot name=\"value\" :count=\"count\" :formatted=\"formattedCount\">{{ formattedCount }}</slot>\n\t\t\t\t</span>\n\t\t\t\t<span class=\"cn-kpi-card__label cn-stats-block__count-label\">{{ countLabel ? tl(countLabel) : countLabel }}</span>\n\t\t\t</div>\n\t\t\t<div v-else-if=\"loading\" class=\"cn-stats-block__loading\">\n\t\t\t\t<NcLoadingIcon :size=\"16\" />\n\t\t\t\t{{ loadingLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-stats-block__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\n\t\t\t<!-- Breakdown details -->\n\t\t\t<div\n\t\t\t\tv-if=\"hasError === false && breakdown && (hasValueSlot || count > 0 || showZeroCount)\"\n\t\t\t\tclass=\"cn-stats-block__breakdown\">\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"(value, key) in breakdown\"\n\t\t\t\t\t:key=\"key\"\n\t\t\t\t\tclass=\"cn-stats-block__breakdown-item\">\n\t\t\t\t\t<span class=\"cn-stats-block__breakdown-label\">{{ formatBreakdownLabel(key) }}</span>\n\t\t\t\t\t<span\n\t\t\t\t\t\tclass=\"cn-stats-block__breakdown-value\"\n\t\t\t\t\t\t:class=\"'cn-stats-block__breakdown-value--' + key\">\n\t\t\t\t\t\t{{ value }}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</component>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { normalizeLook } from '../../composables/useLook.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\n// The canonical KPI look, shared with CnStatWidget. Imported here so the\n// card is styled even when the consuming app has not pulled in the\n// library's global css/index.css.\nimport '../../css/kpi-card.css'\n\n/**\n * CnStatsBlock — Statistics display card with icon, count, and optional breakdown.\n *\n * Renders the canonical KPI card: a large circular icon beside a left-aligned\n * number, drawing no box of its own because the wrapper around it already\n * draws one. Supports colour variants, icons and clickable state. Use in a\n * CnKpiGrid for responsive dashboard layouts.\n *\n * Basic (canonical horizontal card)\n * ```vue\n * <CnStatsBlock title=\"Cases\" :count=\"42\" count-label=\"open cases\" />\n * ```\n *\n * With icon and variant\n * ```vue\n * <CnStatsBlock\n * title=\"Open Cases\"\n * :count=\"42\"\n * :icon=\"BriefcaseOutline\"\n * variant=\"primary\"\n * clickable\n * @click=\"goToCases\" />\n * ```\n *\n * Stacked and boxed — for a block mounted with no wrapper around it\n * ```vue\n * <CnStatsBlock title=\"Cases\" :count=\"42\" vertical filled />\n * ```\n *\n * With route-based navigation (renders as <router-link>)\n * ```vue\n * <CnStatsBlock\n * title=\"Open Cases\"\n * :count=\"42\"\n * :icon=\"BriefcaseOutline\"\n * variant=\"primary\"\n * :route=\"{ name: 'Cases', query: { status: 'open' } }\" />\n * ```\n *\n * With breakdown\n * ```vue\n * <CnStatsBlock\n * title=\"Cases\"\n * :count=\"42\"\n * :breakdown=\"{ total: 100, invalid: 3, deleted: 5, published: 92 }\" />\n * ```\n *\n * Custom icon slot\n * ```vue\n * <CnStatsBlock title=\"Files\" :count=\"128\">\n * <template #icon>\n * <FileDocumentOutline :size=\"24\" />\n * </template>\n * </CnStatsBlock>\n * ```\n */\nexport default {\n\tname: 'CnStatsBlock',\n\n\tcomponents: {\n\t\tNcLoadingIcon,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * The look the app is drawn in, provided by CnAppRoot.\n\t\t *\n\t\t * @spec openspec/changes/screens-dashboard-parity/specs/dashboard-page/spec.md#requirement-the-board-kpi-tile\n\t\t */\n\t\tcnLook: { default: 'nextcloud' },\n\t\t/**\n\t\t * The host's label lookup (CnAppRoot provides it); identity without one, so a\n\t\t * label that is not a key renders as written.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** Block title */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The main count number to display prominently */\n\t\tcount: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0,\n\t\t},\n\n\t\t/** Label displayed next to the count */\n\t\tcountLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'objects'),\n\t\t},\n\n\t\t/** Detailed breakdown object (key-value pairs) */\n\t\tbreakdown: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Whether data is currently loading */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Text shown while loading */\n\t\tloadingLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Loading…'),\n\t\t},\n\n\t\t/** Text shown when count is 0 */\n\t\temptyLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'No items found'),\n\t\t},\n\n\t\t/**\n\t\t * The tile could not load its number. Anything truthy counts — pass the\n\t\t * caught error itself, or just `true`.\n\t\t *\n\t\t * The tile then shows a dash and `errorLabel` INSTEAD of a count, and\n\t\t * tints itself `error`. This takes precedence over `count`, `loading`\n\t\t * and `emptyLabel` deliberately: a stale or defaulted number rendered\n\t\t * during a failure is the exact thing this prop exists to stop. A\n\t\t * dashboard showing 0 because the backend is down is worse than one\n\t\t * showing nothing, because 0 is a number a reader will believe.\n\t\t *\n\t\t * @type {boolean|string|Error|null}\n\t\t */\n\t\terror: {\n\t\t\t// TYPE ORDER IS SEMANTIC HERE. Vue casts an empty value to `true`\n\t\t\t// when Boolean appears BEFORE String in the union, so\n\t\t\t// `[Boolean, String]` would read `error=\"\"` as an error. A caller\n\t\t\t// clearing its message back to '' is reporting RECOVERY, and that\n\t\t\t// would have pinned the tile in its error state after the fetch\n\t\t\t// succeeded again. String first keeps '' falsy.\n\t\t\t// eslint-disable-next-line vue/prefer-prop-type-boolean-first -- Boolean first would make `error=\"\"` truthy, which is the recovery signal.\n\t\t\ttype: [String, Boolean, Object],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Text shown in place of the count when `error` is set. */\n\t\terrorLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Unavailable'),\n\t\t},\n\n\t\t/** Icon component (e.g., imported MDI icon) */\n\t\ticon: {\n\t\t\ttype: [Object, Function],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Icon size in pixels */\n\t\ticonSize: {\n\t\t\ttype: Number,\n\t\t\tdefault: 24,\n\t\t},\n\n\t\t/** Color variant: 'default', 'primary', 'success', 'warning', 'error' */\n\t\tvariant: {\n\t\t\ttype: String,\n\t\t\tdefault: 'default',\n\t\t\tvalidator: (v) => ['default', 'primary', 'success', 'warning', 'error'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Stack the icon above a centred number instead of placing it beside\n\t\t * one. The canonical KPI card is horizontal, so this is the opt-out;\n\t\t * `horizontal` below is kept only for callers that already pass it.\n\t\t */\n\t\tvertical: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * `stacked` is the board's tile, as CnStatWidget `content.layout:\n\t\t * \"stacked\"`: no icon circle, a 14px muted title, the number at\n\t\t * 34px/700 in the text colour. Empty (the default) keeps the KPI card.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-stats-block-can-take-the-stacked-board-look\n\t\t */\n\t\tlayout: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Lay the icon left of the content. No longer needed — this is the\n\t\t * canonical card's own layout — and kept so existing callers that pass\n\t\t * `horizontal` keep working. Pass `vertical` to stack instead.\n\t\t *\n\t\t * @deprecated since 2.25.0, the horizontal layout is the default.\n\t\t */\n\t\thorizontal: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Draw the card's own grey box. Off by default: a stats block is\n\t\t * normally rendered inside a wrapper that already draws a card, and a\n\t\t * second box reads as a card inside a card. Turn it on for a block\n\t\t * mounted with no wrapper around it.\n\t\t */\n\t\tfilled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Whether the card is clickable */\n\t\tclickable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Whether to display 0 as a count value instead of the empty label */\n\t\tshowZeroCount: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Vue Router location object for declarative navigation.\n\t\t * When set, the card renders as a <router-link> and clickable styles are implied.\n\t\t * { name: 'Cases', query: { status: 'open' } }\n\t\t * { path: '/catalogi' }\n\t\t */\n\t\troute: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['click'],\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the app takes the board look.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisBoardLook() {\n\t\t\treturn normalizeLook(this.cnLook) === 'board'\n\t\t},\n\n\t\thasIcon() {\n\t\t\treturn this.icon !== null || this.$slots.icon || this.$slots.icon\n\t\t},\n\n\t\t/**\n\t\t * Whether a consumer provided the `value` slot. When set, the value\n\t\t * area always renders (the slot decides what to show, even at count 0).\n\t\t */\n\t\thasValueSlot() {\n\t\t\treturn !!this.$slots.value || !!this.$slots.value\n\t\t},\n\n\t\t/**\n\t\t * Whether the tile is in its error state.\n\t\t *\n\t\t * Anything truthy counts, so a caller can pass the caught error itself\n\t\t * rather than converting it to a boolean first. An empty string is NOT\n\t\t * an error — that is a caller who cleared the message, not one\n\t\t * reporting a failure.\n\t\t *\n\t\t * @return {boolean} True when the tile should show `errorLabel`.\n\t\t */\n\t\thasError() {\n\t\t\treturn Boolean(this.error)\n\t\t},\n\n\t\tformattedCount() {\n\t\t\treturn this.count.toLocaleString(userLocale())\n\t\t},\n\n\t\t/**\n\t\t * Whether the card is interactive (clickable or has a route).\n\t\t * Used for applying hover/focus styles.\n\t\t */\n\t\tisInteractive() {\n\t\t\treturn !!this.route || this.clickable\n\t\t},\n\n\t\t/**\n\t\t * Determines which HTML element or component to render.\n\t\t * - route set → 'router-link' (SPA navigation)\n\t\t * - clickable → 'a' (app handles click via event)\n\t\t * - neither → 'div' (static display)\n\t\t */\n\t\tcomponentTag() {\n\t\t\tif (this.route) {\n\t\t\t\treturn 'router-link'\n\t\t\t}\n\t\t\tif (this.clickable) {\n\t\t\t\treturn 'a'\n\t\t\t}\n\t\t\treturn 'div'\n\t\t},\n\n\t\t/**\n\t\t * Dynamic attributes for the root element based on rendering mode.\n\t\t */\n\t\tcomponentAttrs() {\n\t\t\tif (this.route) {\n\t\t\t\treturn { to: this.route, tabindex: '0' }\n\t\t\t}\n\t\t\tif (this.clickable) {\n\t\t\t\treturn { href: '#', role: 'button', tabindex: '0' }\n\t\t\t}\n\t\t\treturn {}\n\t\t},\n\n\t\t/**\n\t\t * Whether the block takes the stacked board look (`layout`).\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-stats-block-can-take-the-stacked-board-look\n\t\t * @return {boolean}\n\t\t */\n\t\tisStacked() {\n\t\t\treturn this.layout === 'stacked'\n\t\t},\n\n\t\trootClasses() {\n\t\t\treturn {\n\t\t\t\t// Canonical + legacy, in pairs: the canonical class carries the\n\t\t\t\t// look, the legacy one keeps existing app CSS matching.\n\t\t\t\t// The canonical card is already horizontal, so the legacy pair\n\t\t\t\t// is emitted for app CSS but carries no look of its own.\n\t\t\t\t'cn-kpi-card--horizontal': !this.isStacked && (this.horizontal || !this.vertical),\n\t\t\t\t'cn-kpi-card--stacked': this.isStacked,\n\t\t\t\t'cn-kpi-card--board': this.isBoardLook,\n\t\t\t\t'cn-stats-block--horizontal': this.horizontal || !this.vertical,\n\t\t\t\t'cn-kpi-card--vertical': this.vertical,\n\t\t\t\t'cn-stats-block--vertical': this.vertical,\n\t\t\t\t'cn-kpi-card--filled': this.filled,\n\t\t\t\t'cn-kpi-card--clickable': this.isInteractive,\n\t\t\t\t'cn-stats-block--clickable': this.isInteractive,\n\t\t\t\t'cn-kpi-card--error': this.hasError,\n\t\t\t\t[`cn-kpi-card--${this.variant}`]: this.variant !== 'default' && this.hasError === false,\n\t\t\t\t// An errored tile tints itself. Requiring the consumer to also\n\t\t\t\t// pass `variant=\"error\"` would mean every caller remembering\n\t\t\t\t// two props to express one state, and forgetting the second is\n\t\t\t\t// invisible — a tile that reads \"Unavailable\" in the default\n\t\t\t\t// colour looks like ordinary content.\n\t\t\t\t'cn-stats-block--error': this.hasError,\n\t\t\t\t[`cn-stats-block--${this.variant}`]: this.variant !== 'default' && this.hasError === false,\n\t\t\t}\n\t\t},\n\n\t\ticonClasses() {\n\t\t\treturn {\n\t\t\t\t[`cn-stats-block__icon--${this.variant}`]: this.variant !== 'default',\n\t\t\t}\n\t\t},\n\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * A manifest string through the host's label lookup (`cnTranslate`), so a key\n\t\t * such as \"Mine\" reads in the user's language.\n\t\t *\n\t\t * @param {string} text The text as written in the manifest.\n\t\t * @return {string} The translated text.\n\t\t */\n\t\ttl(text) {\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text\n\t\t},\n\n\t\tformatBreakdownLabel(key) {\n\t\t\treturn key.charAt(0).toUpperCase() + key.slice(1) + ':'\n\t\t},\n\n\t\tonClick(event) {\n\t\t\t// When route is set, router-link handles navigation — do not emit click\n\t\t\tif (this.route) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.clickable) {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tthis.$emit('click', event)\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/*\n * The card's own look — box, body stack, icon circle, title, value, variants,\n * hover — is the CANONICAL KPI card in src/css/kpi-card.css, shared with\n * CnStatWidget. The rules that lived here duplicated it in different numbers\n * (a 1.2rem count row, a 40px icon, hardcoded rgba() tints that ignored a\n * re-themed palette), and that duplication IS how the two KPI looks drifted.\n * What is left below is this component's own extras: the breakdown block, the\n * loading line and the empty line.\n *\n * Those extras are secondary KPI text, so they read the shared\n * `--cn-kpi-label-*` scale rather than carrying sizes of their own — an empty\n * state that is a different size from the label it replaces is the same drift\n * in miniature.\n */\n\n.cn-stats-block__header {\n\tmax-width: 100%;\n\tmin-width: 0;\n}\n\n.cn-stats-block__loading {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: flex-start;\n\tgap: 6px;\n\tfont-size: var(--cn-kpi-label-size, 13px);\n\tcolor: var(--cn-kpi-label-color, var(--color-text-maxcontrast));\n}\n\n.cn-stats-block__empty {\n\ttext-align: start;\n\tfont-size: var(--cn-kpi-label-size, 13px);\n\tcolor: var(--cn-kpi-label-color, var(--color-text-maxcontrast));\n\tfont-style: italic;\n}\n\n.cn-stats-block__breakdown {\n\tmargin-top: 4px;\n\tpadding: 8px;\n\tfont-size: var(--cn-kpi-label-size, 13px);\n\tbackground: var(--color-background-hover);\n\tborder-radius: var(--border-radius);\n\tborder: 1px solid var(--color-border);\n\twidth: 100%;\n\ttext-align: start;\n}\n\n.cn-stats-block__breakdown-item {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: center;\n\tgap: 8px;\n\tmargin-bottom: 4px;\n}\n\n.cn-stats-block__breakdown-item:last-child {\n\tmargin-bottom: 0;\n}\n\n.cn-stats-block__breakdown-label {\n\tfont-weight: 500;\n\tcolor: var(--color-main-text);\n}\n\n/* The value takes the card's accent, so a breakdown reads as part of the tile\n it sits in rather than as a table that happened to land there. It used to\n carry a `--color-background-hover` chip — on a `--color-background-hover`\n breakdown box, which is to say an invisible chip drawn at the cost of two\n extra declarations and a differently-shaped hit area. The per-key modifiers\n below still win: `invalid`, `deleted` and `published` mean something\n specific and outrank the tile's own colour. */\n.cn-stats-block__breakdown-value {\n\tfont-weight: var(--cn-kpi-title-weight, 600);\n\tcolor: var(--cn-kpi-accent, var(--color-primary-element));\n}\n\n.cn-stats-block__breakdown-value--invalid { color: var(--color-element-warning); }\n\n.cn-stats-block__breakdown-value--deleted { color: var(--color-element-error); }\n\n.cn-stats-block__breakdown-value--published { color: var(--color-element-success); }\n</style>\n"],"names":["t"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnStatsBlock.vue3.js","sources":["../../../../src/components/CnStatsBlock/CnStatsBlock.vue?vue&type=template&id=770aba44&lang.js"],"sourcesContent":["<!--\n - CnStatsBlock renders the CANONICAL KPI card (src/css/kpi-card.css) — the\n - same markup and the same stylesheet CnStatWidget renders, so OpenCatalogi's\n - KPIs and dossiq's are one design rather than two. This component's own\n - `cn-stats-block*` classes stay on the same elements for backwards\n - compatibility with app CSS that targets them; the LOOK comes from the\n - shared sheet.\n -\n - The comment lives OUT here rather than inside <template>: a comment node\n - beside the root element makes the component multi-root in Vue 3, and a\n - multi-root component has no root to put class attributes on — `rootClasses`\n - silently stopped reaching the DOM.\n-->\n<template>\n\t<component\n\t\t:is=\"componentTag\"\n\t\tclass=\"cn-kpi-card cn-stats-block\"\n\t\t:class=\"rootClasses\"\n\t\tv-bind=\"componentAttrs\"\n\t\t@click=\"onClick\">\n\t\t<!-- Icon -->\n\t\t<div v-if=\"hasIcon && !isStacked && !isBoardLook\" class=\"cn-kpi-card__icon cn-stats-block__icon\" :class=\"iconClasses\">\n\t\t\t<slot name=\"icon\">\n\t\t\t\t<component :is=\"icon\" v-if=\"icon\" :size=\"iconSize\" />\n\t\t\t</slot>\n\t\t</div>\n\n\t\t<!-- Content -->\n\t\t<div class=\"cn-kpi-card__body cn-stats-block__content\">\n\t\t\t<div class=\"cn-stats-block__header\">\n\t\t\t\t<!-- Board look: an 18px glyph before the label, only on a link tile. -->\n\t\t\t\t<span\n\t\t\t\t\tv-if=\"isBoardLook && isInteractive && hasIcon\"\n\t\t\t\t\tclass=\"cn-kpi-card__glyph cn-stats-block__glyph\"\n\t\t\t\t\tdata-testid=\"cn-stats-block-glyph\"\n\t\t\t\t\taria-hidden=\"true\">\n\t\t\t\t\t<slot name=\"icon\">\n\t\t\t\t\t\t<component :is=\"icon\" v-if=\"icon\" :size=\"18\" />\n\t\t\t\t\t</slot>\n\t\t\t\t</span>\n\t\t\t\t<h4 class=\"cn-kpi-card__title\" :title=\"title || undefined\">\n\t\t\t\t\t{{ title ? tl(title) : t('nextcloud-vue', 'Objects') }}\n\t\t\t\t</h4>\n\t\t\t</div>\n\n\t\t\t<div v-if=\"hasError\" class=\"cn-stats-block__count cn-stats-block__count--error\">\n\t\t\t\t<span class=\"cn-stats-block__count-value\">—</span>\n\t\t\t\t<span class=\"cn-stats-block__count-label\">{{ errorLabel }}</span>\n\t\t\t</div>\n\t\t\t<div v-else-if=\"hasValueSlot || count > 0 || (showZeroCount && count === 0)\" class=\"cn-kpi-card__value-row cn-stats-block__count\">\n\t\t\t\t<span class=\"cn-kpi-card__value cn-stats-block__count-value\">\n\t\t\t\t\t<!-- @slot Override the prominently-displayed value — render a pre-formatted string (currency, percent, a \"—\" placeholder, …). `count` stays the raw number; this is presentation only. Defaults to the localized count. -->\n\t\t\t\t\t<!-- @binding {number} count The raw numeric count. -->\n\t\t\t\t\t<!-- @binding {string} formatted The default localized count string. -->\n\t\t\t\t\t<slot name=\"value\" :count=\"count\" :formatted=\"formattedCount\">{{ formattedCount }}</slot>\n\t\t\t\t</span>\n\t\t\t\t<span class=\"cn-kpi-card__label cn-stats-block__count-label\">{{ countLabel ? tl(countLabel) : countLabel }}</span>\n\t\t\t</div>\n\t\t\t<div v-else-if=\"loading\" class=\"cn-stats-block__loading\">\n\t\t\t\t<NcLoadingIcon :size=\"16\" />\n\t\t\t\t{{ loadingLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-stats-block__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\n\t\t\t<!-- Breakdown details -->\n\t\t\t<div\n\t\t\t\tv-if=\"hasError === false && breakdown && (hasValueSlot || count > 0 || showZeroCount)\"\n\t\t\t\tclass=\"cn-stats-block__breakdown\">\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"(value, key) in breakdown\"\n\t\t\t\t\t:key=\"key\"\n\t\t\t\t\tclass=\"cn-stats-block__breakdown-item\">\n\t\t\t\t\t<span class=\"cn-stats-block__breakdown-label\">{{ formatBreakdownLabel(key) }}</span>\n\t\t\t\t\t<span\n\t\t\t\t\t\tclass=\"cn-stats-block__breakdown-value\"\n\t\t\t\t\t\t:class=\"'cn-stats-block__breakdown-value--' + key\">\n\t\t\t\t\t\t{{ value }}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</component>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { normalizeLook } from '../../composables/useLook.js'\n\n// The canonical KPI look, shared with CnStatWidget. Imported here so the\n// card is styled even when the consuming app has not pulled in the\n// library's global css/index.css.\nimport '../../css/kpi-card.css'\n\n/**\n * CnStatsBlock — Statistics display card with icon, count, and optional breakdown.\n *\n * Renders the canonical KPI card: a large circular icon beside a left-aligned\n * number, drawing no box of its own because the wrapper around it already\n * draws one. Supports colour variants, icons and clickable state. Use in a\n * CnKpiGrid for responsive dashboard layouts.\n *\n * Basic (canonical horizontal card)\n * ```vue\n * <CnStatsBlock title=\"Cases\" :count=\"42\" count-label=\"open cases\" />\n * ```\n *\n * With icon and variant\n * ```vue\n * <CnStatsBlock\n * title=\"Open Cases\"\n * :count=\"42\"\n * :icon=\"BriefcaseOutline\"\n * variant=\"primary\"\n * clickable\n * @click=\"goToCases\" />\n * ```\n *\n * Stacked and boxed — for a block mounted with no wrapper around it\n * ```vue\n * <CnStatsBlock title=\"Cases\" :count=\"42\" vertical filled />\n * ```\n *\n * With route-based navigation (renders as <router-link>)\n * ```vue\n * <CnStatsBlock\n * title=\"Open Cases\"\n * :count=\"42\"\n * :icon=\"BriefcaseOutline\"\n * variant=\"primary\"\n * :route=\"{ name: 'Cases', query: { status: 'open' } }\" />\n * ```\n *\n * With breakdown\n * ```vue\n * <CnStatsBlock\n * title=\"Cases\"\n * :count=\"42\"\n * :breakdown=\"{ total: 100, invalid: 3, deleted: 5, published: 92 }\" />\n * ```\n *\n * Custom icon slot\n * ```vue\n * <CnStatsBlock title=\"Files\" :count=\"128\">\n * <template #icon>\n * <FileDocumentOutline :size=\"24\" />\n * </template>\n * </CnStatsBlock>\n * ```\n */\nexport default {\n\tname: 'CnStatsBlock',\n\n\tcomponents: {\n\t\tNcLoadingIcon,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * The look the app is drawn in, provided by CnAppRoot.\n\t\t *\n\t\t * @spec openspec/changes/screens-dashboard-parity/specs/dashboard-page/spec.md#requirement-the-board-kpi-tile\n\t\t */\n\t\tcnLook: { default: 'nextcloud' },\n\t\t/**\n\t\t * The host's label lookup (CnAppRoot provides it); identity without one, so a\n\t\t * label that is not a key renders as written.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** Block title */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The main count number to display prominently */\n\t\tcount: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0,\n\t\t},\n\n\t\t/** Label displayed next to the count */\n\t\tcountLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'objects'),\n\t\t},\n\n\t\t/** Detailed breakdown object (key-value pairs) */\n\t\tbreakdown: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Whether data is currently loading */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Text shown while loading */\n\t\tloadingLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Loading…'),\n\t\t},\n\n\t\t/** Text shown when count is 0 */\n\t\temptyLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'No items found'),\n\t\t},\n\n\t\t/**\n\t\t * The tile could not load its number. Anything truthy counts — pass the\n\t\t * caught error itself, or just `true`.\n\t\t *\n\t\t * The tile then shows a dash and `errorLabel` INSTEAD of a count, and\n\t\t * tints itself `error`. This takes precedence over `count`, `loading`\n\t\t * and `emptyLabel` deliberately: a stale or defaulted number rendered\n\t\t * during a failure is the exact thing this prop exists to stop. A\n\t\t * dashboard showing 0 because the backend is down is worse than one\n\t\t * showing nothing, because 0 is a number a reader will believe.\n\t\t *\n\t\t * @type {boolean|string|Error|null}\n\t\t */\n\t\terror: {\n\t\t\t// TYPE ORDER IS SEMANTIC HERE. Vue casts an empty value to `true`\n\t\t\t// when Boolean appears BEFORE String in the union, so\n\t\t\t// `[Boolean, String]` would read `error=\"\"` as an error. A caller\n\t\t\t// clearing its message back to '' is reporting RECOVERY, and that\n\t\t\t// would have pinned the tile in its error state after the fetch\n\t\t\t// succeeded again. String first keeps '' falsy.\n\t\t\t// eslint-disable-next-line vue/prefer-prop-type-boolean-first -- Boolean first would make `error=\"\"` truthy, which is the recovery signal.\n\t\t\ttype: [String, Boolean, Object],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Text shown in place of the count when `error` is set. */\n\t\terrorLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Unavailable'),\n\t\t},\n\n\t\t/** Icon component (e.g., imported MDI icon) */\n\t\ticon: {\n\t\t\ttype: [Object, Function],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Icon size in pixels */\n\t\ticonSize: {\n\t\t\ttype: Number,\n\t\t\tdefault: 24,\n\t\t},\n\n\t\t/** Color variant: 'default', 'primary', 'success', 'warning', 'error' */\n\t\tvariant: {\n\t\t\ttype: String,\n\t\t\tdefault: 'default',\n\t\t\tvalidator: (v) => ['default', 'primary', 'success', 'warning', 'error'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Stack the icon above a centred number instead of placing it beside\n\t\t * one. The canonical KPI card is horizontal, so this is the opt-out;\n\t\t * `horizontal` below is kept only for callers that already pass it.\n\t\t */\n\t\tvertical: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * `stacked` is the board's tile, as CnStatWidget `content.layout:\n\t\t * \"stacked\"`: no icon circle, a 14px muted title, the number at\n\t\t * 34px/700 in the text colour. Empty (the default) keeps the KPI card.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-stats-block-can-take-the-stacked-board-look\n\t\t */\n\t\tlayout: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Lay the icon left of the content. No longer needed — this is the\n\t\t * canonical card's own layout — and kept so existing callers that pass\n\t\t * `horizontal` keep working. Pass `vertical` to stack instead.\n\t\t *\n\t\t * @deprecated since 2.25.0, the horizontal layout is the default.\n\t\t */\n\t\thorizontal: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Draw the card's own grey box. Off by default: a stats block is\n\t\t * normally rendered inside a wrapper that already draws a card, and a\n\t\t * second box reads as a card inside a card. Turn it on for a block\n\t\t * mounted with no wrapper around it.\n\t\t */\n\t\tfilled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Whether the card is clickable */\n\t\tclickable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Whether to display 0 as a count value instead of the empty label */\n\t\tshowZeroCount: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Vue Router location object for declarative navigation.\n\t\t * When set, the card renders as a <router-link> and clickable styles are implied.\n\t\t * { name: 'Cases', query: { status: 'open' } }\n\t\t * { path: '/catalogi' }\n\t\t */\n\t\troute: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['click'],\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the app takes the board look.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisBoardLook() {\n\t\t\treturn normalizeLook(this.cnLook) === 'board'\n\t\t},\n\n\t\thasIcon() {\n\t\t\treturn this.icon !== null || this.$slots.icon || this.$slots.icon\n\t\t},\n\n\t\t/**\n\t\t * Whether a consumer provided the `value` slot. When set, the value\n\t\t * area always renders (the slot decides what to show, even at count 0).\n\t\t */\n\t\thasValueSlot() {\n\t\t\treturn !!this.$slots.value || !!this.$slots.value\n\t\t},\n\n\t\t/**\n\t\t * Whether the tile is in its error state.\n\t\t *\n\t\t * Anything truthy counts, so a caller can pass the caught error itself\n\t\t * rather than converting it to a boolean first. An empty string is NOT\n\t\t * an error — that is a caller who cleared the message, not one\n\t\t * reporting a failure.\n\t\t *\n\t\t * @return {boolean} True when the tile should show `errorLabel`.\n\t\t */\n\t\thasError() {\n\t\t\treturn Boolean(this.error)\n\t\t},\n\n\t\tformattedCount() {\n\t\t\treturn this.count.toLocaleString()\n\t\t},\n\n\t\t/**\n\t\t * Whether the card is interactive (clickable or has a route).\n\t\t * Used for applying hover/focus styles.\n\t\t */\n\t\tisInteractive() {\n\t\t\treturn !!this.route || this.clickable\n\t\t},\n\n\t\t/**\n\t\t * Determines which HTML element or component to render.\n\t\t * - route set → 'router-link' (SPA navigation)\n\t\t * - clickable → 'a' (app handles click via event)\n\t\t * - neither → 'div' (static display)\n\t\t */\n\t\tcomponentTag() {\n\t\t\tif (this.route) {\n\t\t\t\treturn 'router-link'\n\t\t\t}\n\t\t\tif (this.clickable) {\n\t\t\t\treturn 'a'\n\t\t\t}\n\t\t\treturn 'div'\n\t\t},\n\n\t\t/**\n\t\t * Dynamic attributes for the root element based on rendering mode.\n\t\t */\n\t\tcomponentAttrs() {\n\t\t\tif (this.route) {\n\t\t\t\treturn { to: this.route, tabindex: '0' }\n\t\t\t}\n\t\t\tif (this.clickable) {\n\t\t\t\treturn { href: '#', role: 'button', tabindex: '0' }\n\t\t\t}\n\t\t\treturn {}\n\t\t},\n\n\t\t/**\n\t\t * Whether the block takes the stacked board look (`layout`).\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-stats-block-can-take-the-stacked-board-look\n\t\t * @return {boolean}\n\t\t */\n\t\tisStacked() {\n\t\t\treturn this.layout === 'stacked'\n\t\t},\n\n\t\trootClasses() {\n\t\t\treturn {\n\t\t\t\t// Canonical + legacy, in pairs: the canonical class carries the\n\t\t\t\t// look, the legacy one keeps existing app CSS matching.\n\t\t\t\t// The canonical card is already horizontal, so the legacy pair\n\t\t\t\t// is emitted for app CSS but carries no look of its own.\n\t\t\t\t'cn-kpi-card--horizontal': !this.isStacked && (this.horizontal || !this.vertical),\n\t\t\t\t'cn-kpi-card--stacked': this.isStacked,\n\t\t\t\t'cn-kpi-card--board': this.isBoardLook,\n\t\t\t\t'cn-stats-block--horizontal': this.horizontal || !this.vertical,\n\t\t\t\t'cn-kpi-card--vertical': this.vertical,\n\t\t\t\t'cn-stats-block--vertical': this.vertical,\n\t\t\t\t'cn-kpi-card--filled': this.filled,\n\t\t\t\t'cn-kpi-card--clickable': this.isInteractive,\n\t\t\t\t'cn-stats-block--clickable': this.isInteractive,\n\t\t\t\t'cn-kpi-card--error': this.hasError,\n\t\t\t\t[`cn-kpi-card--${this.variant}`]: this.variant !== 'default' && this.hasError === false,\n\t\t\t\t// An errored tile tints itself. Requiring the consumer to also\n\t\t\t\t// pass `variant=\"error\"` would mean every caller remembering\n\t\t\t\t// two props to express one state, and forgetting the second is\n\t\t\t\t// invisible — a tile that reads \"Unavailable\" in the default\n\t\t\t\t// colour looks like ordinary content.\n\t\t\t\t'cn-stats-block--error': this.hasError,\n\t\t\t\t[`cn-stats-block--${this.variant}`]: this.variant !== 'default' && this.hasError === false,\n\t\t\t}\n\t\t},\n\n\t\ticonClasses() {\n\t\t\treturn {\n\t\t\t\t[`cn-stats-block__icon--${this.variant}`]: this.variant !== 'default',\n\t\t\t}\n\t\t},\n\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * A manifest string through the host's label lookup (`cnTranslate`), so a key\n\t\t * such as \"Mine\" reads in the user's language.\n\t\t *\n\t\t * @param {string} text The text as written in the manifest.\n\t\t * @return {string} The translated text.\n\t\t */\n\t\ttl(text) {\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text\n\t\t},\n\n\t\tformatBreakdownLabel(key) {\n\t\t\treturn key.charAt(0).toUpperCase() + key.slice(1) + ':'\n\t\t},\n\n\t\tonClick(event) {\n\t\t\t// When route is set, router-link handles navigation — do not emit click\n\t\t\tif (this.route) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.clickable) {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tthis.$emit('click', event)\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/*\n * The card's own look — box, body stack, icon circle, title, value, variants,\n * hover — is the CANONICAL KPI card in src/css/kpi-card.css, shared with\n * CnStatWidget. The rules that lived here duplicated it in different numbers\n * (a 1.2rem count row, a 40px icon, hardcoded rgba() tints that ignored a\n * re-themed palette), and that duplication IS how the two KPI looks drifted.\n * What is left below is this component's own extras: the breakdown block, the\n * loading line and the empty line.\n *\n * Those extras are secondary KPI text, so they read the shared\n * `--cn-kpi-label-*` scale rather than carrying sizes of their own — an empty\n * state that is a different size from the label it replaces is the same drift\n * in miniature.\n */\n\n.cn-stats-block__header {\n\tmax-width: 100%;\n\tmin-width: 0;\n}\n\n.cn-stats-block__loading {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: flex-start;\n\tgap: 6px;\n\tfont-size: var(--cn-kpi-label-size, 13px);\n\tcolor: var(--cn-kpi-label-color, var(--color-text-maxcontrast));\n}\n\n.cn-stats-block__empty {\n\ttext-align: start;\n\tfont-size: var(--cn-kpi-label-size, 13px);\n\tcolor: var(--cn-kpi-label-color, var(--color-text-maxcontrast));\n\tfont-style: italic;\n}\n\n.cn-stats-block__breakdown {\n\tmargin-top: 4px;\n\tpadding: 8px;\n\tfont-size: var(--cn-kpi-label-size, 13px);\n\tbackground: var(--color-background-hover);\n\tborder-radius: var(--border-radius);\n\tborder: 1px solid var(--color-border);\n\twidth: 100%;\n\ttext-align: start;\n}\n\n.cn-stats-block__breakdown-item {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: center;\n\tgap: 8px;\n\tmargin-bottom: 4px;\n}\n\n.cn-stats-block__breakdown-item:last-child {\n\tmargin-bottom: 0;\n}\n\n.cn-stats-block__breakdown-label {\n\tfont-weight: 500;\n\tcolor: var(--color-main-text);\n}\n\n/* The value takes the card's accent, so a breakdown reads as part of the tile\n it sits in rather than as a table that happened to land there. It used to\n carry a `--color-background-hover` chip — on a `--color-background-hover`\n breakdown box, which is to say an invisible chip drawn at the cost of two\n extra declarations and a differently-shaped hit area. The per-key modifiers\n below still win: `invalid`, `deleted` and `published` mean something\n specific and outrank the tile's own colour. */\n.cn-stats-block__breakdown-value {\n\tfont-weight: var(--cn-kpi-title-weight, 600);\n\tcolor: var(--cn-kpi-accent, var(--color-primary-element));\n}\n\n.cn-stats-block__breakdown-value--invalid { color: var(--color-element-warning); }\n\n.cn-stats-block__breakdown-value--deleted { color: var(--color-element-error); }\n\n.cn-stats-block__breakdown-value--published { color: var(--color-element-success); }\n</style>\n"],"names":["_createElementBlock","_normalizeClass","_renderSlot","_openBlock","_createBlock","_resolveDynamicComponent","_createElementVNode","_toDisplayString","_createVNode","_createTextVNode"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnStatsBlock.vue3.js","sources":["../../../../src/components/CnStatsBlock/CnStatsBlock.vue?vue&type=template&id=3ebbfa38&lang.js"],"sourcesContent":["<!--\n - CnStatsBlock renders the CANONICAL KPI card (src/css/kpi-card.css) — the\n - same markup and the same stylesheet CnStatWidget renders, so OpenCatalogi's\n - KPIs and dossiq's are one design rather than two. This component's own\n - `cn-stats-block*` classes stay on the same elements for backwards\n - compatibility with app CSS that targets them; the LOOK comes from the\n - shared sheet.\n -\n - The comment lives OUT here rather than inside <template>: a comment node\n - beside the root element makes the component multi-root in Vue 3, and a\n - multi-root component has no root to put class attributes on — `rootClasses`\n - silently stopped reaching the DOM.\n-->\n<template>\n\t<component\n\t\t:is=\"componentTag\"\n\t\tclass=\"cn-kpi-card cn-stats-block\"\n\t\t:class=\"rootClasses\"\n\t\tv-bind=\"componentAttrs\"\n\t\t@click=\"onClick\">\n\t\t<!-- Icon -->\n\t\t<div v-if=\"hasIcon && !isStacked && !isBoardLook\" class=\"cn-kpi-card__icon cn-stats-block__icon\" :class=\"iconClasses\">\n\t\t\t<slot name=\"icon\">\n\t\t\t\t<component :is=\"icon\" v-if=\"icon\" :size=\"iconSize\" />\n\t\t\t</slot>\n\t\t</div>\n\n\t\t<!-- Content -->\n\t\t<div class=\"cn-kpi-card__body cn-stats-block__content\">\n\t\t\t<div class=\"cn-stats-block__header\">\n\t\t\t\t<!-- Board look: an 18px glyph before the label, only on a link tile. -->\n\t\t\t\t<span\n\t\t\t\t\tv-if=\"isBoardLook && isInteractive && hasIcon\"\n\t\t\t\t\tclass=\"cn-kpi-card__glyph cn-stats-block__glyph\"\n\t\t\t\t\tdata-testid=\"cn-stats-block-glyph\"\n\t\t\t\t\taria-hidden=\"true\">\n\t\t\t\t\t<slot name=\"icon\">\n\t\t\t\t\t\t<component :is=\"icon\" v-if=\"icon\" :size=\"18\" />\n\t\t\t\t\t</slot>\n\t\t\t\t</span>\n\t\t\t\t<h4 class=\"cn-kpi-card__title\" :title=\"title || undefined\">\n\t\t\t\t\t{{ title ? tl(title) : t('nextcloud-vue', 'Objects') }}\n\t\t\t\t</h4>\n\t\t\t</div>\n\n\t\t\t<div v-if=\"hasError\" class=\"cn-stats-block__count cn-stats-block__count--error\">\n\t\t\t\t<span class=\"cn-stats-block__count-value\">—</span>\n\t\t\t\t<span class=\"cn-stats-block__count-label\">{{ errorLabel }}</span>\n\t\t\t</div>\n\t\t\t<div v-else-if=\"hasValueSlot || count > 0 || (showZeroCount && count === 0)\" class=\"cn-kpi-card__value-row cn-stats-block__count\">\n\t\t\t\t<span class=\"cn-kpi-card__value cn-stats-block__count-value\">\n\t\t\t\t\t<!-- @slot Override the prominently-displayed value — render a pre-formatted string (currency, percent, a \"—\" placeholder, …). `count` stays the raw number; this is presentation only. Defaults to the localized count. -->\n\t\t\t\t\t<!-- @binding {number} count The raw numeric count. -->\n\t\t\t\t\t<!-- @binding {string} formatted The default localized count string. -->\n\t\t\t\t\t<slot name=\"value\" :count=\"count\" :formatted=\"formattedCount\">{{ formattedCount }}</slot>\n\t\t\t\t</span>\n\t\t\t\t<span class=\"cn-kpi-card__label cn-stats-block__count-label\">{{ countLabel ? tl(countLabel) : countLabel }}</span>\n\t\t\t</div>\n\t\t\t<div v-else-if=\"loading\" class=\"cn-stats-block__loading\">\n\t\t\t\t<NcLoadingIcon :size=\"16\" />\n\t\t\t\t{{ loadingLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-stats-block__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\n\t\t\t<!-- Breakdown details -->\n\t\t\t<div\n\t\t\t\tv-if=\"hasError === false && breakdown && (hasValueSlot || count > 0 || showZeroCount)\"\n\t\t\t\tclass=\"cn-stats-block__breakdown\">\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"(value, key) in breakdown\"\n\t\t\t\t\t:key=\"key\"\n\t\t\t\t\tclass=\"cn-stats-block__breakdown-item\">\n\t\t\t\t\t<span class=\"cn-stats-block__breakdown-label\">{{ formatBreakdownLabel(key) }}</span>\n\t\t\t\t\t<span\n\t\t\t\t\t\tclass=\"cn-stats-block__breakdown-value\"\n\t\t\t\t\t\t:class=\"'cn-stats-block__breakdown-value--' + key\">\n\t\t\t\t\t\t{{ value }}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</component>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { normalizeLook } from '../../composables/useLook.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\n// The canonical KPI look, shared with CnStatWidget. Imported here so the\n// card is styled even when the consuming app has not pulled in the\n// library's global css/index.css.\nimport '../../css/kpi-card.css'\n\n/**\n * CnStatsBlock — Statistics display card with icon, count, and optional breakdown.\n *\n * Renders the canonical KPI card: a large circular icon beside a left-aligned\n * number, drawing no box of its own because the wrapper around it already\n * draws one. Supports colour variants, icons and clickable state. Use in a\n * CnKpiGrid for responsive dashboard layouts.\n *\n * Basic (canonical horizontal card)\n * ```vue\n * <CnStatsBlock title=\"Cases\" :count=\"42\" count-label=\"open cases\" />\n * ```\n *\n * With icon and variant\n * ```vue\n * <CnStatsBlock\n * title=\"Open Cases\"\n * :count=\"42\"\n * :icon=\"BriefcaseOutline\"\n * variant=\"primary\"\n * clickable\n * @click=\"goToCases\" />\n * ```\n *\n * Stacked and boxed — for a block mounted with no wrapper around it\n * ```vue\n * <CnStatsBlock title=\"Cases\" :count=\"42\" vertical filled />\n * ```\n *\n * With route-based navigation (renders as <router-link>)\n * ```vue\n * <CnStatsBlock\n * title=\"Open Cases\"\n * :count=\"42\"\n * :icon=\"BriefcaseOutline\"\n * variant=\"primary\"\n * :route=\"{ name: 'Cases', query: { status: 'open' } }\" />\n * ```\n *\n * With breakdown\n * ```vue\n * <CnStatsBlock\n * title=\"Cases\"\n * :count=\"42\"\n * :breakdown=\"{ total: 100, invalid: 3, deleted: 5, published: 92 }\" />\n * ```\n *\n * Custom icon slot\n * ```vue\n * <CnStatsBlock title=\"Files\" :count=\"128\">\n * <template #icon>\n * <FileDocumentOutline :size=\"24\" />\n * </template>\n * </CnStatsBlock>\n * ```\n */\nexport default {\n\tname: 'CnStatsBlock',\n\n\tcomponents: {\n\t\tNcLoadingIcon,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * The look the app is drawn in, provided by CnAppRoot.\n\t\t *\n\t\t * @spec openspec/changes/screens-dashboard-parity/specs/dashboard-page/spec.md#requirement-the-board-kpi-tile\n\t\t */\n\t\tcnLook: { default: 'nextcloud' },\n\t\t/**\n\t\t * The host's label lookup (CnAppRoot provides it); identity without one, so a\n\t\t * label that is not a key renders as written.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** Block title */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The main count number to display prominently */\n\t\tcount: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0,\n\t\t},\n\n\t\t/** Label displayed next to the count */\n\t\tcountLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'objects'),\n\t\t},\n\n\t\t/** Detailed breakdown object (key-value pairs) */\n\t\tbreakdown: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Whether data is currently loading */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Text shown while loading */\n\t\tloadingLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Loading…'),\n\t\t},\n\n\t\t/** Text shown when count is 0 */\n\t\temptyLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'No items found'),\n\t\t},\n\n\t\t/**\n\t\t * The tile could not load its number. Anything truthy counts — pass the\n\t\t * caught error itself, or just `true`.\n\t\t *\n\t\t * The tile then shows a dash and `errorLabel` INSTEAD of a count, and\n\t\t * tints itself `error`. This takes precedence over `count`, `loading`\n\t\t * and `emptyLabel` deliberately: a stale or defaulted number rendered\n\t\t * during a failure is the exact thing this prop exists to stop. A\n\t\t * dashboard showing 0 because the backend is down is worse than one\n\t\t * showing nothing, because 0 is a number a reader will believe.\n\t\t *\n\t\t * @type {boolean|string|Error|null}\n\t\t */\n\t\terror: {\n\t\t\t// TYPE ORDER IS SEMANTIC HERE. Vue casts an empty value to `true`\n\t\t\t// when Boolean appears BEFORE String in the union, so\n\t\t\t// `[Boolean, String]` would read `error=\"\"` as an error. A caller\n\t\t\t// clearing its message back to '' is reporting RECOVERY, and that\n\t\t\t// would have pinned the tile in its error state after the fetch\n\t\t\t// succeeded again. String first keeps '' falsy.\n\t\t\t// eslint-disable-next-line vue/prefer-prop-type-boolean-first -- Boolean first would make `error=\"\"` truthy, which is the recovery signal.\n\t\t\ttype: [String, Boolean, Object],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Text shown in place of the count when `error` is set. */\n\t\terrorLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Unavailable'),\n\t\t},\n\n\t\t/** Icon component (e.g., imported MDI icon) */\n\t\ticon: {\n\t\t\ttype: [Object, Function],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Icon size in pixels */\n\t\ticonSize: {\n\t\t\ttype: Number,\n\t\t\tdefault: 24,\n\t\t},\n\n\t\t/** Color variant: 'default', 'primary', 'success', 'warning', 'error' */\n\t\tvariant: {\n\t\t\ttype: String,\n\t\t\tdefault: 'default',\n\t\t\tvalidator: (v) => ['default', 'primary', 'success', 'warning', 'error'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Stack the icon above a centred number instead of placing it beside\n\t\t * one. The canonical KPI card is horizontal, so this is the opt-out;\n\t\t * `horizontal` below is kept only for callers that already pass it.\n\t\t */\n\t\tvertical: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * `stacked` is the board's tile, as CnStatWidget `content.layout:\n\t\t * \"stacked\"`: no icon circle, a 14px muted title, the number at\n\t\t * 34px/700 in the text colour. Empty (the default) keeps the KPI card.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-stats-block-can-take-the-stacked-board-look\n\t\t */\n\t\tlayout: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Lay the icon left of the content. No longer needed — this is the\n\t\t * canonical card's own layout — and kept so existing callers that pass\n\t\t * `horizontal` keep working. Pass `vertical` to stack instead.\n\t\t *\n\t\t * @deprecated since 2.25.0, the horizontal layout is the default.\n\t\t */\n\t\thorizontal: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Draw the card's own grey box. Off by default: a stats block is\n\t\t * normally rendered inside a wrapper that already draws a card, and a\n\t\t * second box reads as a card inside a card. Turn it on for a block\n\t\t * mounted with no wrapper around it.\n\t\t */\n\t\tfilled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Whether the card is clickable */\n\t\tclickable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Whether to display 0 as a count value instead of the empty label */\n\t\tshowZeroCount: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Vue Router location object for declarative navigation.\n\t\t * When set, the card renders as a <router-link> and clickable styles are implied.\n\t\t * { name: 'Cases', query: { status: 'open' } }\n\t\t * { path: '/catalogi' }\n\t\t */\n\t\troute: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['click'],\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the app takes the board look.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisBoardLook() {\n\t\t\treturn normalizeLook(this.cnLook) === 'board'\n\t\t},\n\n\t\thasIcon() {\n\t\t\treturn this.icon !== null || this.$slots.icon || this.$slots.icon\n\t\t},\n\n\t\t/**\n\t\t * Whether a consumer provided the `value` slot. When set, the value\n\t\t * area always renders (the slot decides what to show, even at count 0).\n\t\t */\n\t\thasValueSlot() {\n\t\t\treturn !!this.$slots.value || !!this.$slots.value\n\t\t},\n\n\t\t/**\n\t\t * Whether the tile is in its error state.\n\t\t *\n\t\t * Anything truthy counts, so a caller can pass the caught error itself\n\t\t * rather than converting it to a boolean first. An empty string is NOT\n\t\t * an error — that is a caller who cleared the message, not one\n\t\t * reporting a failure.\n\t\t *\n\t\t * @return {boolean} True when the tile should show `errorLabel`.\n\t\t */\n\t\thasError() {\n\t\t\treturn Boolean(this.error)\n\t\t},\n\n\t\tformattedCount() {\n\t\t\treturn this.count.toLocaleString(userLocale())\n\t\t},\n\n\t\t/**\n\t\t * Whether the card is interactive (clickable or has a route).\n\t\t * Used for applying hover/focus styles.\n\t\t */\n\t\tisInteractive() {\n\t\t\treturn !!this.route || this.clickable\n\t\t},\n\n\t\t/**\n\t\t * Determines which HTML element or component to render.\n\t\t * - route set → 'router-link' (SPA navigation)\n\t\t * - clickable → 'a' (app handles click via event)\n\t\t * - neither → 'div' (static display)\n\t\t */\n\t\tcomponentTag() {\n\t\t\tif (this.route) {\n\t\t\t\treturn 'router-link'\n\t\t\t}\n\t\t\tif (this.clickable) {\n\t\t\t\treturn 'a'\n\t\t\t}\n\t\t\treturn 'div'\n\t\t},\n\n\t\t/**\n\t\t * Dynamic attributes for the root element based on rendering mode.\n\t\t */\n\t\tcomponentAttrs() {\n\t\t\tif (this.route) {\n\t\t\t\treturn { to: this.route, tabindex: '0' }\n\t\t\t}\n\t\t\tif (this.clickable) {\n\t\t\t\treturn { href: '#', role: 'button', tabindex: '0' }\n\t\t\t}\n\t\t\treturn {}\n\t\t},\n\n\t\t/**\n\t\t * Whether the block takes the stacked board look (`layout`).\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-stats-block-can-take-the-stacked-board-look\n\t\t * @return {boolean}\n\t\t */\n\t\tisStacked() {\n\t\t\treturn this.layout === 'stacked'\n\t\t},\n\n\t\trootClasses() {\n\t\t\treturn {\n\t\t\t\t// Canonical + legacy, in pairs: the canonical class carries the\n\t\t\t\t// look, the legacy one keeps existing app CSS matching.\n\t\t\t\t// The canonical card is already horizontal, so the legacy pair\n\t\t\t\t// is emitted for app CSS but carries no look of its own.\n\t\t\t\t'cn-kpi-card--horizontal': !this.isStacked && (this.horizontal || !this.vertical),\n\t\t\t\t'cn-kpi-card--stacked': this.isStacked,\n\t\t\t\t'cn-kpi-card--board': this.isBoardLook,\n\t\t\t\t'cn-stats-block--horizontal': this.horizontal || !this.vertical,\n\t\t\t\t'cn-kpi-card--vertical': this.vertical,\n\t\t\t\t'cn-stats-block--vertical': this.vertical,\n\t\t\t\t'cn-kpi-card--filled': this.filled,\n\t\t\t\t'cn-kpi-card--clickable': this.isInteractive,\n\t\t\t\t'cn-stats-block--clickable': this.isInteractive,\n\t\t\t\t'cn-kpi-card--error': this.hasError,\n\t\t\t\t[`cn-kpi-card--${this.variant}`]: this.variant !== 'default' && this.hasError === false,\n\t\t\t\t// An errored tile tints itself. Requiring the consumer to also\n\t\t\t\t// pass `variant=\"error\"` would mean every caller remembering\n\t\t\t\t// two props to express one state, and forgetting the second is\n\t\t\t\t// invisible — a tile that reads \"Unavailable\" in the default\n\t\t\t\t// colour looks like ordinary content.\n\t\t\t\t'cn-stats-block--error': this.hasError,\n\t\t\t\t[`cn-stats-block--${this.variant}`]: this.variant !== 'default' && this.hasError === false,\n\t\t\t}\n\t\t},\n\n\t\ticonClasses() {\n\t\t\treturn {\n\t\t\t\t[`cn-stats-block__icon--${this.variant}`]: this.variant !== 'default',\n\t\t\t}\n\t\t},\n\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * A manifest string through the host's label lookup (`cnTranslate`), so a key\n\t\t * such as \"Mine\" reads in the user's language.\n\t\t *\n\t\t * @param {string} text The text as written in the manifest.\n\t\t * @return {string} The translated text.\n\t\t */\n\t\ttl(text) {\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text\n\t\t},\n\n\t\tformatBreakdownLabel(key) {\n\t\t\treturn key.charAt(0).toUpperCase() + key.slice(1) + ':'\n\t\t},\n\n\t\tonClick(event) {\n\t\t\t// When route is set, router-link handles navigation — do not emit click\n\t\t\tif (this.route) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.clickable) {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tthis.$emit('click', event)\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/*\n * The card's own look — box, body stack, icon circle, title, value, variants,\n * hover — is the CANONICAL KPI card in src/css/kpi-card.css, shared with\n * CnStatWidget. The rules that lived here duplicated it in different numbers\n * (a 1.2rem count row, a 40px icon, hardcoded rgba() tints that ignored a\n * re-themed palette), and that duplication IS how the two KPI looks drifted.\n * What is left below is this component's own extras: the breakdown block, the\n * loading line and the empty line.\n *\n * Those extras are secondary KPI text, so they read the shared\n * `--cn-kpi-label-*` scale rather than carrying sizes of their own — an empty\n * state that is a different size from the label it replaces is the same drift\n * in miniature.\n */\n\n.cn-stats-block__header {\n\tmax-width: 100%;\n\tmin-width: 0;\n}\n\n.cn-stats-block__loading {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: flex-start;\n\tgap: 6px;\n\tfont-size: var(--cn-kpi-label-size, 13px);\n\tcolor: var(--cn-kpi-label-color, var(--color-text-maxcontrast));\n}\n\n.cn-stats-block__empty {\n\ttext-align: start;\n\tfont-size: var(--cn-kpi-label-size, 13px);\n\tcolor: var(--cn-kpi-label-color, var(--color-text-maxcontrast));\n\tfont-style: italic;\n}\n\n.cn-stats-block__breakdown {\n\tmargin-top: 4px;\n\tpadding: 8px;\n\tfont-size: var(--cn-kpi-label-size, 13px);\n\tbackground: var(--color-background-hover);\n\tborder-radius: var(--border-radius);\n\tborder: 1px solid var(--color-border);\n\twidth: 100%;\n\ttext-align: start;\n}\n\n.cn-stats-block__breakdown-item {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: center;\n\tgap: 8px;\n\tmargin-bottom: 4px;\n}\n\n.cn-stats-block__breakdown-item:last-child {\n\tmargin-bottom: 0;\n}\n\n.cn-stats-block__breakdown-label {\n\tfont-weight: 500;\n\tcolor: var(--color-main-text);\n}\n\n/* The value takes the card's accent, so a breakdown reads as part of the tile\n it sits in rather than as a table that happened to land there. It used to\n carry a `--color-background-hover` chip — on a `--color-background-hover`\n breakdown box, which is to say an invisible chip drawn at the cost of two\n extra declarations and a differently-shaped hit area. The per-key modifiers\n below still win: `invalid`, `deleted` and `published` mean something\n specific and outrank the tile's own colour. */\n.cn-stats-block__breakdown-value {\n\tfont-weight: var(--cn-kpi-title-weight, 600);\n\tcolor: var(--cn-kpi-accent, var(--color-primary-element));\n}\n\n.cn-stats-block__breakdown-value--invalid { color: var(--color-element-warning); }\n\n.cn-stats-block__breakdown-value--deleted { color: var(--color-element-error); }\n\n.cn-stats-block__breakdown-value--published { color: var(--color-element-success); }\n</style>\n"],"names":["_createElementBlock","_normalizeClass","_renderSlot","_openBlock","_createBlock","_resolveDynamicComponent","_createElementVNode","_toDisplayString","_createVNode","_createTextVNode"],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import script from './CnSummaryAggregates.vue2.js';
|
|
|
2
2
|
import { render } from './CnSummaryAggregates.vue3.js';
|
|
3
3
|
|
|
4
4
|
script.render = render;
|
|
5
|
-
script.__scopeId = "data-v-
|
|
5
|
+
script.__scopeId = "data-v-2ea9593e";
|
|
6
6
|
const __sfc_main = script;
|
|
7
7
|
|
|
8
8
|
export { __sfc_main as default };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { NcLoadingIcon } from '@nextcloud/vue';
|
|
2
2
|
import { fetchAggregateValue } from '../../utils/fetchAggregate.js';
|
|
3
|
+
import { userLocale } from '../../utils/userLocale.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* CnSummaryAggregates — declarative count/sum/avg stat chips for a
|
|
@@ -129,7 +130,7 @@ var script = {
|
|
|
129
130
|
return new Intl.NumberFormat(undefined, { style: 'currency', currency: 'EUR' }).format(num)
|
|
130
131
|
}
|
|
131
132
|
if (typeof raw === 'number') {
|
|
132
|
-
return raw.toLocaleString()
|
|
133
|
+
return raw.toLocaleString(userLocale())
|
|
133
134
|
}
|
|
134
135
|
return raw
|
|
135
136
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnSummaryAggregates.vue2.js","sources":["../../../../src/components/CnSummaryAggregates/CnSummaryAggregates.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div v-if=\"aggregates.length > 0\" class=\"cn-summary-aggregates\" data-testid=\"cn-summary-aggregates\">\n\t\t<div\n\t\t\tv-for=\"(agg, i) in resolvedChips\"\n\t\t\t:key=\"i\"\n\t\t\tclass=\"cn-summary-aggregates__chip\"\n\t\t\t:data-testid=\"`cn-summary-aggregate-${i}`\">\n\t\t\t<span class=\"cn-summary-aggregates__value\">\n\t\t\t\t<NcLoadingIcon v-if=\"agg.loading\" :size=\"16\" />\n\t\t\t\t<template v-else>{{ agg.display }}</template>\n\t\t\t</span>\n\t\t\t<span class=\"cn-summary-aggregates__label\">{{ agg.label }}</span>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { fetchAggregateValue } from '../../utils/fetchAggregate.js'\n\n/**\n * CnSummaryAggregates — declarative count/sum/avg stat chips for a\n * `type:\"detail\"` page header.\n *\n * Each entry runs ONE aggregation (via OpenRegister's ad-hoc `/value`\n * endpoint — the `runAdhocByRef` contract) over a related schema, scoped to\n * the current detail-page object through `@objectId` / `@object.<field>`\n * filter tokens, and shows the result as a small labelled chip. Lets a detail\n * page surface \"12 open cases / €4.2k outstanding\" without per-app code.\n *\n * Mounted by `CnDetailPage` when the manifest page config declares\n * `summaryAggregates`. Reads the detail page's `cnObjectContext` inject so the\n * filter tokens resolve against the live object.\n *\n * Example config (consumed by CnDetailPage):\n * ```js\n * summaryAggregates: [\n * { label: 'Open cases', register: 'pipelinq', schema: 'case', metric: 'count', filter: { client: '@objectId', status: 'open' } },\n * { label: 'Outstanding', register: 'pipelinq', schema: 'invoice', metric: 'sum', field: 'amount', filter: { client: '@objectId', paid: false }, format: 'currency' },\n * ]\n * ```\n */\nexport default {\n\tname: 'CnSummaryAggregates',\n\n\tcomponents: { NcLoadingIcon },\n\n\tinject: {\n\t\t/**\n\t\t * Detail-page object context (`{ objectId, object, register, schema }`)\n\t\t * provided by CnDetailPage. Resolves `@objectId` / `@object.<field>` tokens\n\t\t * in each aggregate's filter. Null on surfaces that don't provide one.\n\t\t */\n\t\tcnObjectContext: { default: null },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The aggregate descriptors.\n\t\t *\n\t\t * @type {Array<{label: string, register: string, schema: string, metric?: string, field?: string, filter?: object, format?: string}>}\n\t\t */\n\t\taggregates: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Per-aggregate fetched values, keyed by index. */\n\t\t\tvalues: {},\n\t\t\t/** Per-aggregate loading flags, keyed by index. */\n\t\t\tloadingMap: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** The unwrapped object context (or null). */\n\t\tobjectCtx() {\n\t\t\tconst c = this.cnObjectContext\n\t\t\tif (!c) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn (typeof c === 'object' && 'value' in c) ? c.value : c\n\t\t},\n\n\t\t/**\n\t\t * The chips with their fetched value formatted for display.\n\t\t *\n\t\t * @return {Array<{label: string, display: (string|number), loading: boolean}>}\n\t\t */\n\t\tresolvedChips() {\n\t\t\treturn this.aggregates.map((agg, i) => {\n\t\t\t\tconst raw = this.values[i]\n\t\t\t\treturn {\n\t\t\t\t\tlabel: agg.label || '',\n\t\t\t\t\tdisplay: this.formatChip(raw, agg.format),\n\t\t\t\t\tloading: !!this.loadingMap[i],\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * A stable signature of the aggregate query set + the object id it is\n\t\t * scoped to, so the watcher only refetches on a real change.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tsourceKey() {\n\t\t\treturn JSON.stringify({\n\t\t\t\taggs: this.aggregates,\n\t\t\t\tobjectId: this.objectCtx ? this.objectCtx.objectId : null,\n\t\t\t})\n\t\t},\n\t},\n\n\twatch: {\n\t\tsourceKey: {\n\t\t\timmediate: true,\n\t\t\thandler() {\n\t\t\t\tthis.fetchAll()\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Format a fetched value for the chip. Defaults to a thousands-grouped\n\t\t * number; `format: 'currency'` renders a EUR amount, `format: 'number'`\n\t\t * forces numeric grouping.\n\t\t *\n\t\t * @param {number|null} raw The fetched value.\n\t\t * @param {string} [format] Optional format key (`currency` | `number`).\n\t\t * @return {string|number}\n\t\t */\n\t\tformatChip(raw, format) {\n\t\t\tif (raw === null || raw === undefined) {\n\t\t\t\treturn '—'\n\t\t\t}\n\t\t\tif (format === 'currency') {\n\t\t\t\tconst num = Number(raw)\n\t\t\t\tif (Number.isNaN(num)) {\n\t\t\t\t\treturn String(raw)\n\t\t\t\t}\n\t\t\t\treturn new Intl.NumberFormat(undefined, { style: 'currency', currency: 'EUR' }).format(num)\n\t\t\t}\n\t\t\tif (typeof raw === 'number') {\n\t\t\t\treturn raw.toLocaleString()\n\t\t\t}\n\t\t\treturn raw\n\t\t},\n\n\t\t/**\n\t\t * Fetch every aggregate, scoping the filter tokens to the current object.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync fetchAll() {\n\t\t\tconst ctx = this.objectCtx || {}\n\t\t\tawait Promise.all(this.aggregates.map((agg, i) => this.fetchOne(agg, i, ctx)))\n\t\t},\n\n\t\t/**\n\t\t * Fetch one aggregate value into `values[i]`.\n\t\t *\n\t\t * @param {object} agg The aggregate descriptor.\n\t\t * @param {number} i The index in the list.\n\t\t * @param {object} ctx The object context for token resolution.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync fetchOne(agg, i, ctx) {\n\t\t\tif (!agg.register || !agg.schema) {\n\t\t\t\tthis.values[i] = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loadingMap[i] = true\n\t\t\ttry {\n\t\t\t\tconst value = await fetchAggregateValue({\n\t\t\t\t\tregister: agg.register,\n\t\t\t\t\tschema: agg.schema,\n\t\t\t\t\tmetric: agg.metric || 'count',\n\t\t\t\t\tfield: agg.field,\n\t\t\t\t\tfilter: agg.filter || {},\n\t\t\t\t}, ctx)\n\t\t\t\tthis.values[i] = value\n\t\t\t} catch {\n\t\t\t\tthis.values[i] = null\n\t\t\t} finally {\n\t\t\t\tthis.loadingMap[i] = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-summary-aggregates {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 12px;\n\tmargin: 0 0 4px;\n}\n\n.cn-summary-aggregates__chip {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: flex-start;\n\tpadding: 6px 12px;\n\tborder-radius: var(--border-radius-large, 12px);\n\tbackground: var(--color-background-hover);\n\tmin-width: 72px;\n}\n\n.cn-summary-aggregates__value {\n\tfont-size: 1.25em;\n\tfont-weight: 600;\n\tline-height: 1.2;\n\tcolor: var(--color-main-text);\n}\n\n.cn-summary-aggregates__label {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"CnSummaryAggregates.vue2.js","sources":["../../../../src/components/CnSummaryAggregates/CnSummaryAggregates.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div v-if=\"aggregates.length > 0\" class=\"cn-summary-aggregates\" data-testid=\"cn-summary-aggregates\">\n\t\t<div\n\t\t\tv-for=\"(agg, i) in resolvedChips\"\n\t\t\t:key=\"i\"\n\t\t\tclass=\"cn-summary-aggregates__chip\"\n\t\t\t:data-testid=\"`cn-summary-aggregate-${i}`\">\n\t\t\t<span class=\"cn-summary-aggregates__value\">\n\t\t\t\t<NcLoadingIcon v-if=\"agg.loading\" :size=\"16\" />\n\t\t\t\t<template v-else>{{ agg.display }}</template>\n\t\t\t</span>\n\t\t\t<span class=\"cn-summary-aggregates__label\">{{ agg.label }}</span>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { fetchAggregateValue } from '../../utils/fetchAggregate.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\n/**\n * CnSummaryAggregates — declarative count/sum/avg stat chips for a\n * `type:\"detail\"` page header.\n *\n * Each entry runs ONE aggregation (via OpenRegister's ad-hoc `/value`\n * endpoint — the `runAdhocByRef` contract) over a related schema, scoped to\n * the current detail-page object through `@objectId` / `@object.<field>`\n * filter tokens, and shows the result as a small labelled chip. Lets a detail\n * page surface \"12 open cases / €4.2k outstanding\" without per-app code.\n *\n * Mounted by `CnDetailPage` when the manifest page config declares\n * `summaryAggregates`. Reads the detail page's `cnObjectContext` inject so the\n * filter tokens resolve against the live object.\n *\n * Example config (consumed by CnDetailPage):\n * ```js\n * summaryAggregates: [\n * { label: 'Open cases', register: 'pipelinq', schema: 'case', metric: 'count', filter: { client: '@objectId', status: 'open' } },\n * { label: 'Outstanding', register: 'pipelinq', schema: 'invoice', metric: 'sum', field: 'amount', filter: { client: '@objectId', paid: false }, format: 'currency' },\n * ]\n * ```\n */\nexport default {\n\tname: 'CnSummaryAggregates',\n\n\tcomponents: { NcLoadingIcon },\n\n\tinject: {\n\t\t/**\n\t\t * Detail-page object context (`{ objectId, object, register, schema }`)\n\t\t * provided by CnDetailPage. Resolves `@objectId` / `@object.<field>` tokens\n\t\t * in each aggregate's filter. Null on surfaces that don't provide one.\n\t\t */\n\t\tcnObjectContext: { default: null },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The aggregate descriptors.\n\t\t *\n\t\t * @type {Array<{label: string, register: string, schema: string, metric?: string, field?: string, filter?: object, format?: string}>}\n\t\t */\n\t\taggregates: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Per-aggregate fetched values, keyed by index. */\n\t\t\tvalues: {},\n\t\t\t/** Per-aggregate loading flags, keyed by index. */\n\t\t\tloadingMap: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** The unwrapped object context (or null). */\n\t\tobjectCtx() {\n\t\t\tconst c = this.cnObjectContext\n\t\t\tif (!c) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn (typeof c === 'object' && 'value' in c) ? c.value : c\n\t\t},\n\n\t\t/**\n\t\t * The chips with their fetched value formatted for display.\n\t\t *\n\t\t * @return {Array<{label: string, display: (string|number), loading: boolean}>}\n\t\t */\n\t\tresolvedChips() {\n\t\t\treturn this.aggregates.map((agg, i) => {\n\t\t\t\tconst raw = this.values[i]\n\t\t\t\treturn {\n\t\t\t\t\tlabel: agg.label || '',\n\t\t\t\t\tdisplay: this.formatChip(raw, agg.format),\n\t\t\t\t\tloading: !!this.loadingMap[i],\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * A stable signature of the aggregate query set + the object id it is\n\t\t * scoped to, so the watcher only refetches on a real change.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tsourceKey() {\n\t\t\treturn JSON.stringify({\n\t\t\t\taggs: this.aggregates,\n\t\t\t\tobjectId: this.objectCtx ? this.objectCtx.objectId : null,\n\t\t\t})\n\t\t},\n\t},\n\n\twatch: {\n\t\tsourceKey: {\n\t\t\timmediate: true,\n\t\t\thandler() {\n\t\t\t\tthis.fetchAll()\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Format a fetched value for the chip. Defaults to a thousands-grouped\n\t\t * number; `format: 'currency'` renders a EUR amount, `format: 'number'`\n\t\t * forces numeric grouping.\n\t\t *\n\t\t * @param {number|null} raw The fetched value.\n\t\t * @param {string} [format] Optional format key (`currency` | `number`).\n\t\t * @return {string|number}\n\t\t */\n\t\tformatChip(raw, format) {\n\t\t\tif (raw === null || raw === undefined) {\n\t\t\t\treturn '—'\n\t\t\t}\n\t\t\tif (format === 'currency') {\n\t\t\t\tconst num = Number(raw)\n\t\t\t\tif (Number.isNaN(num)) {\n\t\t\t\t\treturn String(raw)\n\t\t\t\t}\n\t\t\t\treturn new Intl.NumberFormat(undefined, { style: 'currency', currency: 'EUR' }).format(num)\n\t\t\t}\n\t\t\tif (typeof raw === 'number') {\n\t\t\t\treturn raw.toLocaleString(userLocale())\n\t\t\t}\n\t\t\treturn raw\n\t\t},\n\n\t\t/**\n\t\t * Fetch every aggregate, scoping the filter tokens to the current object.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync fetchAll() {\n\t\t\tconst ctx = this.objectCtx || {}\n\t\t\tawait Promise.all(this.aggregates.map((agg, i) => this.fetchOne(agg, i, ctx)))\n\t\t},\n\n\t\t/**\n\t\t * Fetch one aggregate value into `values[i]`.\n\t\t *\n\t\t * @param {object} agg The aggregate descriptor.\n\t\t * @param {number} i The index in the list.\n\t\t * @param {object} ctx The object context for token resolution.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync fetchOne(agg, i, ctx) {\n\t\t\tif (!agg.register || !agg.schema) {\n\t\t\t\tthis.values[i] = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loadingMap[i] = true\n\t\t\ttry {\n\t\t\t\tconst value = await fetchAggregateValue({\n\t\t\t\t\tregister: agg.register,\n\t\t\t\t\tschema: agg.schema,\n\t\t\t\t\tmetric: agg.metric || 'count',\n\t\t\t\t\tfield: agg.field,\n\t\t\t\t\tfilter: agg.filter || {},\n\t\t\t\t}, ctx)\n\t\t\t\tthis.values[i] = value\n\t\t\t} catch {\n\t\t\t\tthis.values[i] = null\n\t\t\t} finally {\n\t\t\t\tthis.loadingMap[i] = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-summary-aggregates {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 12px;\n\tmargin: 0 0 4px;\n}\n\n.cn-summary-aggregates__chip {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: flex-start;\n\tpadding: 6px 12px;\n\tborder-radius: var(--border-radius-large, 12px);\n\tbackground: var(--color-background-hover);\n\tmin-width: 72px;\n}\n\n.cn-summary-aggregates__value {\n\tfont-size: 1.25em;\n\tfont-weight: 600;\n\tline-height: 1.2;\n\tcolor: var(--color-main-text);\n}\n\n.cn-summary-aggregates__label {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":[],"mappings":";;;;AAyBA;;;AAGC;;AAEA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;;AAEA;;AAEA;;AAEA;;AAED,aAAe;CACd,IAAI,EAAE,qBAAqB;;CAE3B,UAAU,EAAE,EAAE,aAAY,EAAG;;AAE7B,CAAA,MAAM,EAAE;AACP;;AAEC;;;AAGD,EAAA,eAAe,EAAE,EAAE,OAAO,EAAE,IAAG,EAAG;EAClC;;AAED,CAAA,KAAK,EAAE;AACN;;AAEC;;;EAGD,UAAU,EAAE;GACX,IAAI,EAAE,KAAK;AACX,GAAA,OAAO,EAAE,MAAM,EAAE;GACjB;EACD;;AAED,CAAA,IAAI,GAAG;AACN,EAAA,OAAO;AACN;AACA,GAAA,MAAM,EAAE,EAAE;AACV;GACA,UAAU,EAAE,EAAE;AACf,GAAA;EACA;;AAED,CAAA,QAAQ,EAAE;AACT;EACA,SAAS,GAAG;GACX,MAAM,CAAA,GAAI,IAAI,CAAC,gBAAc;AAC7B,GAAA,IAAI,CAAC,CAAC,EAAE;AACP,IAAA,OAAO,IAAG;AACX,IAAA;GACA,OAAO,CAAC,OAAO,CAAA,KAAM,QAAS,IAAG,OAAQ,IAAG,CAAC,IAAI,CAAC,CAAC,KAAI,GAAI,CAAA;GAC3D;;AAED;;AAEC;AACA;;AAED,EAAA,aAAa,GAAG;AACf,GAAA,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,KAAK;IACtC,MAAM,GAAE,GAAI,IAAI,CAAC,MAAM,CAAC,CAAC,EAAA;AACzB,IAAA,OAAO;AACN,KAAA,KAAK,EAAE,GAAG,CAAC,SAAS,EAAE;AACtB,KAAA,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC;KACzC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;AAC9B,KAAA;IACA,CAAA;GACD;;AAED;;;AAGC;AACA;;EAED,SAAS,GAAG;AACX,GAAA,OAAO,IAAI,CAAC,SAAS,CAAC;AACrB,IAAA,IAAI,EAAE,IAAI,CAAC,UAAU;IACrB,QAAQ,EAAE,IAAI,CAAC,SAAU,GAAE,IAAI,CAAC,SAAS,CAAC,WAAW,IAAI;IACzD,CAAA;GACD;EACD;;AAED,CAAA,KAAK,EAAE;EACN,SAAS,EAAE;AACV,GAAA,SAAS,EAAE,IAAI;AACf,GAAA,OAAO,GAAG;AACT,IAAA,IAAI,CAAC,QAAQ,GAAC;IACd;GACD;EACD;;AAED,CAAA,OAAO,EAAE;AACR;;;;AAIC;AACA;;;;EAID,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE;AACvB,GAAA,IAAI,GAAI,KAAI,IAAG,IAAK,GAAI,KAAI,SAAS,EAAE;AACtC,IAAA,OAAO,GAAE;AACV,IAAA;GACA,IAAI,WAAW,UAAU,EAAE;AAC1B,IAAA,MAAM,GAAI,GAAE,MAAM,CAAC,GAAG,EAAA;AACtB,IAAA,IAAI,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;AACtB,KAAA,OAAO,MAAM,CAAC,GAAG,CAAA;AAClB,KAAA;AACA,IAAA,OAAO,IAAI,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAI,EAAG,CAAC,CAAC,MAAM,CAAC,GAAG,CAAA;AAC3F,IAAA;GACA,IAAI,OAAO,GAAE,KAAM,QAAQ,EAAE;AAC5B,IAAA,OAAO,GAAG,CAAC,cAAc,CAAC,UAAU,EAAE,CAAA;AACvC,IAAA;AACA,GAAA,OAAO,GAAE;GACT;;AAED;;AAEC;;;AAGD,EAAA,MAAM,QAAQ,GAAG;GAChB,MAAM,GAAI,GAAE,IAAI,CAAC,SAAQ,IAAK,GAAC;AAC/B,GAAA,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,EAAA;GAC7E;;AAED;AACC;AACA;AACA;AACA;;;;EAID,MAAM,QAAQ,CAAC,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE;AAC3B,GAAA,IAAI,CAAC,GAAG,CAAC,QAAS,IAAG,CAAC,GAAG,CAAC,MAAM,EAAE;AACjC,IAAA,IAAI,CAAC,MAAM,CAAC,CAAC,CAAA,GAAI,KAAG;IACpB,MAAK;AACN,IAAA;GACA,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,KAAG;AACxB,GAAA,IAAI;AACH,IAAA,MAAM,KAAI,GAAI,MAAM,mBAAmB,CAAC;AACvC,KAAA,QAAQ,EAAE,GAAG,CAAC,QAAQ;AACtB,KAAA,MAAM,EAAE,GAAG,CAAC,MAAM;KAClB,MAAM,EAAE,GAAG,CAAC,MAAO,IAAG,OAAO;AAC7B,KAAA,KAAK,EAAE,GAAG,CAAC,KAAK;AAChB,KAAA,MAAM,EAAE,GAAG,CAAC,UAAU,EAAE;KACxB,EAAE,GAAG,EAAA;AACN,IAAA,IAAI,CAAC,MAAM,CAAC,CAAC,CAAA,GAAI,MAAI;AACtB,IAAA,CAAE,MAAM;AACP,IAAA,IAAI,CAAC,MAAM,CAAC,CAAC,CAAA,GAAI,KAAG;AACrB,IAAA,SAAU;IACT,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,MAAI;AAC1B,IAAA;GACA;EACD;AACF;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnSummaryAggregates.vue3.js","sources":["../../../../src/components/CnSummaryAggregates/CnSummaryAggregates.vue?vue&type=template&id=
|
|
1
|
+
{"version":3,"file":"CnSummaryAggregates.vue3.js","sources":["../../../../src/components/CnSummaryAggregates/CnSummaryAggregates.vue?vue&type=template&id=2ea9593e&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div v-if=\"aggregates.length > 0\" class=\"cn-summary-aggregates\" data-testid=\"cn-summary-aggregates\">\n\t\t<div\n\t\t\tv-for=\"(agg, i) in resolvedChips\"\n\t\t\t:key=\"i\"\n\t\t\tclass=\"cn-summary-aggregates__chip\"\n\t\t\t:data-testid=\"`cn-summary-aggregate-${i}`\">\n\t\t\t<span class=\"cn-summary-aggregates__value\">\n\t\t\t\t<NcLoadingIcon v-if=\"agg.loading\" :size=\"16\" />\n\t\t\t\t<template v-else>{{ agg.display }}</template>\n\t\t\t</span>\n\t\t\t<span class=\"cn-summary-aggregates__label\">{{ agg.label }}</span>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { fetchAggregateValue } from '../../utils/fetchAggregate.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\n/**\n * CnSummaryAggregates — declarative count/sum/avg stat chips for a\n * `type:\"detail\"` page header.\n *\n * Each entry runs ONE aggregation (via OpenRegister's ad-hoc `/value`\n * endpoint — the `runAdhocByRef` contract) over a related schema, scoped to\n * the current detail-page object through `@objectId` / `@object.<field>`\n * filter tokens, and shows the result as a small labelled chip. Lets a detail\n * page surface \"12 open cases / €4.2k outstanding\" without per-app code.\n *\n * Mounted by `CnDetailPage` when the manifest page config declares\n * `summaryAggregates`. Reads the detail page's `cnObjectContext` inject so the\n * filter tokens resolve against the live object.\n *\n * Example config (consumed by CnDetailPage):\n * ```js\n * summaryAggregates: [\n * { label: 'Open cases', register: 'pipelinq', schema: 'case', metric: 'count', filter: { client: '@objectId', status: 'open' } },\n * { label: 'Outstanding', register: 'pipelinq', schema: 'invoice', metric: 'sum', field: 'amount', filter: { client: '@objectId', paid: false }, format: 'currency' },\n * ]\n * ```\n */\nexport default {\n\tname: 'CnSummaryAggregates',\n\n\tcomponents: { NcLoadingIcon },\n\n\tinject: {\n\t\t/**\n\t\t * Detail-page object context (`{ objectId, object, register, schema }`)\n\t\t * provided by CnDetailPage. Resolves `@objectId` / `@object.<field>` tokens\n\t\t * in each aggregate's filter. Null on surfaces that don't provide one.\n\t\t */\n\t\tcnObjectContext: { default: null },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The aggregate descriptors.\n\t\t *\n\t\t * @type {Array<{label: string, register: string, schema: string, metric?: string, field?: string, filter?: object, format?: string}>}\n\t\t */\n\t\taggregates: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Per-aggregate fetched values, keyed by index. */\n\t\t\tvalues: {},\n\t\t\t/** Per-aggregate loading flags, keyed by index. */\n\t\t\tloadingMap: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** The unwrapped object context (or null). */\n\t\tobjectCtx() {\n\t\t\tconst c = this.cnObjectContext\n\t\t\tif (!c) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn (typeof c === 'object' && 'value' in c) ? c.value : c\n\t\t},\n\n\t\t/**\n\t\t * The chips with their fetched value formatted for display.\n\t\t *\n\t\t * @return {Array<{label: string, display: (string|number), loading: boolean}>}\n\t\t */\n\t\tresolvedChips() {\n\t\t\treturn this.aggregates.map((agg, i) => {\n\t\t\t\tconst raw = this.values[i]\n\t\t\t\treturn {\n\t\t\t\t\tlabel: agg.label || '',\n\t\t\t\t\tdisplay: this.formatChip(raw, agg.format),\n\t\t\t\t\tloading: !!this.loadingMap[i],\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * A stable signature of the aggregate query set + the object id it is\n\t\t * scoped to, so the watcher only refetches on a real change.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tsourceKey() {\n\t\t\treturn JSON.stringify({\n\t\t\t\taggs: this.aggregates,\n\t\t\t\tobjectId: this.objectCtx ? this.objectCtx.objectId : null,\n\t\t\t})\n\t\t},\n\t},\n\n\twatch: {\n\t\tsourceKey: {\n\t\t\timmediate: true,\n\t\t\thandler() {\n\t\t\t\tthis.fetchAll()\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Format a fetched value for the chip. Defaults to a thousands-grouped\n\t\t * number; `format: 'currency'` renders a EUR amount, `format: 'number'`\n\t\t * forces numeric grouping.\n\t\t *\n\t\t * @param {number|null} raw The fetched value.\n\t\t * @param {string} [format] Optional format key (`currency` | `number`).\n\t\t * @return {string|number}\n\t\t */\n\t\tformatChip(raw, format) {\n\t\t\tif (raw === null || raw === undefined) {\n\t\t\t\treturn '—'\n\t\t\t}\n\t\t\tif (format === 'currency') {\n\t\t\t\tconst num = Number(raw)\n\t\t\t\tif (Number.isNaN(num)) {\n\t\t\t\t\treturn String(raw)\n\t\t\t\t}\n\t\t\t\treturn new Intl.NumberFormat(undefined, { style: 'currency', currency: 'EUR' }).format(num)\n\t\t\t}\n\t\t\tif (typeof raw === 'number') {\n\t\t\t\treturn raw.toLocaleString(userLocale())\n\t\t\t}\n\t\t\treturn raw\n\t\t},\n\n\t\t/**\n\t\t * Fetch every aggregate, scoping the filter tokens to the current object.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync fetchAll() {\n\t\t\tconst ctx = this.objectCtx || {}\n\t\t\tawait Promise.all(this.aggregates.map((agg, i) => this.fetchOne(agg, i, ctx)))\n\t\t},\n\n\t\t/**\n\t\t * Fetch one aggregate value into `values[i]`.\n\t\t *\n\t\t * @param {object} agg The aggregate descriptor.\n\t\t * @param {number} i The index in the list.\n\t\t * @param {object} ctx The object context for token resolution.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync fetchOne(agg, i, ctx) {\n\t\t\tif (!agg.register || !agg.schema) {\n\t\t\t\tthis.values[i] = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loadingMap[i] = true\n\t\t\ttry {\n\t\t\t\tconst value = await fetchAggregateValue({\n\t\t\t\t\tregister: agg.register,\n\t\t\t\t\tschema: agg.schema,\n\t\t\t\t\tmetric: agg.metric || 'count',\n\t\t\t\t\tfield: agg.field,\n\t\t\t\t\tfilter: agg.filter || {},\n\t\t\t\t}, ctx)\n\t\t\t\tthis.values[i] = value\n\t\t\t} catch {\n\t\t\t\tthis.values[i] = null\n\t\t\t} finally {\n\t\t\t\tthis.loadingMap[i] = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-summary-aggregates {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 12px;\n\tmargin: 0 0 4px;\n}\n\n.cn-summary-aggregates__chip {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: flex-start;\n\tpadding: 6px 12px;\n\tborder-radius: var(--border-radius-large, 12px);\n\tbackground: var(--color-background-hover);\n\tmin-width: 72px;\n}\n\n.cn-summary-aggregates__value {\n\tfont-size: 1.25em;\n\tfont-weight: 600;\n\tline-height: 1.2;\n\tcolor: var(--color-main-text);\n}\n\n.cn-summary-aggregates__label {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_renderList","_createElementVNode","_createBlock","_Fragment","_createTextVNode","_toDisplayString"],"mappings":";;;;AASmC,EAAA,KAAE,EAAA,uBAAA;AAAA,EAAA,aAAA,EAAA,uBAAA;;;AAM5B,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,8BAA8B,GAAA;;;;;;AANjC,EAAA,OAAA,CAAA,MAAA,CAAA,UAAU,CAAC,MAAM,GAAA,CAAA;AAA1B,OAAAA,SAAA,EAAA,EAAAC,kBAAA,QAAA;AACA,SAAAD,SAAA,CAAA,IAAA,CAAA,EAAAC,kBAAA,SACmB,EAAA,IAAA,EAAAC,UAAA,CAAA,QAAA,CAAA,aAAa,EAAxB,CAAA,GAAG,EAAE,CAAC,KAAA;+BADdD;AAEC,YAAA,GAAG,EAAE,CAAC;AACN,YAAA,KAAI,EAAC,6BAA6B;AAClC,YAAA,aAAK,0BAAA,CAAA,CAAA,CAAA;;AACN,YAAAE,kBAAA,SAAA;AACI,cAAA,CAAA,GAAA,CAAA,OAAA;gCAAHC,WAAG,CAAA,wBAAA,EAAA;;AAAA,oBAAA,IAAA,EAAA,EAAA;;gCACHH,kBAAE,CAAAI,QAAA,EAAA,EAAA,GAAA,EAAA,CAAA,EAAA,EAAA;AAAA,oBAAAC,eAAA,CAAAC,eAAA,CAAA,GAAA,CAAA,OAAA,CAAA,EAAA,CAAA,YAAA;;;;;;;;;;;;"}
|
|
@@ -2,7 +2,7 @@ import script from './CnTabsWidget.vue2.js';
|
|
|
2
2
|
import { render } from './CnTabsWidget.vue3.js';
|
|
3
3
|
|
|
4
4
|
script.render = render;
|
|
5
|
-
script.__scopeId = "data-v-
|
|
5
|
+
script.__scopeId = "data-v-72f3975c";
|
|
6
6
|
const __sfc_main = script;
|
|
7
7
|
|
|
8
8
|
export { __sfc_main as default };
|
|
@@ -779,11 +779,14 @@ var script = {
|
|
|
779
779
|
const from = tab && typeof tab === 'object' ? tab.countFrom : null;
|
|
780
780
|
let source = null;
|
|
781
781
|
if (from === 'widget') {
|
|
782
|
-
// A stack counts its first widget that has a list
|
|
782
|
+
// A stack counts its first widget that has a list: flat
|
|
783
|
+
// (`content.register`) or a dashboard table's `content.source`.
|
|
783
784
|
const lists = this.tabWidgetIds(tab)
|
|
784
785
|
.map((id) => this.availableWidgets.find((w) => w && w.id === id))
|
|
785
|
-
.
|
|
786
|
-
|
|
786
|
+
.map((w) => (w && w.content && typeof w.content === 'object' ? w.content : null))
|
|
787
|
+
.map((content) => (content && content.source && typeof content.source === 'object' && content.source.register ? content.source : content))
|
|
788
|
+
.filter((content) => content && content.register && content.schema);
|
|
789
|
+
source = lists.length ? lists[0] : null;
|
|
787
790
|
} else if (from && typeof from === 'object') {
|
|
788
791
|
source = from;
|
|
789
792
|
}
|