@conduction/nextcloud-vue 2.79.0 → 2.81.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/CnActionButtons/CnActionButtons.vue2.js.map +1 -1
- package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js +3 -1
- package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +32 -3
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +13 -11
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +80 -4
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +95 -91
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +179 -13
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +52 -16
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +96 -4
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +158 -110
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue.js +10 -0
- package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue.js.map +1 -0
- package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue2.js +252 -0
- package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue3.js +140 -0
- package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnChipsFieldWidget/index.js +26 -0
- package/dist/esm/components/CnChipsFieldWidget/index.js.map +1 -0
- package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue.js +10 -0
- package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue.js.map +1 -0
- package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue2.js +354 -0
- package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue3.js +172 -0
- package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnChoicePickerWidget/index.js +27 -0
- package/dist/esm/components/CnChoicePickerWidget/index.js.map +1 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +2 -2
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +65 -5
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +2 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +19 -11
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +4 -2
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +134 -4
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +21 -4
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +24 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +43 -4
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js +1 -1
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue2.js +7 -7
- package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue3.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +11 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js.map +1 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +867 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +238 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnFactsWidget/index.js +30 -0
- package/dist/esm/components/CnFactsWidget/index.js.map +1 -0
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js +10 -0
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js.map +1 -0
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +626 -0
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +396 -0
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnFieldListWidget/index.js +29 -0
- package/dist/esm/components/CnFieldListWidget/index.js.map +1 -0
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +302 -4
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +417 -244
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js +101 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js +141 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +65 -17
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +4 -4
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue2.js +78 -3
- package/dist/esm/components/CnFormPage/CnFormPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue3.js +74 -49
- package/dist/esm/components/CnFormPage/CnFormPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +333 -73
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +79 -17
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js +10 -0
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js.map +1 -0
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js +99 -0
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js.map +1 -0
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js +48 -0
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js.map +1 -0
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +7 -7
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js +1 -1
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +65 -2
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +6 -2
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +63 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +8 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js +14 -0
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js +5 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js +29 -1
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js +10 -0
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js.map +1 -0
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js +174 -0
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js +75 -0
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnRadioCardsWidget/index.js +28 -0
- package/dist/esm/components/CnRadioCardsWidget/index.js.map +1 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +10 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js.map +1 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +411 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +178 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnRecordListWidget/index.js +31 -0
- package/dist/esm/components/CnRecordListWidget/index.js.map +1 -0
- package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue.js +10 -0
- package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue.js.map +1 -0
- package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue2.js +222 -0
- package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue3.js +64 -0
- package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnSavedViewsWidget/index.js +30 -0
- package/dist/esm/components/CnSavedViewsWidget/index.js.map +1 -0
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue.js +10 -0
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue.js.map +1 -0
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue2.js +351 -0
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue3.js +71 -0
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnSectionsWidget/index.js +35 -0
- package/dist/esm/components/CnSectionsWidget/index.js.map +1 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -1
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -1
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js +10 -0
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js.map +1 -0
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js +347 -0
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js +184 -0
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnSettingsFormWidget/index.js +27 -0
- package/dist/esm/components/CnSettingsFormWidget/index.js.map +1 -0
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js +81 -2
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +87 -6
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +1 -0
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +84 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +85 -41
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js +10 -0
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js.map +1 -0
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js +159 -0
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue3.js +60 -0
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnSwitchRowsWidget/index.js +26 -0
- package/dist/esm/components/CnSwitchRowsWidget/index.js.map +1 -0
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +168 -7
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +86 -23
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +80 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +75 -32
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js +10 -0
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js.map +1 -0
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js +269 -0
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js +106 -0
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnToggleTableWidget/index.js +27 -0
- package/dist/esm/components/CnToggleTableWidget/index.js.map +1 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +11 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +7 -2
- 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/CnWidgetObjectTable/dashboardRegistration.js +5 -0
- package/dist/esm/components/CnWidgetObjectTable/dashboardRegistration.js.map +1 -1
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js +301 -6
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js +153 -55
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js.map +1 -1
- package/dist/esm/composables/useFormDraft.js +140 -9
- package/dist/esm/composables/useFormDraft.js.map +1 -1
- package/dist/esm/composables/useListView.js +4 -4
- package/dist/esm/composables/useListView.js.map +1 -1
- package/dist/esm/index.js +21 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js +28 -20
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue3.js +9 -0
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/field-inspection.js +21 -0
- package/dist/esm/integrations/builtin/field-inspection.js.map +1 -1
- package/dist/esm/integrations/offline/checklistSubmission.js +141 -0
- package/dist/esm/integrations/offline/checklistSubmission.js.map +1 -0
- package/dist/esm/integrations/offline/fieldCollectionHelpers.js +70 -5
- package/dist/esm/integrations/offline/fieldCollectionHelpers.js.map +1 -1
- package/dist/esm/integrations/offline/index.js +2 -1
- package/dist/esm/integrations/offline/index.js.map +1 -1
- package/dist/esm/integrations/offline/offlineDb.js +6 -1
- package/dist/esm/integrations/offline/offlineDb.js.map +1 -1
- package/dist/esm/integrations/offline/syncReplayService.js +8 -2
- package/dist/esm/integrations/offline/syncReplayService.js.map +1 -1
- package/dist/esm/l10n/en.json.js +20 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +70 -2
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +2192 -222
- package/dist/esm/utils/contentHeaderLink.js +51 -0
- package/dist/esm/utils/contentHeaderLink.js.map +1 -0
- package/dist/esm/utils/detailActionModel.js +60 -3
- package/dist/esm/utils/detailActionModel.js.map +1 -1
- package/dist/esm/utils/facets.js +75 -3
- package/dist/esm/utils/facets.js.map +1 -1
- package/dist/esm/utils/fetchFilterCounts.js +40 -5
- package/dist/esm/utils/fetchFilterCounts.js.map +1 -1
- package/dist/esm/utils/fillTemplate.js +120 -0
- package/dist/esm/utils/fillTemplate.js.map +1 -0
- package/dist/esm/utils/initials.js +32 -0
- package/dist/esm/utils/initials.js.map +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js +11 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/objectWrite.js +59 -0
- package/dist/esm/utils/objectWrite.js.map +1 -0
- package/dist/esm/utils/pageControls.js +81 -0
- package/dist/esm/utils/pageControls.js.map +1 -0
- package/dist/esm/utils/personalLenses.js +40 -3
- package/dist/esm/utils/personalLenses.js.map +1 -1
- package/dist/esm/utils/queryPresets.js +96 -0
- package/dist/esm/utils/queryPresets.js.map +1 -0
- package/dist/esm/utils/resolveFilterTokens.js +12 -1
- package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
- package/dist/esm/utils/resolveManifestTokens.js +2 -0
- package/dist/esm/utils/resolveManifestTokens.js.map +1 -1
- package/dist/esm/utils/schema.js +21 -0
- package/dist/esm/utils/schema.js.map +1 -1
- package/dist/esm/utils/sentinelTokens.js +22 -4
- package/dist/esm/utils/sentinelTokens.js.map +1 -1
- package/dist/esm/utils/userGroupsPeek.js +91 -0
- package/dist/esm/utils/userGroupsPeek.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/visibleWhen.js +57 -5
- package/dist/esm/utils/visibleWhen.js.map +1 -1
- package/dist/esm/utils/widgetDispatch.js +7 -1
- package/dist/esm/utils/widgetDispatch.js.map +1 -1
- package/dist/esm/utils/widgetVisibility.js +4 -57
- package/dist/esm/utils/widgetVisibility.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +19708 -9301
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +2192 -222
- package/l10n/en.json +20 -1
- package/l10n/nl.json +70 -2
- package/package.json +2 -1
- package/scripts/check-docs-mdx.mjs +108 -0
- package/src/components/CnActionButtons/CnActionButtons.vue +2 -0
- package/src/components/CnActionsBar/CnActionsBar.md +4 -0
- package/src/components/CnActionsBar/CnActionsBar.vue +48 -11
- package/src/components/CnAppRoot/CnAppRoot.md +4 -2
- package/src/components/CnAppRoot/CnAppRoot.vue +151 -59
- package/src/components/CnBannerWidget/CnBannerWidget.vue +244 -23
- package/src/components/CnCellRenderer/CnCellRenderer.md +2 -0
- package/src/components/CnCellRenderer/CnCellRenderer.vue +235 -5
- package/src/components/CnChipsFieldWidget/CnChipsFieldWidget.vue +433 -0
- package/src/components/CnChipsFieldWidget/index.js +25 -0
- package/src/components/CnChoicePickerWidget/CnChoicePickerWidget.vue +556 -0
- package/src/components/CnChoicePickerWidget/index.js +26 -0
- package/src/components/CnConversationThread/CnConversationWidget.vue +1 -1
- package/src/components/CnDashboardGrid/CnDashboardGrid.vue +2 -2
- package/src/components/CnDashboardPage/CnDashboardPage.vue +78 -5
- package/src/components/CnDataTable/CnDataTable.vue +22 -12
- package/src/components/CnDetailPage/CnDetailPage.md +1 -1
- package/src/components/CnDetailPage/CnDetailPage.vue +152 -6
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +66 -0
- package/src/components/CnDocumentReviewList/CnDocumentReviewWidget.vue +1 -1
- package/src/components/CnFacetSidebar/CnFacetSidebar.vue +7 -7
- package/src/components/CnFactsWidget/CnFactsWidget.vue +1190 -0
- package/src/components/CnFactsWidget/index.js +29 -0
- package/src/components/CnFieldListWidget/CnFieldListWidget.vue +1008 -0
- package/src/components/CnFieldListWidget/index.js +28 -0
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +588 -117
- package/src/components/CnFilesBrowser/filesBrowser.js +100 -0
- package/src/components/CnFilesBrowser/filesBrowserColumns.js +140 -0
- package/src/components/CnFormDialog/CnFormDialog.md +9 -3
- package/src/components/CnFormDialog/CnFormDialog.vue +69 -21
- package/src/components/CnFormPage/CnFormPage.vue +99 -3
- package/src/components/CnIndexPage/CnIndexPage.vue +309 -9
- package/src/components/CnIndexPage/CnIndexPageWidgets.vue +169 -0
- package/src/components/CnIndexSidebar/CnIndexSidebar.vue +7 -7
- package/src/components/CnNextStepCard/CnNextStepWidget.vue +1 -1
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +69 -2
- package/src/components/CnObjectSidebar/CnFilesTab.vue +70 -1
- package/src/components/CnObjectSidebar/CnNotesTab.vue +16 -0
- package/src/components/CnPageRenderer/CnPageRenderer.vue +29 -1
- package/src/components/CnRadioCardsWidget/CnRadioCardsWidget.vue +313 -0
- package/src/components/CnRadioCardsWidget/index.js +27 -0
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +652 -0
- package/src/components/CnRecordListWidget/index.js +30 -0
- package/src/components/CnSavedViewsWidget/CnSavedViewsWidget.vue +296 -0
- package/src/components/CnSavedViewsWidget/index.js +29 -0
- package/src/components/CnSectionsWidget/CnSectionsWidget.vue +489 -0
- package/src/components/CnSectionsWidget/index.js +34 -0
- package/src/components/CnSegmentedControl/CnSegmentedControl.vue +13 -0
- package/src/components/CnSettingsFormWidget/CnSettingsFormWidget.vue +555 -0
- package/src/components/CnSettingsFormWidget/index.js +26 -0
- package/src/components/CnStagesWidget/CnStagesWidget.vue +81 -2
- package/src/components/CnStatWidget/CnStatWidget.vue +88 -7
- package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +180 -3
- package/src/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue +226 -0
- package/src/components/CnSwitchRowsWidget/index.js +25 -0
- package/src/components/CnTabsWidget/CnTabsWidget.vue +263 -8
- package/src/components/CnTasksWidget/CnTasksWidget.vue +175 -15
- package/src/components/CnToggleTableWidget/CnToggleTableWidget.vue +386 -0
- package/src/components/CnToggleTableWidget/index.js +26 -0
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +11 -0
- package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +7 -2
- package/src/components/CnWidgetObjectTable/dashboardRegistration.js +5 -0
- package/src/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue +493 -11
- package/src/components/index.js +19 -0
- package/src/composables/__tests__/useFormDraft.spec.js +69 -0
- package/src/composables/useFormDraft.js +139 -8
- package/src/composables/useListView.js +4 -4
- package/src/css/form-field.css +14 -0
- package/src/css/index-page.css +71 -0
- package/src/css/kpi-card.css +32 -0
- package/src/css/look-board-detail.css +113 -2
- package/src/css/look-board-index.css +90 -8
- package/src/css/look-board.css +109 -2
- package/src/css/table.css +19 -0
- package/src/index.js +19 -0
- package/src/integrations/builtin/field-inspection/CnFieldInspectionCard.vue +35 -20
- package/src/integrations/builtin/field-inspection.js +21 -0
- package/src/integrations/offline/checklistSubmission.js +137 -0
- package/src/integrations/offline/fieldCollectionHelpers.js +69 -4
- package/src/integrations/offline/index.js +3 -0
- package/src/integrations/offline/offlineDb.js +6 -1
- package/src/integrations/offline/syncReplayService.js +8 -2
- package/src/schemas/app-manifest-v2.schema.json +196 -13
- package/src/utils/contentHeaderLink.js +48 -0
- package/src/utils/detailActionModel.js +59 -2
- package/src/utils/facets.js +74 -2
- package/src/utils/fetchFilterCounts.js +39 -4
- package/src/utils/fillTemplate.js +116 -0
- package/src/utils/initials.js +29 -0
- package/src/utils/libraryWidgetKeys.js +11 -0
- package/src/utils/objectWrite.js +55 -0
- package/src/utils/pageControls.js +78 -0
- package/src/utils/personalLenses.js +39 -2
- package/src/utils/queryPresets.js +93 -0
- package/src/utils/resolveFilterTokens.js +14 -1
- package/src/utils/resolveManifestTokens.js +2 -0
- package/src/utils/schema.js +21 -0
- package/src/utils/sentinelTokens.js +22 -4
- package/src/utils/userGroupsPeek.js +87 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
- package/src/utils/visibleWhen.js +56 -4
- package/src/utils/widgetDispatch.js +7 -1
- package/src/utils/widgetVisibility.js +7 -56
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* contentHeaderLink — the text link a widget's content asks its card header
|
|
6
|
+
* to carry: `content.headerLink`, or the shorter `viewAllRoute` (with an
|
|
7
|
+
* optional `viewAllLabel`) that an inbox-style widget uses for its "View all".
|
|
8
|
+
*
|
|
9
|
+
* @module utils/contentHeaderLink
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The header link a widget's `content` declares, in the shape
|
|
14
|
+
* CnWidgetWrapper's `headerLink` takes, or null when it declares none.
|
|
15
|
+
* The label is left as written: the wrapper translates it.
|
|
16
|
+
*
|
|
17
|
+
* `viewAllRoute` is a route name, or a location `{ name, params?, query? }`.
|
|
18
|
+
*
|
|
19
|
+
* @param {object|null|undefined} content The widget's content.
|
|
20
|
+
* @return {{label: string, route?: string, params?: object, query?: object, href?: string}|null}
|
|
21
|
+
*/
|
|
22
|
+
function contentHeaderLink(content) {
|
|
23
|
+
if (!content || typeof content !== 'object') {
|
|
24
|
+
return null
|
|
25
|
+
}
|
|
26
|
+
const declared = content.headerLink;
|
|
27
|
+
if (declared && typeof declared === 'object' && typeof declared.label === 'string' && declared.label !== '') {
|
|
28
|
+
return declared
|
|
29
|
+
}
|
|
30
|
+
const view = content.viewAllRoute;
|
|
31
|
+
const name = typeof view === 'string' ? view : (view && typeof view === 'object' ? view.name : '');
|
|
32
|
+
if (typeof name !== 'string' || name === '') {
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
const link = {
|
|
36
|
+
label: typeof content.viewAllLabel === 'string' && content.viewAllLabel !== '' ? content.viewAllLabel : 'View all',
|
|
37
|
+
route: name,
|
|
38
|
+
};
|
|
39
|
+
if (view && typeof view === 'object') {
|
|
40
|
+
if (view.params && typeof view.params === 'object') {
|
|
41
|
+
link.params = view.params;
|
|
42
|
+
}
|
|
43
|
+
if (view.query && typeof view.query === 'object') {
|
|
44
|
+
link.query = view.query;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return link
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { contentHeaderLink };
|
|
51
|
+
//# sourceMappingURL=contentHeaderLink.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contentHeaderLink.js","sources":["../../../src/utils/contentHeaderLink.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * contentHeaderLink — the text link a widget's content asks its card header\n * to carry: `content.headerLink`, or the shorter `viewAllRoute` (with an\n * optional `viewAllLabel`) that an inbox-style widget uses for its \"View all\".\n *\n * @module utils/contentHeaderLink\n */\n\n/**\n * The header link a widget's `content` declares, in the shape\n * CnWidgetWrapper's `headerLink` takes, or null when it declares none.\n * The label is left as written: the wrapper translates it.\n *\n * `viewAllRoute` is a route name, or a location `{ name, params?, query? }`.\n *\n * @param {object|null|undefined} content The widget's content.\n * @return {{label: string, route?: string, params?: object, query?: object, href?: string}|null}\n */\nexport function contentHeaderLink(content) {\n\tif (!content || typeof content !== 'object') {\n\t\treturn null\n\t}\n\tconst declared = content.headerLink\n\tif (declared && typeof declared === 'object' && typeof declared.label === 'string' && declared.label !== '') {\n\t\treturn declared\n\t}\n\tconst view = content.viewAllRoute\n\tconst name = typeof view === 'string' ? view : (view && typeof view === 'object' ? view.name : '')\n\tif (typeof name !== 'string' || name === '') {\n\t\treturn null\n\t}\n\tconst link = {\n\t\tlabel: typeof content.viewAllLabel === 'string' && content.viewAllLabel !== '' ? content.viewAllLabel : 'View all',\n\t\troute: name,\n\t}\n\tif (view && typeof view === 'object') {\n\t\tif (view.params && typeof view.params === 'object') {\n\t\t\tlink.params = view.params\n\t\t}\n\t\tif (view.query && typeof view.query === 'object') {\n\t\t\tlink.query = view.query\n\t\t}\n\t}\n\treturn link\n}\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,iBAAiB,CAAC,OAAO,EAAE;AAC3C,CAAC,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE;AAC9C,EAAE,OAAO,IAAI;AACb,EAAE;AACF,CAAC,MAAM,QAAQ,GAAG,OAAO,CAAC,WAAU;AACpC,CAAC,IAAI,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,OAAO,QAAQ,CAAC,KAAK,KAAK,QAAQ,IAAI,QAAQ,CAAC,KAAK,KAAK,EAAE,EAAE;AAC9G,EAAE,OAAO,QAAQ;AACjB,EAAE;AACF,CAAC,MAAM,IAAI,GAAG,OAAO,CAAC,aAAY;AAClC,CAAC,MAAM,IAAI,GAAG,OAAO,IAAI,KAAK,QAAQ,GAAG,IAAI,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,GAAG,IAAI,CAAC,IAAI,GAAG,EAAE,EAAC;AACnG,CAAC,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,EAAE,EAAE;AAC9C,EAAE,OAAO,IAAI;AACb,EAAE;AACF,CAAC,MAAM,IAAI,GAAG;AACd,EAAE,KAAK,EAAE,OAAO,OAAO,CAAC,YAAY,KAAK,QAAQ,IAAI,OAAO,CAAC,YAAY,KAAK,EAAE,GAAG,OAAO,CAAC,YAAY,GAAG,UAAU;AACpH,EAAE,KAAK,EAAE,IAAI;AACb,GAAE;AACF,CAAC,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE;AACvC,EAAE,IAAI,IAAI,CAAC,MAAM,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,EAAE;AACtD,GAAG,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,OAAM;AAC5B,GAAG;AACH,EAAE,IAAI,IAAI,CAAC,KAAK,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,EAAE;AACpD,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,MAAK;AAC1B,GAAG;AACH,EAAE;AACF,CAAC,OAAO,IAAI;AACZ;;;;"}
|
|
@@ -202,13 +202,29 @@ function resolveNextStep(config, object, stageField = 'status') {
|
|
|
202
202
|
/**
|
|
203
203
|
* Resolve a header pill from the record.
|
|
204
204
|
*
|
|
205
|
-
*
|
|
205
|
+
* A pill with `field` reads it off the record; one without `field` and with
|
|
206
|
+
* `text` is a constant. A count pill (`countFrom`) is resolved by
|
|
207
|
+
* {@link resolveCountPill} once its count is known.
|
|
208
|
+
*
|
|
209
|
+
* @param {{field?: string, text?: string, colorMap?: object, labels?: object, variant?: string}|null} config The pill config.
|
|
206
210
|
* @param {object|null} object The record.
|
|
207
211
|
* @return {{label: string, colorKey: string, colorMap: object|null, variant: string}|null} What CnStatusBadge needs, or null when the field is empty.
|
|
208
212
|
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-header-pills
|
|
209
213
|
*/
|
|
210
214
|
function resolvePill(config, object) {
|
|
211
|
-
if (!config || typeof config !== 'object'
|
|
215
|
+
if (!config || typeof config !== 'object') {
|
|
216
|
+
return null
|
|
217
|
+
}
|
|
218
|
+
// A constant pill (`text`): "Inwoner", "Klant" (screens-relation-pages).
|
|
219
|
+
if ((typeof config.field !== 'string' || config.field === '') && typeof config.text === 'string' && config.text !== '') {
|
|
220
|
+
return {
|
|
221
|
+
label: config.text,
|
|
222
|
+
colorKey: config.text,
|
|
223
|
+
colorMap: config.colorMap && typeof config.colorMap === 'object' ? config.colorMap : null,
|
|
224
|
+
variant: typeof config.variant === 'string' && config.variant !== '' ? config.variant : 'default',
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (typeof config.field !== 'string' || config.field === '') {
|
|
212
228
|
return null
|
|
213
229
|
}
|
|
214
230
|
const raw = stageOf(object, config.field);
|
|
@@ -262,5 +278,46 @@ function resolveTabCount(tab, object) {
|
|
|
262
278
|
return Number.isFinite(number) ? number : null
|
|
263
279
|
}
|
|
264
280
|
|
|
265
|
-
|
|
281
|
+
/**
|
|
282
|
+
* The list a count pill counts (`countFrom`), or null for any other pill.
|
|
283
|
+
*
|
|
284
|
+
* @param {object|null} config The pill config.
|
|
285
|
+
* @return {{register: string, schema: string, filter: object}|null} The list.
|
|
286
|
+
* @spec openspec/changes/screens-relation-pages/specs/relation-pages/spec.md#requirement-a-header-pill-can-be-a-constant-or-a-count
|
|
287
|
+
*/
|
|
288
|
+
function pillCountSource(config) {
|
|
289
|
+
const from = config && typeof config === 'object' && !config.field ? config.countFrom : null;
|
|
290
|
+
if (!from || typeof from !== 'object' || typeof from.register !== 'string' || from.register === '' || typeof from.schema !== 'string' || from.schema === '') {
|
|
291
|
+
return null
|
|
292
|
+
}
|
|
293
|
+
const filter = from.filter && typeof from.filter === 'object' && !Array.isArray(from.filter) ? from.filter : {};
|
|
294
|
+
return { register: from.register, schema: from.schema, filter }
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* A count pill once its count is known: "1 lopende zaak", "2 lopende zaken".
|
|
299
|
+
* Nothing for a count of 0, an unknown count or a failed one.
|
|
300
|
+
*
|
|
301
|
+
* @param {{countLabel?: {one?: string, other?: string}, colorMap?: object, variant?: string}|null} config The pill config.
|
|
302
|
+
* @param {number|null|undefined} count The count.
|
|
303
|
+
* @param {(text: string) => string} [translate] Applied to the label before `{count}` is filled.
|
|
304
|
+
* @return {{label: string, colorKey: string, colorMap: object|null, variant: string}|null} What CnStatusBadge needs, or null.
|
|
305
|
+
* @spec openspec/changes/screens-relation-pages/specs/relation-pages/spec.md#requirement-a-header-pill-can-be-a-constant-or-a-count
|
|
306
|
+
*/
|
|
307
|
+
function resolveCountPill(config, count, translate = (text) => text) {
|
|
308
|
+
if (!pillCountSource(config) || typeof count !== 'number' || !Number.isFinite(count) || count <= 0) {
|
|
309
|
+
return null
|
|
310
|
+
}
|
|
311
|
+
const labels = config.countLabel && typeof config.countLabel === 'object' ? config.countLabel : {};
|
|
312
|
+
const template = (count === 1 ? labels.one : labels.other) || labels.other || labels.one || '{count}';
|
|
313
|
+
const label = translate(template).replace(/\{count\}/g, String(count));
|
|
314
|
+
return {
|
|
315
|
+
label,
|
|
316
|
+
colorKey: '',
|
|
317
|
+
colorMap: config.colorMap && typeof config.colorMap === 'object' ? config.colorMap : null,
|
|
318
|
+
variant: typeof config.variant === 'string' && config.variant !== '' ? config.variant : 'default',
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export { MAX_QUICK_ACTIONS, groupMenuEntries, isChecklistItemDone, normalisePinnedAction, pillCountSource, resolveCountPill, resolveNextStep, resolvePill, resolveTabCount, stageEntry, stageOf };
|
|
266
323
|
//# sourceMappingURL=detailActionModel.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detailActionModel.js","sources":["../../../src/utils/detailActionModel.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * detailActionModel: the rules behind a detail page's four action levels and\n * the case surfaces that read the record's stage.\n *\n * 1. the next step: one primary button, chosen by the record's stage,\n * 2. quick actions: at most three always visible buttons,\n * 3. the overflow menu: the rest, in named groups,\n * 4. admin actions: the last group, for admins only.\n *\n * Pure functions, no Vue and no fetch, so CnDetailPage, the widgets and the\n * tests all read one implementation.\n *\n * @module utils/detailActionModel\n */\n\nimport { readPath } from './readPath.js'\nimport { evaluateVisibleWhenLocal } from './visibleWhen.js'\n\n/** The most quick actions a page shows. Later entries are dropped. */\nexport const MAX_QUICK_ACTIONS = 3\n\n/**\n * The record's stage, as the string a stage map is keyed on.\n *\n * @param {object|null} object The record.\n * @param {string} [stageField] Dot-path to the stage. Defaults to `status`.\n * @return {string} The stage, or '' when the record has none.\n * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-primary-action-follows-the-stage\n */\nexport function stageOf(object, stageField = 'status') {\n\tif (!object || typeof object !== 'object') {\n\t\treturn ''\n\t}\n\tconst value = readPath(object, stageField || 'status')\n\tif (value === null || value === undefined || typeof value === 'object') {\n\t\treturn ''\n\t}\n\treturn String(value)\n}\n\n/**\n * Look a stage up in a stage-keyed map. An exact key wins; otherwise the\n * match ignores case, because a status is stored as `in_behandeling` in one\n * register and `In_Behandeling` in the next.\n *\n * @param {object|null} map The stage-keyed map.\n * @param {string} stage The stage.\n * @return {unknown} The entry, or undefined.\n * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-primary-action-follows-the-stage\n */\nexport function stageEntry(map, stage) {\n\tif (!map || typeof map !== 'object' || Array.isArray(map) || stage === '') {\n\t\treturn undefined\n\t}\n\tif (Object.hasOwn(map, stage)) {\n\t\treturn map[stage]\n\t}\n\tconst wanted = stage.toLowerCase()\n\tconst key = Object.keys(map).find((candidate) => candidate.toLowerCase() === wanted)\n\treturn key === undefined ? undefined : map[key]\n}\n\n/**\n * Turn a pinned action entry into a descriptor the page can dispatch.\n *\n * An entry is the id of a declared header action, or an action object of its\n * own. An object without an id gets `fallbackId`, since every dispatched\n * action is addressed by id.\n *\n * @param {string|object|null} entry The entry.\n * @param {string} fallbackId Id for an inline action that names none.\n * @return {{id: string, inline: object|null}|null} The id, plus the inline definition when the entry carried one.\n * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-quick-actions\n */\nexport function normalisePinnedAction(entry, fallbackId) {\n\tif (typeof entry === 'string') {\n\t\treturn entry === '' ? null : { id: entry, inline: null }\n\t}\n\tif (!entry || typeof entry !== 'object' || Array.isArray(entry)) {\n\t\treturn null\n\t}\n\tif (typeof entry.label !== 'string' || entry.label === '') {\n\t\treturn null\n\t}\n\tconst id = typeof entry.id === 'string' && entry.id !== '' ? entry.id : fallbackId\n\treturn { id, inline: { ...entry, id } }\n}\n\n/**\n * Split menu entries into the groups the overflow menu draws.\n *\n * Ungrouped entries come first, under no caption. Named groups follow in the\n * order they first appear. Admin only entries are dropped for everyone else\n * and form the last group for admins, whatever `group` they also name.\n *\n * `adminOnly` hides a control. It is never an authorization decision: the\n * endpoint behind the action decides who may call it.\n *\n * @param {Array<object>} entries Menu-ready entries, each with an `id`.\n * @param {object} actionsById The declared actions by id, carrying `group` / `adminOnly`.\n * @param {{isAdmin?: boolean, adminLabel?: string}} [options] Who is looking, and what the admin group is called.\n * @return {Array<{key: string, label: string, entries: Array<object>}>} The non-empty groups, in render order.\n * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-grouped-and-admin-only-menu-actions\n */\nexport function groupMenuEntries(entries, actionsById = {}, options = {}) {\n\tconst isAdmin = options.isAdmin === true\n\tconst plain = { key: '', label: '', entries: [] }\n\tconst named = []\n\tconst admin = { key: 'cn-admin', label: options.adminLabel || '', entries: [] }\n\tfor (const entry of Array.isArray(entries) ? entries : []) {\n\t\tconst declared = (entry && actionsById[entry.id]) || {}\n\t\tif (declared.adminOnly === true) {\n\t\t\tif (isAdmin) {\n\t\t\t\tadmin.entries.push(entry)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tconst label = typeof declared.group === 'string' ? declared.group.trim() : ''\n\t\tif (label === '') {\n\t\t\tplain.entries.push(entry)\n\t\t\tcontinue\n\t\t}\n\t\tlet group = named.find((candidate) => candidate.label === label)\n\t\tif (!group) {\n\t\t\tgroup = { key: `cn-group-${named.length}`, label, entries: [] }\n\t\t\tnamed.push(group)\n\t\t}\n\t\tgroup.entries.push(entry)\n\t}\n\treturn [plain, ...named, admin].filter((group) => group.entries.length > 0)\n}\n\n/**\n * Whether one checklist item is done for this record.\n *\n * `done` (a literal) wins, then `doneWhen` (a local visibleWhen condition),\n * then `doneField` (truthy, and for a list: not empty).\n *\n * @param {object} item The checklist item.\n * @param {object|null} object The record.\n * @return {boolean} True when the item is done.\n * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-next-step-card\n */\nexport function isChecklistItemDone(item, object) {\n\tif (!item || typeof item !== 'object') {\n\t\treturn false\n\t}\n\tif (typeof item.done === 'boolean') {\n\t\treturn item.done\n\t}\n\tconst data = object && typeof object === 'object' ? object : {}\n\tif (item.doneWhen && typeof item.doneWhen === 'object') {\n\t\treturn evaluateVisibleWhenLocal(item.doneWhen, data)\n\t}\n\tif (typeof item.doneField === 'string' && item.doneField !== '') {\n\t\tconst value = readPath(data, item.doneField)\n\t\treturn Array.isArray(value) ? value.length > 0 : Boolean(value)\n\t}\n\treturn false\n}\n\n/**\n * Resolve the \"what now\" card for the record's stage.\n *\n * @param {{stages?: object, title?: string}|null} config The `nextStep` config.\n * @param {object|null} object The record.\n * @param {string} [stageField] Dot-path to the stage.\n * @return {{stage: string, title: string, items: Array<{label: string, done: boolean, hint: string}>, after: string}|null} The card, or null when this stage declares no checklist.\n * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-next-step-card\n */\nexport function resolveNextStep(config, object, stageField = 'status') {\n\tif (!config || typeof config !== 'object') {\n\t\treturn null\n\t}\n\tconst stage = stageOf(object, config.field || stageField)\n\tconst declared = stageEntry(config.stages, stage)\n\tif (!declared || typeof declared !== 'object' || !Array.isArray(declared.checklist)) {\n\t\treturn null\n\t}\n\tconst items = declared.checklist\n\t\t.filter((item) => item && typeof item === 'object' && typeof item.label === 'string' && item.label !== '')\n\t\t.map((item) => ({\n\t\t\tlabel: item.label,\n\t\t\tdone: isChecklistItemDone(item, object),\n\t\t\thint: typeof item.hint === 'string' ? item.hint : '',\n\t\t}))\n\tif (items.length === 0) {\n\t\treturn null\n\t}\n\treturn {\n\t\tstage,\n\t\ttitle: typeof declared.title === 'string' ? declared.title : (typeof config.title === 'string' ? config.title : ''),\n\t\titems,\n\t\tafter: typeof declared.after === 'string' ? declared.after : '',\n\t}\n}\n\n/**\n * Resolve a header pill from the record.\n *\n * @param {{field?: string, colorMap?: object, labels?: object, variant?: string}|null} config The pill config.\n * @param {object|null} object The record.\n * @return {{label: string, colorKey: string, colorMap: object|null, variant: string}|null} What CnStatusBadge needs, or null when the field is empty.\n * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-header-pills\n */\nexport function resolvePill(config, object) {\n\tif (!config || typeof config !== 'object' || typeof config.field !== 'string' || config.field === '') {\n\t\treturn null\n\t}\n\tconst raw = stageOf(object, config.field)\n\tif (raw === '') {\n\t\treturn null\n\t}\n\tconst mapped = stageEntry(config.labels, raw)\n\treturn {\n\t\tlabel: typeof mapped === 'string' && mapped !== '' ? mapped : raw,\n\t\tcolorKey: raw,\n\t\tcolorMap: config.colorMap && typeof config.colorMap === 'object' ? config.colorMap : null,\n\t\tvariant: typeof config.variant === 'string' && config.variant !== '' ? config.variant : 'default',\n\t}\n}\n\n/**\n * Resolve a tab's count.\n *\n * `count` (a number) wins. Otherwise `countField` is read off the record: a\n * list counts its items, a number is taken as is. A tab that declares neither\n * takes what is new on it from `@self.unreadCounts[<unreadKey>]` (the key\n * defaults to the tab's `id`, else its `widgetId`) when that is above 0;\n * absent or 0 is no count.\n *\n * @param {{count?: number, countField?: string, unreadKey?: string, id?: string, widgetId?: string}|null} tab The tab declaration.\n * @param {object|null} object The record.\n * @return {number|null} The count, or null when the tab declares none.\n * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow\n */\nexport function resolveTabCount(tab, object) {\n\tif (!tab || typeof tab !== 'object') {\n\t\treturn null\n\t}\n\tif (typeof tab.count === 'number' && Number.isFinite(tab.count)) {\n\t\treturn tab.count\n\t}\n\tif (typeof tab.countField !== 'string' || tab.countField === '') {\n\t\tconst counts = object && typeof object === 'object' && object['@self'] && object['@self'].unreadCounts\n\t\tconst key = tab.unreadKey || tab.id || tab.widgetId\n\t\tconst unread = counts && typeof counts === 'object' && typeof key === 'string' ? Number(counts[key]) : 0\n\t\treturn Number.isFinite(unread) && unread > 0 ? unread : null\n\t}\n\tconst value = readPath(object && typeof object === 'object' ? object : {}, tab.countField)\n\tif (Array.isArray(value)) {\n\t\treturn value.length\n\t}\n\tif (value === null || value === undefined || value === '') {\n\t\treturn 0\n\t}\n\tconst number = Number(value)\n\treturn Number.isFinite(number) ? number : null\n}\n"],"names":[],"mappings":";;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACY,MAAC,iBAAiB,GAAG,EAAC;AAClC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,OAAO,CAAC,MAAM,EAAE,UAAU,GAAG,QAAQ,EAAE;AACvD,CAAC,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE;AAC5C,EAAE,OAAO,EAAE;AACX,EAAE;AACF,CAAC,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,EAAE,UAAU,IAAI,QAAQ,EAAC;AACvD,CAAC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;AACzE,EAAE,OAAO,EAAE;AACX,EAAE;AACF,CAAC,OAAO,MAAM,CAAC,KAAK,CAAC;AACrB,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,UAAU,CAAC,GAAG,EAAE,KAAK,EAAE;AACvC,CAAC,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,KAAK,KAAK,EAAE,EAAE;AAC5E,EAAE,OAAO,SAAS;AAClB,EAAE;AACF,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE;AAChC,EAAE,OAAO,GAAG,CAAC,KAAK,CAAC;AACnB,EAAE;AACF,CAAC,MAAM,MAAM,GAAG,KAAK,CAAC,WAAW,GAAE;AACnC,CAAC,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,WAAW,EAAE,KAAK,MAAM,EAAC;AACrF,CAAC,OAAO,GAAG,KAAK,SAAS,GAAG,SAAS,GAAG,GAAG,CAAC,GAAG,CAAC;AAChD,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,qBAAqB,CAAC,KAAK,EAAE,UAAU,EAAE;AACzD,CAAC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;AAChC,EAAE,OAAO,KAAK,KAAK,EAAE,GAAG,IAAI,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE;AAC1D,EAAE;AACF,CAAC,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;AAClE,EAAE,OAAO,IAAI;AACb,EAAE;AACF,CAAC,IAAI,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,KAAK,KAAK,EAAE,EAAE;AAC5D,EAAE,OAAO,IAAI;AACb,EAAE;AACF,CAAC,MAAM,EAAE,GAAG,OAAO,KAAK,CAAC,EAAE,KAAK,QAAQ,IAAI,KAAK,CAAC,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC,EAAE,GAAG,WAAU;AACnF,CAAC,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,GAAG,KAAK,EAAE,EAAE,EAAE,EAAE;AACxC,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,EAAE,EAAE,OAAO,GAAG,EAAE,EAAE;AAC1E,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,KAAK,KAAI;AACzC,CAAC,MAAM,KAAK,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,GAAE;AAClD,CAAC,MAAM,KAAK,GAAG,GAAE;AACjB,CAAC,MAAM,KAAK,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,CAAC,UAAU,IAAI,EAAE,EAAE,OAAO,EAAE,EAAE,GAAE;AAChF,CAAC,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,GAAG,EAAE,EAAE;AAC5D,EAAE,MAAM,QAAQ,GAAG,CAAC,KAAK,IAAI,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,GAAE;AACzD,EAAE,IAAI,QAAQ,CAAC,SAAS,KAAK,IAAI,EAAE;AACnC,GAAG,IAAI,OAAO,EAAE;AAChB,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,EAAC;AAC7B,IAAI;AACJ,GAAG,QAAQ;AACX,GAAG;AACH,EAAE,MAAM,KAAK,GAAG,OAAO,QAAQ,CAAC,KAAK,KAAK,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,GAAE;AAC/E,EAAE,IAAI,KAAK,KAAK,EAAE,EAAE;AACpB,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,EAAC;AAC5B,GAAG,QAAQ;AACX,GAAG;AACH,EAAE,IAAI,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,KAAK,KAAK,KAAK,EAAC;AAClE,EAAE,IAAI,CAAC,KAAK,EAAE;AACd,GAAG,KAAK,GAAG,EAAE,GAAG,EAAE,CAAC,SAAS,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,GAAE;AAClE,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,EAAC;AACpB,GAAG;AACH,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,EAAC;AAC3B,EAAE;AACF,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,KAAK,EAAE,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;AAC5E,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE;AAClD,CAAC,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE;AACxC,EAAE,OAAO,KAAK;AACd,EAAE;AACF,CAAC,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE;AACrC,EAAE,OAAO,IAAI,CAAC,IAAI;AAClB,EAAE;AACF,CAAC,MAAM,IAAI,GAAG,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,GAAG,MAAM,GAAG,GAAE;AAChE,CAAC,IAAI,IAAI,CAAC,QAAQ,IAAI,OAAO,IAAI,CAAC,QAAQ,KAAK,QAAQ,EAAE;AACzD,EAAE,OAAO,wBAAwB,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC;AACtD,EAAE;AACF,CAAC,IAAI,OAAO,IAAI,CAAC,SAAS,KAAK,QAAQ,IAAI,IAAI,CAAC,SAAS,KAAK,EAAE,EAAE;AAClE,EAAE,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,EAAC;AAC9C,EAAE,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC;AACjE,EAAE;AACF,CAAC,OAAO,KAAK;AACb,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,QAAQ,EAAE;AACvE,CAAC,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE;AAC5C,EAAE,OAAO,IAAI;AACb,EAAE;AACF,CAAC,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,IAAI,UAAU,EAAC;AAC1D,CAAC,MAAM,QAAQ,GAAG,UAAU,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,EAAC;AAClD,CAAC,IAAI,CAAC,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE;AACtF,EAAE,OAAO,IAAI;AACb,EAAE;AACF,CAAC,MAAM,KAAK,GAAG,QAAQ,CAAC,SAAS;AACjC,GAAG,MAAM,CAAC,CAAC,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,IAAI,IAAI,CAAC,KAAK,KAAK,EAAE,CAAC;AAC5G,GAAG,GAAG,CAAC,CAAC,IAAI,MAAM;AAClB,GAAG,KAAK,EAAE,IAAI,CAAC,KAAK;AACpB,GAAG,IAAI,EAAE,mBAAmB,CAAC,IAAI,EAAE,MAAM,CAAC;AAC1C,GAAG,IAAI,EAAE,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,GAAG,IAAI,CAAC,IAAI,GAAG,EAAE;AACvD,GAAG,CAAC,EAAC;AACL,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE;AACzB,EAAE,OAAO,IAAI;AACb,EAAE;AACF,CAAC,OAAO;AACR,EAAE,KAAK;AACP,EAAE,KAAK,EAAE,OAAO,QAAQ,CAAC,KAAK,KAAK,QAAQ,GAAG,QAAQ,CAAC,KAAK,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,GAAG,MAAM,CAAC,KAAK,GAAG,EAAE,CAAC;AACrH,EAAE,KAAK;AACP,EAAE,KAAK,EAAE,OAAO,QAAQ,CAAC,KAAK,KAAK,QAAQ,GAAG,QAAQ,CAAC,KAAK,GAAG,EAAE;AACjE,EAAE;AACF,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE;AAC5C,CAAC,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,KAAK,KAAK,EAAE,EAAE;AACvG,EAAE,OAAO,IAAI;AACb,EAAE;AACF,CAAC,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,EAAC;AAC1C,CAAC,IAAI,GAAG,KAAK,EAAE,EAAE;AACjB,EAAE,OAAO,IAAI;AACb,EAAE;AACF,CAAC,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,EAAC;AAC9C,CAAC,OAAO;AACR,EAAE,KAAK,EAAE,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,EAAE,GAAG,MAAM,GAAG,GAAG;AACnE,EAAE,QAAQ,EAAE,GAAG;AACf,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ,GAAG,MAAM,CAAC,QAAQ,GAAG,IAAI;AAC3F,EAAE,OAAO,EAAE,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,IAAI,MAAM,CAAC,OAAO,KAAK,EAAE,GAAG,MAAM,CAAC,OAAO,GAAG,SAAS;AACnG,EAAE;AACF,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE;AAC7C,CAAC,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE;AACtC,EAAE,OAAO,IAAI;AACb,EAAE;AACF,CAAC,IAAI,OAAO,GAAG,CAAC,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE;AAClE,EAAE,OAAO,GAAG,CAAC,KAAK;AAClB,EAAE;AACF,CAAC,IAAI,OAAO,GAAG,CAAC,UAAU,KAAK,QAAQ,IAAI,GAAG,CAAC,UAAU,KAAK,EAAE,EAAE;AAClE,EAAE,MAAM,MAAM,GAAG,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,aAAY;AACxG,EAAE,MAAM,GAAG,GAAG,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,SAAQ;AACrD,EAAE,MAAM,MAAM,GAAG,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,OAAO,GAAG,KAAK,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,EAAC;AAC1G,EAAE,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,GAAG,MAAM,GAAG,IAAI;AAC9D,EAAE;AACF,CAAC,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,GAAG,MAAM,GAAG,EAAE,EAAE,GAAG,CAAC,UAAU,EAAC;AAC3F,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;AAC3B,EAAE,OAAO,KAAK,CAAC,MAAM;AACrB,EAAE;AACF,CAAC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,EAAE;AAC5D,EAAE,OAAO,CAAC;AACV,EAAE;AACF,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,EAAC;AAC7B,CAAC,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI;AAC/C;;;;"}
|
|
1
|
+
{"version":3,"file":"detailActionModel.js","sources":["../../../src/utils/detailActionModel.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * detailActionModel: the rules behind a detail page's four action levels and\n * the case surfaces that read the record's stage.\n *\n * 1. the next step: one primary button, chosen by the record's stage,\n * 2. quick actions: at most three always visible buttons,\n * 3. the overflow menu: the rest, in named groups,\n * 4. admin actions: the last group, for admins only.\n *\n * Pure functions, no Vue and no fetch, so CnDetailPage, the widgets and the\n * tests all read one implementation.\n *\n * @module utils/detailActionModel\n */\n\nimport { readPath } from './readPath.js'\nimport { evaluateVisibleWhenLocal } from './visibleWhen.js'\n\n/** The most quick actions a page shows. Later entries are dropped. */\nexport const MAX_QUICK_ACTIONS = 3\n\n/**\n * The record's stage, as the string a stage map is keyed on.\n *\n * @param {object|null} object The record.\n * @param {string} [stageField] Dot-path to the stage. Defaults to `status`.\n * @return {string} The stage, or '' when the record has none.\n * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-primary-action-follows-the-stage\n */\nexport function stageOf(object, stageField = 'status') {\n\tif (!object || typeof object !== 'object') {\n\t\treturn ''\n\t}\n\tconst value = readPath(object, stageField || 'status')\n\tif (value === null || value === undefined || typeof value === 'object') {\n\t\treturn ''\n\t}\n\treturn String(value)\n}\n\n/**\n * Look a stage up in a stage-keyed map. An exact key wins; otherwise the\n * match ignores case, because a status is stored as `in_behandeling` in one\n * register and `In_Behandeling` in the next.\n *\n * @param {object|null} map The stage-keyed map.\n * @param {string} stage The stage.\n * @return {unknown} The entry, or undefined.\n * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-primary-action-follows-the-stage\n */\nexport function stageEntry(map, stage) {\n\tif (!map || typeof map !== 'object' || Array.isArray(map) || stage === '') {\n\t\treturn undefined\n\t}\n\tif (Object.hasOwn(map, stage)) {\n\t\treturn map[stage]\n\t}\n\tconst wanted = stage.toLowerCase()\n\tconst key = Object.keys(map).find((candidate) => candidate.toLowerCase() === wanted)\n\treturn key === undefined ? undefined : map[key]\n}\n\n/**\n * Turn a pinned action entry into a descriptor the page can dispatch.\n *\n * An entry is the id of a declared header action, or an action object of its\n * own. An object without an id gets `fallbackId`, since every dispatched\n * action is addressed by id.\n *\n * @param {string|object|null} entry The entry.\n * @param {string} fallbackId Id for an inline action that names none.\n * @return {{id: string, inline: object|null}|null} The id, plus the inline definition when the entry carried one.\n * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-quick-actions\n */\nexport function normalisePinnedAction(entry, fallbackId) {\n\tif (typeof entry === 'string') {\n\t\treturn entry === '' ? null : { id: entry, inline: null }\n\t}\n\tif (!entry || typeof entry !== 'object' || Array.isArray(entry)) {\n\t\treturn null\n\t}\n\tif (typeof entry.label !== 'string' || entry.label === '') {\n\t\treturn null\n\t}\n\tconst id = typeof entry.id === 'string' && entry.id !== '' ? entry.id : fallbackId\n\treturn { id, inline: { ...entry, id } }\n}\n\n/**\n * Split menu entries into the groups the overflow menu draws.\n *\n * Ungrouped entries come first, under no caption. Named groups follow in the\n * order they first appear. Admin only entries are dropped for everyone else\n * and form the last group for admins, whatever `group` they also name.\n *\n * `adminOnly` hides a control. It is never an authorization decision: the\n * endpoint behind the action decides who may call it.\n *\n * @param {Array<object>} entries Menu-ready entries, each with an `id`.\n * @param {object} actionsById The declared actions by id, carrying `group` / `adminOnly`.\n * @param {{isAdmin?: boolean, adminLabel?: string}} [options] Who is looking, and what the admin group is called.\n * @return {Array<{key: string, label: string, entries: Array<object>}>} The non-empty groups, in render order.\n * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-grouped-and-admin-only-menu-actions\n */\nexport function groupMenuEntries(entries, actionsById = {}, options = {}) {\n\tconst isAdmin = options.isAdmin === true\n\tconst plain = { key: '', label: '', entries: [] }\n\tconst named = []\n\tconst admin = { key: 'cn-admin', label: options.adminLabel || '', entries: [] }\n\tfor (const entry of Array.isArray(entries) ? entries : []) {\n\t\tconst declared = (entry && actionsById[entry.id]) || {}\n\t\tif (declared.adminOnly === true) {\n\t\t\tif (isAdmin) {\n\t\t\t\tadmin.entries.push(entry)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tconst label = typeof declared.group === 'string' ? declared.group.trim() : ''\n\t\tif (label === '') {\n\t\t\tplain.entries.push(entry)\n\t\t\tcontinue\n\t\t}\n\t\tlet group = named.find((candidate) => candidate.label === label)\n\t\tif (!group) {\n\t\t\tgroup = { key: `cn-group-${named.length}`, label, entries: [] }\n\t\t\tnamed.push(group)\n\t\t}\n\t\tgroup.entries.push(entry)\n\t}\n\treturn [plain, ...named, admin].filter((group) => group.entries.length > 0)\n}\n\n/**\n * Whether one checklist item is done for this record.\n *\n * `done` (a literal) wins, then `doneWhen` (a local visibleWhen condition),\n * then `doneField` (truthy, and for a list: not empty).\n *\n * @param {object} item The checklist item.\n * @param {object|null} object The record.\n * @return {boolean} True when the item is done.\n * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-next-step-card\n */\nexport function isChecklistItemDone(item, object) {\n\tif (!item || typeof item !== 'object') {\n\t\treturn false\n\t}\n\tif (typeof item.done === 'boolean') {\n\t\treturn item.done\n\t}\n\tconst data = object && typeof object === 'object' ? object : {}\n\tif (item.doneWhen && typeof item.doneWhen === 'object') {\n\t\treturn evaluateVisibleWhenLocal(item.doneWhen, data)\n\t}\n\tif (typeof item.doneField === 'string' && item.doneField !== '') {\n\t\tconst value = readPath(data, item.doneField)\n\t\treturn Array.isArray(value) ? value.length > 0 : Boolean(value)\n\t}\n\treturn false\n}\n\n/**\n * Resolve the \"what now\" card for the record's stage.\n *\n * @param {{stages?: object, title?: string}|null} config The `nextStep` config.\n * @param {object|null} object The record.\n * @param {string} [stageField] Dot-path to the stage.\n * @return {{stage: string, title: string, items: Array<{label: string, done: boolean, hint: string}>, after: string}|null} The card, or null when this stage declares no checklist.\n * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-next-step-card\n */\nexport function resolveNextStep(config, object, stageField = 'status') {\n\tif (!config || typeof config !== 'object') {\n\t\treturn null\n\t}\n\tconst stage = stageOf(object, config.field || stageField)\n\tconst declared = stageEntry(config.stages, stage)\n\tif (!declared || typeof declared !== 'object' || !Array.isArray(declared.checklist)) {\n\t\treturn null\n\t}\n\tconst items = declared.checklist\n\t\t.filter((item) => item && typeof item === 'object' && typeof item.label === 'string' && item.label !== '')\n\t\t.map((item) => ({\n\t\t\tlabel: item.label,\n\t\t\tdone: isChecklistItemDone(item, object),\n\t\t\thint: typeof item.hint === 'string' ? item.hint : '',\n\t\t}))\n\tif (items.length === 0) {\n\t\treturn null\n\t}\n\treturn {\n\t\tstage,\n\t\ttitle: typeof declared.title === 'string' ? declared.title : (typeof config.title === 'string' ? config.title : ''),\n\t\titems,\n\t\tafter: typeof declared.after === 'string' ? declared.after : '',\n\t}\n}\n\n/**\n * Resolve a header pill from the record.\n *\n * A pill with `field` reads it off the record; one without `field` and with\n * `text` is a constant. A count pill (`countFrom`) is resolved by\n * {@link resolveCountPill} once its count is known.\n *\n * @param {{field?: string, text?: string, colorMap?: object, labels?: object, variant?: string}|null} config The pill config.\n * @param {object|null} object The record.\n * @return {{label: string, colorKey: string, colorMap: object|null, variant: string}|null} What CnStatusBadge needs, or null when the field is empty.\n * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-header-pills\n */\nexport function resolvePill(config, object) {\n\tif (!config || typeof config !== 'object') {\n\t\treturn null\n\t}\n\t// A constant pill (`text`): \"Inwoner\", \"Klant\" (screens-relation-pages).\n\tif ((typeof config.field !== 'string' || config.field === '') && typeof config.text === 'string' && config.text !== '') {\n\t\treturn {\n\t\t\tlabel: config.text,\n\t\t\tcolorKey: config.text,\n\t\t\tcolorMap: config.colorMap && typeof config.colorMap === 'object' ? config.colorMap : null,\n\t\t\tvariant: typeof config.variant === 'string' && config.variant !== '' ? config.variant : 'default',\n\t\t}\n\t}\n\tif (typeof config.field !== 'string' || config.field === '') {\n\t\treturn null\n\t}\n\tconst raw = stageOf(object, config.field)\n\tif (raw === '') {\n\t\treturn null\n\t}\n\tconst mapped = stageEntry(config.labels, raw)\n\treturn {\n\t\tlabel: typeof mapped === 'string' && mapped !== '' ? mapped : raw,\n\t\tcolorKey: raw,\n\t\tcolorMap: config.colorMap && typeof config.colorMap === 'object' ? config.colorMap : null,\n\t\tvariant: typeof config.variant === 'string' && config.variant !== '' ? config.variant : 'default',\n\t}\n}\n\n/**\n * Resolve a tab's count.\n *\n * `count` (a number) wins. Otherwise `countField` is read off the record: a\n * list counts its items, a number is taken as is. A tab that declares neither\n * takes what is new on it from `@self.unreadCounts[<unreadKey>]` (the key\n * defaults to the tab's `id`, else its `widgetId`) when that is above 0;\n * absent or 0 is no count.\n *\n * @param {{count?: number, countField?: string, unreadKey?: string, id?: string, widgetId?: string}|null} tab The tab declaration.\n * @param {object|null} object The record.\n * @return {number|null} The count, or null when the tab declares none.\n * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow\n */\nexport function resolveTabCount(tab, object) {\n\tif (!tab || typeof tab !== 'object') {\n\t\treturn null\n\t}\n\tif (typeof tab.count === 'number' && Number.isFinite(tab.count)) {\n\t\treturn tab.count\n\t}\n\tif (typeof tab.countField !== 'string' || tab.countField === '') {\n\t\tconst counts = object && typeof object === 'object' && object['@self'] && object['@self'].unreadCounts\n\t\tconst key = tab.unreadKey || tab.id || tab.widgetId\n\t\tconst unread = counts && typeof counts === 'object' && typeof key === 'string' ? Number(counts[key]) : 0\n\t\treturn Number.isFinite(unread) && unread > 0 ? unread : null\n\t}\n\tconst value = readPath(object && typeof object === 'object' ? object : {}, tab.countField)\n\tif (Array.isArray(value)) {\n\t\treturn value.length\n\t}\n\tif (value === null || value === undefined || value === '') {\n\t\treturn 0\n\t}\n\tconst number = Number(value)\n\treturn Number.isFinite(number) ? number : null\n}\n\n/**\n * The list a count pill counts (`countFrom`), or null for any other pill.\n *\n * @param {object|null} config The pill config.\n * @return {{register: string, schema: string, filter: object}|null} The list.\n * @spec openspec/changes/screens-relation-pages/specs/relation-pages/spec.md#requirement-a-header-pill-can-be-a-constant-or-a-count\n */\nexport function pillCountSource(config) {\n\tconst from = config && typeof config === 'object' && !config.field ? config.countFrom : null\n\tif (!from || typeof from !== 'object' || typeof from.register !== 'string' || from.register === '' || typeof from.schema !== 'string' || from.schema === '') {\n\t\treturn null\n\t}\n\tconst filter = from.filter && typeof from.filter === 'object' && !Array.isArray(from.filter) ? from.filter : {}\n\treturn { register: from.register, schema: from.schema, filter }\n}\n\n/**\n * A count pill once its count is known: \"1 lopende zaak\", \"2 lopende zaken\".\n * Nothing for a count of 0, an unknown count or a failed one.\n *\n * @param {{countLabel?: {one?: string, other?: string}, colorMap?: object, variant?: string}|null} config The pill config.\n * @param {number|null|undefined} count The count.\n * @param {(text: string) => string} [translate] Applied to the label before `{count}` is filled.\n * @return {{label: string, colorKey: string, colorMap: object|null, variant: string}|null} What CnStatusBadge needs, or null.\n * @spec openspec/changes/screens-relation-pages/specs/relation-pages/spec.md#requirement-a-header-pill-can-be-a-constant-or-a-count\n */\nexport function resolveCountPill(config, count, translate = (text) => text) {\n\tif (!pillCountSource(config) || typeof count !== 'number' || !Number.isFinite(count) || count <= 0) {\n\t\treturn null\n\t}\n\tconst labels = config.countLabel && typeof config.countLabel === 'object' ? config.countLabel : {}\n\tconst template = (count === 1 ? labels.one : labels.other) || labels.other || labels.one || '{count}'\n\tconst label = translate(template).replace(/\\{count\\}/g, String(count))\n\treturn {\n\t\tlabel,\n\t\tcolorKey: '',\n\t\tcolorMap: config.colorMap && typeof config.colorMap === 'object' ? config.colorMap : null,\n\t\tvariant: typeof config.variant === 'string' && config.variant !== '' ? config.variant : 'default',\n\t}\n}\n"],"names":[],"mappings":"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"}
|
package/dist/esm/utils/facets.js
CHANGED
|
@@ -37,14 +37,29 @@ function normalizeFacetBucket(bucket) {
|
|
|
37
37
|
return option
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* The option id that stands for "this property holds no value".
|
|
42
|
+
*
|
|
43
|
+
* Chosen so no real bucket key can collide with it. Selecting it is sent to
|
|
44
|
+
* OpenRegister as `<property>_isnull=true` by {@link facetFilterParams}.
|
|
45
|
+
*
|
|
46
|
+
* @type {string}
|
|
47
|
+
*/
|
|
48
|
+
const MISSING_VALUE = '__cn_missing__';
|
|
49
|
+
|
|
40
50
|
/**
|
|
41
51
|
* Normalise the `facets` block of a collection response into sidebar format.
|
|
42
52
|
*
|
|
43
53
|
* Fields whose facet carries no bucket list are skipped entirely, so a caller
|
|
44
54
|
* can tell an unfaceted field from one that faceted to nothing.
|
|
45
55
|
*
|
|
56
|
+
* What OpenRegister puts beside the buckets is carried through rather than
|
|
57
|
+
* dropped: `missing` (how many objects in scope hold no value, sent as
|
|
58
|
+
* `{ results }` or `{ count }`), `type` and `title`. A missing count is only
|
|
59
|
+
* set when the backend sent one, for the same reason a bucket count is.
|
|
60
|
+
*
|
|
46
61
|
* @param {object} facets The `facets` block of an API response
|
|
47
|
-
* @return {object} `{ fieldName: { values: [{ value, count?, label? }] } }`
|
|
62
|
+
* @return {object} `{ fieldName: { values: [{ value, count?, label? }], missing?, type?, title? } }`
|
|
48
63
|
*/
|
|
49
64
|
function normalizeFacets(facets) {
|
|
50
65
|
const transformed = {};
|
|
@@ -54,12 +69,69 @@ function normalizeFacets(facets) {
|
|
|
54
69
|
if (!buckets) {
|
|
55
70
|
continue
|
|
56
71
|
}
|
|
57
|
-
|
|
72
|
+
const normalized = { values: buckets.map(normalizeFacetBucket) };
|
|
73
|
+
|
|
74
|
+
const missing = facet.missing?.results ?? facet.missing?.count;
|
|
75
|
+
if (typeof missing === 'number') {
|
|
76
|
+
normalized.missing = missing;
|
|
77
|
+
}
|
|
78
|
+
for (const name of ['type', 'title']) {
|
|
79
|
+
if (facet[name] !== undefined && facet[name] !== null) {
|
|
80
|
+
normalized[name] = facet[name];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
transformed[key] = normalized;
|
|
58
85
|
}
|
|
59
86
|
|
|
60
87
|
return transformed
|
|
61
88
|
}
|
|
62
89
|
|
|
90
|
+
/**
|
|
91
|
+
* The sidebar options for one normalised facet, with the missing-value option
|
|
92
|
+
* appended when some objects hold no value.
|
|
93
|
+
*
|
|
94
|
+
* @param {object|undefined} facet A normalised facet from {@link normalizeFacets}
|
|
95
|
+
* @param {function(number): string} missingLabel Renders the missing option's label from its count
|
|
96
|
+
* @return {Array<{id: unknown, label: string}>|null} The options, or null when the
|
|
97
|
+
* facet offers nothing, so the caller can fall back to its static options
|
|
98
|
+
*/
|
|
99
|
+
function facetOptions(facet, missingLabel) {
|
|
100
|
+
const options = (facet?.values || []).map((v) => ({
|
|
101
|
+
id: v.value,
|
|
102
|
+
label: facetOptionLabel(v),
|
|
103
|
+
}));
|
|
104
|
+
|
|
105
|
+
if (typeof facet?.missing === 'number' && facet.missing > 0) {
|
|
106
|
+
options.push({ id: MISSING_VALUE, label: missingLabel(facet.missing) });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return options.length > 0 ? options : null
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The query parameters one facet selection sends.
|
|
114
|
+
*
|
|
115
|
+
* An ordinary selection is the property itself, one value unwrapped and
|
|
116
|
+
* several as a list. The missing-value option is exclusive: no object both
|
|
117
|
+
* holds one of the chosen values and holds none, so combining them would
|
|
118
|
+
* always answer an empty list. It is sent as `<property>_isnull=true`.
|
|
119
|
+
*
|
|
120
|
+
* @param {string} key The filtered property
|
|
121
|
+
* @param {Array} values The selected option ids
|
|
122
|
+
* @return {object} The parameters to merge into the request
|
|
123
|
+
*/
|
|
124
|
+
function facetFilterParams(key, values) {
|
|
125
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
126
|
+
return {}
|
|
127
|
+
}
|
|
128
|
+
if (values.includes(MISSING_VALUE)) {
|
|
129
|
+
return { [`${key}_isnull`]: 'true' }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return { [key]: values.length === 1 ? values[0] : values }
|
|
133
|
+
}
|
|
134
|
+
|
|
63
135
|
/**
|
|
64
136
|
* Build the visible label for one faceted filter option.
|
|
65
137
|
*
|
|
@@ -82,5 +154,5 @@ function facetOptionLabel(bucket) {
|
|
|
82
154
|
return `${text} (${bucket.count})`
|
|
83
155
|
}
|
|
84
156
|
|
|
85
|
-
export { facetOptionLabel, normalizeFacetBucket, normalizeFacets };
|
|
157
|
+
export { MISSING_VALUE, facetFilterParams, facetOptionLabel, facetOptions, normalizeFacetBucket, normalizeFacets };
|
|
86
158
|
//# sourceMappingURL=facets.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"facets.js","sources":["../../../src/utils/facets.js"],"sourcesContent":["/**\n * Facet helpers shared by the object store, the search plugin and the two\n * filter sidebars.\n *\n * The shape on the wire is not ours. OpenRegister emits a terms bucket as\n * `{ key, results, label }` from its object search, and `{ value, count,\n * label }` from its facetable-field discovery. Both are real, so both are\n * read here rather than one being declared correct.\n *\n * The rule these helpers exist to hold is narrower than the field names: a\n * number that was never sent must never reach the screen as a number. An\n * absent count normalises to an absent count and renders as nothing, because\n * `(0)` is a claim about the data that no response supports.\n */\n\n/**\n * Normalise one facet bucket into the sidebar's option shape.\n *\n * `count` and `label` are omitted when the bucket carries neither name for\n * them, so a caller can distinguish \"no count\" from \"a count of zero\".\n *\n * @param {object} bucket A raw facet bucket from the API\n * @return {{value: unknown, count?: unknown, label?: unknown}} The normalised option\n */\nexport function normalizeFacetBucket(bucket) {\n\tconst option = { value: bucket.key ?? bucket.value }\n\n\tconst count = bucket.results ?? bucket.count\n\tif (count !== undefined && count !== null) {\n\t\toption.count = count\n\t}\n\n\tif (bucket.label !== undefined && bucket.label !== null) {\n\t\toption.label = bucket.label\n\t}\n\n\treturn option\n}\n\n/**\n * Normalise the `facets` block of a collection response into sidebar format.\n *\n * Fields whose facet carries no bucket list are skipped entirely, so a caller\n * can tell an unfaceted field from one that faceted to nothing.\n *\n * @param {object} facets The `facets` block of an API response\n * @return {object} `{ fieldName: { values: [{ value, count?, label? }] } }`\n */\nexport function normalizeFacets(facets) {\n\tconst transformed = {}\n\n\tfor (const [key, facet] of Object.entries(facets || {})) {\n\t\tconst buckets = facet?.buckets || facet?.data?.buckets\n\t\tif (!buckets) {\n\t\t\tcontinue\n\t\t}\n\t\
|
|
1
|
+
{"version":3,"file":"facets.js","sources":["../../../src/utils/facets.js"],"sourcesContent":["/**\n * Facet helpers shared by the object store, the search plugin and the two\n * filter sidebars.\n *\n * The shape on the wire is not ours. OpenRegister emits a terms bucket as\n * `{ key, results, label }` from its object search, and `{ value, count,\n * label }` from its facetable-field discovery. Both are real, so both are\n * read here rather than one being declared correct.\n *\n * The rule these helpers exist to hold is narrower than the field names: a\n * number that was never sent must never reach the screen as a number. An\n * absent count normalises to an absent count and renders as nothing, because\n * `(0)` is a claim about the data that no response supports.\n */\n\n/**\n * Normalise one facet bucket into the sidebar's option shape.\n *\n * `count` and `label` are omitted when the bucket carries neither name for\n * them, so a caller can distinguish \"no count\" from \"a count of zero\".\n *\n * @param {object} bucket A raw facet bucket from the API\n * @return {{value: unknown, count?: unknown, label?: unknown}} The normalised option\n */\nexport function normalizeFacetBucket(bucket) {\n\tconst option = { value: bucket.key ?? bucket.value }\n\n\tconst count = bucket.results ?? bucket.count\n\tif (count !== undefined && count !== null) {\n\t\toption.count = count\n\t}\n\n\tif (bucket.label !== undefined && bucket.label !== null) {\n\t\toption.label = bucket.label\n\t}\n\n\treturn option\n}\n\n/**\n * The option id that stands for \"this property holds no value\".\n *\n * Chosen so no real bucket key can collide with it. Selecting it is sent to\n * OpenRegister as `<property>_isnull=true` by {@link facetFilterParams}.\n *\n * @type {string}\n */\nexport const MISSING_VALUE = '__cn_missing__'\n\n/**\n * Normalise the `facets` block of a collection response into sidebar format.\n *\n * Fields whose facet carries no bucket list are skipped entirely, so a caller\n * can tell an unfaceted field from one that faceted to nothing.\n *\n * What OpenRegister puts beside the buckets is carried through rather than\n * dropped: `missing` (how many objects in scope hold no value, sent as\n * `{ results }` or `{ count }`), `type` and `title`. A missing count is only\n * set when the backend sent one, for the same reason a bucket count is.\n *\n * @param {object} facets The `facets` block of an API response\n * @return {object} `{ fieldName: { values: [{ value, count?, label? }], missing?, type?, title? } }`\n */\nexport function normalizeFacets(facets) {\n\tconst transformed = {}\n\n\tfor (const [key, facet] of Object.entries(facets || {})) {\n\t\tconst buckets = facet?.buckets || facet?.data?.buckets\n\t\tif (!buckets) {\n\t\t\tcontinue\n\t\t}\n\t\tconst normalized = { values: buckets.map(normalizeFacetBucket) }\n\n\t\tconst missing = facet.missing?.results ?? facet.missing?.count\n\t\tif (typeof missing === 'number') {\n\t\t\tnormalized.missing = missing\n\t\t}\n\t\tfor (const name of ['type', 'title']) {\n\t\t\tif (facet[name] !== undefined && facet[name] !== null) {\n\t\t\t\tnormalized[name] = facet[name]\n\t\t\t}\n\t\t}\n\n\t\ttransformed[key] = normalized\n\t}\n\n\treturn transformed\n}\n\n/**\n * The sidebar options for one normalised facet, with the missing-value option\n * appended when some objects hold no value.\n *\n * @param {object|undefined} facet A normalised facet from {@link normalizeFacets}\n * @param {function(number): string} missingLabel Renders the missing option's label from its count\n * @return {Array<{id: unknown, label: string}>|null} The options, or null when the\n * facet offers nothing, so the caller can fall back to its static options\n */\nexport function facetOptions(facet, missingLabel) {\n\tconst options = (facet?.values || []).map((v) => ({\n\t\tid: v.value,\n\t\tlabel: facetOptionLabel(v),\n\t}))\n\n\tif (typeof facet?.missing === 'number' && facet.missing > 0) {\n\t\toptions.push({ id: MISSING_VALUE, label: missingLabel(facet.missing) })\n\t}\n\n\treturn options.length > 0 ? options : null\n}\n\n/**\n * The query parameters one facet selection sends.\n *\n * An ordinary selection is the property itself, one value unwrapped and\n * several as a list. The missing-value option is exclusive: no object both\n * holds one of the chosen values and holds none, so combining them would\n * always answer an empty list. It is sent as `<property>_isnull=true`.\n *\n * @param {string} key The filtered property\n * @param {Array} values The selected option ids\n * @return {object} The parameters to merge into the request\n */\nexport function facetFilterParams(key, values) {\n\tif (!Array.isArray(values) || values.length === 0) {\n\t\treturn {}\n\t}\n\tif (values.includes(MISSING_VALUE)) {\n\t\treturn { [`${key}_isnull`]: 'true' }\n\t}\n\n\treturn { [key]: values.length === 1 ? values[0] : values }\n}\n\n/**\n * Build the visible label for one faceted filter option.\n *\n * A bucket that carries a label renders the label: a facet over a `$ref` keys\n * its buckets by identifier, so without this the filter shows the user a\n * column of uuids. A bucket with no count renders no count at all.\n *\n * @param {object} bucket A normalised facet value: `{ value, count?, label? }`\n * @return {string} The label to render for this option\n */\nexport function facetOptionLabel(bucket) {\n\tconst text = (bucket.label !== undefined && bucket.label !== null)\n\t\t? String(bucket.label)\n\t\t: String(bucket.value)\n\n\tif (bucket.count === undefined || bucket.count === null) {\n\t\treturn text\n\t}\n\n\treturn `${text} (${bucket.count})`\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -13,8 +13,9 @@ import { resolveFilterTokens } from './resolveFilterTokens.js';
|
|
|
13
13
|
* `status: closed`, ...). Those are answered by one `/grouped` request on that
|
|
14
14
|
* field. A filter that narrows on something else (two fields, an operator, a
|
|
15
15
|
* list of values) cannot be read off a grouped result, so each of those costs
|
|
16
|
-
* one `/value` count. A filter that holds a LIST of values
|
|
17
|
-
* the list endpoint, because the
|
|
16
|
+
* one `/value` count. A filter that holds a LIST of values, or tests a field
|
|
17
|
+
* for no value (`isnull`), is counted through the list endpoint, because the
|
|
18
|
+
* aggregation endpoints cannot express it.
|
|
18
19
|
* Nothing is requested for an entry that did not ask.
|
|
19
20
|
*
|
|
20
21
|
* @module utils/fetchFilterCounts
|
|
@@ -39,6 +40,37 @@ function hasListValue(filter) {
|
|
|
39
40
|
return Object.values(filter).some((value) => Array.isArray(value))
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Whether a filter tests a field for "no value" (`{ assignee: null }` or
|
|
45
|
+
* `{ supersededBy: { isnull: true } }`).
|
|
46
|
+
*
|
|
47
|
+
* OpenRegister's aggregation endpoints have no null operator: `isnull` is
|
|
48
|
+
* refused there and an empty value is dropped or matches nothing, so such a
|
|
49
|
+
* filter counted 0 while the list showed rows. The list endpoint does read
|
|
50
|
+
* `field[isnull]=true`, so a filter like this is counted through it.
|
|
51
|
+
*
|
|
52
|
+
* @param {object} filter A filter map.
|
|
53
|
+
* @return {boolean} True when any value is null or carries an `isnull` operator.
|
|
54
|
+
*/
|
|
55
|
+
function hasNullTest(filter) {
|
|
56
|
+
if (!filter || typeof filter !== 'object') {
|
|
57
|
+
return false
|
|
58
|
+
}
|
|
59
|
+
return Object.values(filter).some((value) => value === null
|
|
60
|
+
|| (value && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).some((op) => op.toLowerCase() === 'isnull')))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Whether the aggregation endpoints cannot express a filter, so it is counted
|
|
65
|
+
* through the list endpoint: a list of values, or a test for no value.
|
|
66
|
+
*
|
|
67
|
+
* @param {object} filter A filter map.
|
|
68
|
+
* @return {boolean} True when the filter needs the list endpoint.
|
|
69
|
+
*/
|
|
70
|
+
function needsListEndpoint(filter) {
|
|
71
|
+
return hasListValue(filter) || hasNullTest(filter)
|
|
72
|
+
}
|
|
73
|
+
|
|
42
74
|
/**
|
|
43
75
|
* Count the records a filter matches by asking the LIST endpoint for one
|
|
44
76
|
* record and reading its `total`. The filter is sent the way the list request
|
|
@@ -60,6 +92,9 @@ async function fetchListTotal(register, schema, filter, ctx) {
|
|
|
60
92
|
for (const [op, operand] of Object.entries(value)) {
|
|
61
93
|
params[`${key}[${op}]`] = operand;
|
|
62
94
|
}
|
|
95
|
+
} else if (value === null) {
|
|
96
|
+
// "No value": the list endpoint reads it as the isnull operator.
|
|
97
|
+
params[`${key}[isnull]`] = 'true';
|
|
63
98
|
} else {
|
|
64
99
|
params[key] = value;
|
|
65
100
|
}
|
|
@@ -126,12 +161,12 @@ async function fetchFilterCounts({ register, schema, entries, baseFilter, ctx }
|
|
|
126
161
|
// and `/value` returns exactly its number.
|
|
127
162
|
// A list of values anywhere in a request rules the aggregation endpoints
|
|
128
163
|
// out for it. A base filter with one rules them out for every entry.
|
|
129
|
-
const baseHasList =
|
|
164
|
+
const baseHasList = needsListEndpoint(base);
|
|
130
165
|
const byField = {};
|
|
131
166
|
const single = [];
|
|
132
167
|
const viaList = [];
|
|
133
168
|
for (const entry of entries) {
|
|
134
|
-
if (baseHasList ||
|
|
169
|
+
if (baseHasList || needsListEndpoint(entry.filter)) {
|
|
135
170
|
viaList.push(entry);
|
|
136
171
|
continue
|
|
137
172
|
}
|
|
@@ -182,5 +217,5 @@ async function fetchFilterCounts({ register, schema, entries, baseFilter, ctx }
|
|
|
182
217
|
return counts
|
|
183
218
|
}
|
|
184
219
|
|
|
185
|
-
export { fetchFilterCounts, fetchListTotal, hasListValue, singleEqualityOf };
|
|
220
|
+
export { fetchFilterCounts, fetchListTotal, hasListValue, hasNullTest, needsListEndpoint, singleEqualityOf };
|
|
186
221
|
//# sourceMappingURL=fetchFilterCounts.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetchFilterCounts.js","sources":["../../../src/utils/fetchFilterCounts.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * fetchFilterCounts: how many records each of a set of filters matches, in as\n * few requests as the API allows.\n *\n * Quick filters and saved views usually differ in ONE field (`status: open`,\n * `status: closed`, ...). Those are answered by one `/grouped` request on that\n * field. A filter that narrows on something else (two fields, an operator, a\n * list of values) cannot be read off a grouped result, so each of those costs\n * one `/value` count. A filter that holds a LIST of values is counted through\n * the list endpoint, because the aggregation endpoints cannot express it.\n * Nothing is requested for an entry that did not ask.\n *\n * @module utils/fetchFilterCounts\n */\n\nimport { fetchAggregateValue, fetchGroupedCounts } from './fetchAggregate.js'\nimport { buildHeaders, buildQueryString, prefixUrl } from './headers.js'\nimport { resolveFilterTokens } from './resolveFilterTokens.js'\n\n/**\n * Whether a filter holds a list of values (`{ status: ['open', 'hold'] }`).\n *\n * The aggregation endpoints have no spelling for \"one of these values\", so a\n * filter like this is never sent to them. It is counted through the list\n * endpoint instead, which is where the page's own request sends it.\n *\n * @param {object} filter A filter map.\n * @return {boolean} True when any value is an array.\n * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views\n */\nexport function hasListValue(filter) {\n\tif (!filter || typeof filter !== 'object') {\n\t\treturn false\n\t}\n\treturn Object.values(filter).some((value) => Array.isArray(value))\n}\n\n/**\n * Count the records a filter matches by asking the LIST endpoint for one\n * record and reading its `total`. The filter is sent the way the list request\n * sends it: a list of values as repeated `field[]=`, an operator as\n * `field[op]=`.\n *\n * @param {string} register The register slug.\n * @param {string} schema The schema slug.\n * @param {object} filter The filter map.\n * @param {object} [ctx] Token-resolution context for the filter.\n * @return {Promise<number|null>} The total, or null when the response carries none.\n * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views\n */\nexport async function fetchListTotal(register, schema, filter, ctx) {\n\tconst resolved = resolveFilterTokens(filter || {}, ctx || {})\n\tconst params = {}\n\tfor (const [key, value] of Object.entries(resolved || {})) {\n\t\tif (value && typeof value === 'object' && !Array.isArray(value)) {\n\t\t\tfor (const [op, operand] of Object.entries(value)) {\n\t\t\t\tparams[`${key}[${op}]`] = operand\n\t\t\t}\n\t\t} else {\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tconst qs = buildQueryString({ ...params, _limit: 1 })\n\tconst url = prefixUrl(`/apps/openregister/api/objects/${encodeURIComponent(register)}/${encodeURIComponent(schema)}${qs}`)\n\tconst response = await fetch(url, { headers: buildHeaders() })\n\tif (!response.ok) {\n\t\tthrow new Error(`list returned ${response.status}`)\n\t}\n\tconst data = await response.json()\n\treturn typeof data.total === 'number' ? data.total : null\n}\n\n/**\n * The single field a filter narrows on with a plain equality, or null when the\n * filter is anything else.\n *\n * @param {object} filter A filter map.\n * @return {{field: string, value: string}|null} The field and the value it must equal.\n * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views\n */\nexport function singleEqualityOf(filter) {\n\tif (!filter || typeof filter !== 'object' || Array.isArray(filter)) {\n\t\treturn null\n\t}\n\tconst keys = Object.keys(filter)\n\tif (keys.length !== 1) {\n\t\treturn null\n\t}\n\tconst value = filter[keys[0]]\n\tif (value === null || value === undefined || value === '' || typeof value === 'object') {\n\t\treturn null\n\t}\n\t// A token (`@me`, `@today`) resolves to a value the grouped keys are\n\t// compared against only after resolution, which the count request does.\n\tif (typeof value === 'string' && value.charAt(0) === '@') {\n\t\treturn null\n\t}\n\treturn { field: keys[0], value: String(value) }\n}\n\n/**\n * Count the records each entry's filter matches.\n *\n * @param {object} options The request.\n * @param {string} options.register The register slug.\n * @param {string} options.schema The schema slug.\n * @param {Array<{key: (string|number), filter: object}>} options.entries The filters to count, each under its own key.\n * @param {object} [options.baseFilter] A filter every count is narrowed by (the page's fixed filter).\n * @param {object} [options.ctx] Token-resolution context for the filters.\n * @return {Promise<{[key: string]: number}>} The count per entry key. An entry whose count failed is left out.\n * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views\n */\nexport async function fetchFilterCounts({ register, schema, entries, baseFilter, ctx } = {}) {\n\tconst counts = {}\n\tif (!register || !schema || !Array.isArray(entries) || entries.length === 0) {\n\t\treturn counts\n\t}\n\tconst base = (baseFilter && typeof baseFilter === 'object') ? baseFilter : {}\n\n\t// Entries that share one equality field go into one grouped request, but\n\t// only when at least two do: a lone entry costs one request either way,\n\t// and `/value` returns exactly its number.\n\t// A list of values anywhere in a request rules the aggregation endpoints\n\t// out for it. A base filter with one rules them out for every entry.\n\tconst baseHasList = hasListValue(base)\n\tconst byField = {}\n\tconst single = []\n\tconst viaList = []\n\tfor (const entry of entries) {\n\t\tif (baseHasList || hasListValue(entry.filter)) {\n\t\t\tviaList.push(entry)\n\t\t\tcontinue\n\t\t}\n\t\tconst eq = singleEqualityOf(entry.filter)\n\t\tif (eq !== null && !(eq.field in base)) {\n\t\t\t(byField[eq.field] = byField[eq.field] || []).push({ entry, value: eq.value })\n\t\t} else {\n\t\t\tsingle.push(entry)\n\t\t}\n\t}\n\tconst jobs = []\n\tfor (const [field, members] of Object.entries(byField)) {\n\t\tif (members.length < 2) {\n\t\t\tsingle.push(members[0].entry)\n\t\t\tcontinue\n\t\t}\n\t\tjobs.push(fetchGroupedCounts({ register, schema, groupBy: field, filter: base }, ctx)\n\t\t\t.then((groups) => {\n\t\t\t\tconst map = {}\n\t\t\t\tfor (const group of groups) {\n\t\t\t\t\tmap[group.key] = group.count\n\t\t\t\t}\n\t\t\t\tfor (const member of members) {\n\t\t\t\t\tcounts[member.entry.key] = map[member.value] || 0\n\t\t\t\t}\n\t\t\t})\n\t\t\t.catch(() => {}))\n\t}\n\tfor (const entry of single) {\n\t\tjobs.push(fetchAggregateValue({ register, schema, metric: 'count', filter: { ...base, ...(entry.filter || {}) } }, ctx)\n\t\t\t.then((value) => {\n\t\t\t\tif (value !== null && Number.isFinite(Number(value))) {\n\t\t\t\t\tcounts[entry.key] = Number(value)\n\t\t\t\t}\n\t\t\t})\n\t\t\t.catch(() => {}))\n\t}\n\tfor (const entry of viaList) {\n\t\tjobs.push(fetchListTotal(register, schema, { ...base, ...(entry.filter || {}) }, ctx)\n\t\t\t.then((total) => {\n\t\t\t\tif (total !== null) {\n\t\t\t\t\tcounts[entry.key] = total\n\t\t\t\t}\n\t\t\t})\n\t\t\t.catch(() => {}))\n\t}\n\tawait Promise.all(jobs)\n\treturn counts\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"fetchFilterCounts.js","sources":["../../../src/utils/fetchFilterCounts.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * fetchFilterCounts: how many records each of a set of filters matches, in as\n * few requests as the API allows.\n *\n * Quick filters and saved views usually differ in ONE field (`status: open`,\n * `status: closed`, ...). Those are answered by one `/grouped` request on that\n * field. A filter that narrows on something else (two fields, an operator, a\n * list of values) cannot be read off a grouped result, so each of those costs\n * one `/value` count. A filter that holds a LIST of values, or tests a field\n * for no value (`isnull`), is counted through the list endpoint, because the\n * aggregation endpoints cannot express it.\n * Nothing is requested for an entry that did not ask.\n *\n * @module utils/fetchFilterCounts\n */\n\nimport { fetchAggregateValue, fetchGroupedCounts } from './fetchAggregate.js'\nimport { buildHeaders, buildQueryString, prefixUrl } from './headers.js'\nimport { resolveFilterTokens } from './resolveFilterTokens.js'\n\n/**\n * Whether a filter holds a list of values (`{ status: ['open', 'hold'] }`).\n *\n * The aggregation endpoints have no spelling for \"one of these values\", so a\n * filter like this is never sent to them. It is counted through the list\n * endpoint instead, which is where the page's own request sends it.\n *\n * @param {object} filter A filter map.\n * @return {boolean} True when any value is an array.\n * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views\n */\nexport function hasListValue(filter) {\n\tif (!filter || typeof filter !== 'object') {\n\t\treturn false\n\t}\n\treturn Object.values(filter).some((value) => Array.isArray(value))\n}\n\n/**\n * Whether a filter tests a field for \"no value\" (`{ assignee: null }` or\n * `{ supersededBy: { isnull: true } }`).\n *\n * OpenRegister's aggregation endpoints have no null operator: `isnull` is\n * refused there and an empty value is dropped or matches nothing, so such a\n * filter counted 0 while the list showed rows. The list endpoint does read\n * `field[isnull]=true`, so a filter like this is counted through it.\n *\n * @param {object} filter A filter map.\n * @return {boolean} True when any value is null or carries an `isnull` operator.\n */\nexport function hasNullTest(filter) {\n\tif (!filter || typeof filter !== 'object') {\n\t\treturn false\n\t}\n\treturn Object.values(filter).some((value) => value === null\n\t\t|| (value && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).some((op) => op.toLowerCase() === 'isnull')))\n}\n\n/**\n * Whether the aggregation endpoints cannot express a filter, so it is counted\n * through the list endpoint: a list of values, or a test for no value.\n *\n * @param {object} filter A filter map.\n * @return {boolean} True when the filter needs the list endpoint.\n */\nexport function needsListEndpoint(filter) {\n\treturn hasListValue(filter) || hasNullTest(filter)\n}\n\n/**\n * Count the records a filter matches by asking the LIST endpoint for one\n * record and reading its `total`. The filter is sent the way the list request\n * sends it: a list of values as repeated `field[]=`, an operator as\n * `field[op]=`.\n *\n * @param {string} register The register slug.\n * @param {string} schema The schema slug.\n * @param {object} filter The filter map.\n * @param {object} [ctx] Token-resolution context for the filter.\n * @return {Promise<number|null>} The total, or null when the response carries none.\n * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views\n */\nexport async function fetchListTotal(register, schema, filter, ctx) {\n\tconst resolved = resolveFilterTokens(filter || {}, ctx || {})\n\tconst params = {}\n\tfor (const [key, value] of Object.entries(resolved || {})) {\n\t\tif (value && typeof value === 'object' && !Array.isArray(value)) {\n\t\t\tfor (const [op, operand] of Object.entries(value)) {\n\t\t\t\tparams[`${key}[${op}]`] = operand\n\t\t\t}\n\t\t} else if (value === null) {\n\t\t\t// \"No value\": the list endpoint reads it as the isnull operator.\n\t\t\tparams[`${key}[isnull]`] = 'true'\n\t\t} else {\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tconst qs = buildQueryString({ ...params, _limit: 1 })\n\tconst url = prefixUrl(`/apps/openregister/api/objects/${encodeURIComponent(register)}/${encodeURIComponent(schema)}${qs}`)\n\tconst response = await fetch(url, { headers: buildHeaders() })\n\tif (!response.ok) {\n\t\tthrow new Error(`list returned ${response.status}`)\n\t}\n\tconst data = await response.json()\n\treturn typeof data.total === 'number' ? data.total : null\n}\n\n/**\n * The single field a filter narrows on with a plain equality, or null when the\n * filter is anything else.\n *\n * @param {object} filter A filter map.\n * @return {{field: string, value: string}|null} The field and the value it must equal.\n * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views\n */\nexport function singleEqualityOf(filter) {\n\tif (!filter || typeof filter !== 'object' || Array.isArray(filter)) {\n\t\treturn null\n\t}\n\tconst keys = Object.keys(filter)\n\tif (keys.length !== 1) {\n\t\treturn null\n\t}\n\tconst value = filter[keys[0]]\n\tif (value === null || value === undefined || value === '' || typeof value === 'object') {\n\t\treturn null\n\t}\n\t// A token (`@me`, `@today`) resolves to a value the grouped keys are\n\t// compared against only after resolution, which the count request does.\n\tif (typeof value === 'string' && value.charAt(0) === '@') {\n\t\treturn null\n\t}\n\treturn { field: keys[0], value: String(value) }\n}\n\n/**\n * Count the records each entry's filter matches.\n *\n * @param {object} options The request.\n * @param {string} options.register The register slug.\n * @param {string} options.schema The schema slug.\n * @param {Array<{key: (string|number), filter: object}>} options.entries The filters to count, each under its own key.\n * @param {object} [options.baseFilter] A filter every count is narrowed by (the page's fixed filter).\n * @param {object} [options.ctx] Token-resolution context for the filters.\n * @return {Promise<{[key: string]: number}>} The count per entry key. An entry whose count failed is left out.\n * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views\n */\nexport async function fetchFilterCounts({ register, schema, entries, baseFilter, ctx } = {}) {\n\tconst counts = {}\n\tif (!register || !schema || !Array.isArray(entries) || entries.length === 0) {\n\t\treturn counts\n\t}\n\tconst base = (baseFilter && typeof baseFilter === 'object') ? baseFilter : {}\n\n\t// Entries that share one equality field go into one grouped request, but\n\t// only when at least two do: a lone entry costs one request either way,\n\t// and `/value` returns exactly its number.\n\t// A list of values anywhere in a request rules the aggregation endpoints\n\t// out for it. A base filter with one rules them out for every entry.\n\tconst baseHasList = needsListEndpoint(base)\n\tconst byField = {}\n\tconst single = []\n\tconst viaList = []\n\tfor (const entry of entries) {\n\t\tif (baseHasList || needsListEndpoint(entry.filter)) {\n\t\t\tviaList.push(entry)\n\t\t\tcontinue\n\t\t}\n\t\tconst eq = singleEqualityOf(entry.filter)\n\t\tif (eq !== null && !(eq.field in base)) {\n\t\t\t(byField[eq.field] = byField[eq.field] || []).push({ entry, value: eq.value })\n\t\t} else {\n\t\t\tsingle.push(entry)\n\t\t}\n\t}\n\tconst jobs = []\n\tfor (const [field, members] of Object.entries(byField)) {\n\t\tif (members.length < 2) {\n\t\t\tsingle.push(members[0].entry)\n\t\t\tcontinue\n\t\t}\n\t\tjobs.push(fetchGroupedCounts({ register, schema, groupBy: field, filter: base }, ctx)\n\t\t\t.then((groups) => {\n\t\t\t\tconst map = {}\n\t\t\t\tfor (const group of groups) {\n\t\t\t\t\tmap[group.key] = group.count\n\t\t\t\t}\n\t\t\t\tfor (const member of members) {\n\t\t\t\t\tcounts[member.entry.key] = map[member.value] || 0\n\t\t\t\t}\n\t\t\t})\n\t\t\t.catch(() => {}))\n\t}\n\tfor (const entry of single) {\n\t\tjobs.push(fetchAggregateValue({ register, schema, metric: 'count', filter: { ...base, ...(entry.filter || {}) } }, ctx)\n\t\t\t.then((value) => {\n\t\t\t\tif (value !== null && Number.isFinite(Number(value))) {\n\t\t\t\t\tcounts[entry.key] = Number(value)\n\t\t\t\t}\n\t\t\t})\n\t\t\t.catch(() => {}))\n\t}\n\tfor (const entry of viaList) {\n\t\tjobs.push(fetchListTotal(register, schema, { ...base, ...(entry.filter || {}) }, ctx)\n\t\t\t.then((total) => {\n\t\t\t\tif (total !== null) {\n\t\t\t\t\tcounts[entry.key] = total\n\t\t\t\t}\n\t\t\t})\n\t\t\t.catch(() => {}))\n\t}\n\tawait Promise.all(jobs)\n\treturn counts\n}\n"],"names":[],"mappings":"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"}
|