@conduction/nextcloud-vue 2.80.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/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +3 -3
- 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 +140 -10
- 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 +17 -3
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +63 -52
- 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 +3 -2
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +72 -0
- 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/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.vue2.js +131 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +80 -5
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +282 -69
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -1
- 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/CnFormPage/CnFormPage.vue2.js +60 -0
- 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 +29 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +48 -4
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- 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/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.vue2.js +30 -6
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +34 -25
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- 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/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/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.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 +47 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +16 -14
- 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 +7 -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 +197 -6
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js +96 -50
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.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 +17 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +9 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +55 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +1041 -12
- package/dist/esm/utils/contentHeaderLink.js +51 -0
- package/dist/esm/utils/contentHeaderLink.js.map +1 -0
- 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 +7 -1
- package/dist/esm/utils/fillTemplate.js.map +1 -1
- package/dist/esm/utils/initials.js +32 -0
- package/dist/esm/utils/initials.js.map +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js +7 -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/resolveFilterTokens.js +1 -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/sentinelTokens.js +19 -1
- 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 +1 -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 +13589 -7833
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +1041 -12
- package/l10n/en.json +9 -1
- package/l10n/nl.json +55 -1
- package/package.json +2 -1
- package/scripts/check-docs-mdx.mjs +108 -0
- package/src/components/CnActionsBar/CnActionsBar.vue +6 -1
- package/src/components/CnAppRoot/CnAppRoot.md +4 -2
- package/src/components/CnAppRoot/CnAppRoot.vue +151 -59
- package/src/components/CnBannerWidget/CnBannerWidget.vue +205 -20
- package/src/components/CnCellRenderer/CnCellRenderer.md +2 -0
- package/src/components/CnCellRenderer/CnCellRenderer.vue +47 -4
- 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 +21 -12
- package/src/components/CnDetailPage/CnDetailPage.vue +89 -3
- package/src/components/CnDocumentReviewList/CnDocumentReviewWidget.vue +1 -1
- package/src/components/CnFacetSidebar/CnFacetSidebar.vue +7 -7
- package/src/components/CnFactsWidget/CnFactsWidget.vue +142 -2
- package/src/components/CnFieldListWidget/CnFieldListWidget.vue +268 -6
- 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/CnFormPage/CnFormPage.vue +81 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +56 -4
- 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/CnRadioCardsWidget/CnRadioCardsWidget.vue +313 -0
- package/src/components/CnRadioCardsWidget/index.js +27 -0
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +55 -6
- package/src/components/CnSavedViewsWidget/CnSavedViewsWidget.vue +296 -0
- package/src/components/CnSavedViewsWidget/index.js +29 -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/CnStatWidget/CnStatWidget.vue +87 -6
- 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 +82 -3
- 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 +7 -0
- package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +7 -2
- package/src/components/CnWidgetObjectTable/dashboardRegistration.js +5 -0
- package/src/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue +309 -11
- package/src/components/index.js +15 -0
- package/src/composables/useListView.js +4 -4
- package/src/css/look-board-detail.css +36 -2
- package/src/css/look-board-index.css +39 -8
- package/src/css/look-board.css +72 -0
- package/src/index.js +15 -0
- package/src/schemas/app-manifest-v2.schema.json +93 -9
- package/src/utils/contentHeaderLink.js +48 -0
- package/src/utils/facets.js +74 -2
- package/src/utils/fetchFilterCounts.js +39 -4
- package/src/utils/fillTemplate.js +7 -1
- package/src/utils/initials.js +29 -0
- package/src/utils/libraryWidgetKeys.js +7 -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/resolveFilterTokens.js +3 -1
- package/src/utils/resolveManifestTokens.js +2 -0
- package/src/utils/sentinelTokens.js +19 -1
- 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 +1 -1
- package/src/utils/widgetVisibility.js +7 -56
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnSegmentedControl.vue3.js","sources":["../../../../src/components/CnSegmentedControl/CnSegmentedControl.vue?vue&type=template&id=7b344b4f&lang.js"],"sourcesContent":["<!--\n SPDX-FileCopyrightText: 2026 Conduction B.V.\n SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div\n\t\tclass=\"cn-segmented-control\"\n\t\t:class=\"{ 'cn-segmented-control--stretch': stretch, 'cn-segmented-control--compact': size === 'compact' }\"\n\t\t:role=\"isToggle ? 'group' : 'radiogroup'\"\n\t\t:aria-label=\"ariaLabel || null\"\n\t\t:aria-labelledby=\"ariaLabelledby || null\"\n\t\t@keydown=\"onKeydown\">\n\t\t<button\n\t\t\tv-for=\"(option, index) in normalizedOptions\"\n\t\t\t:key=\"option.key\"\n\t\t\ttype=\"button\"\n\t\t\t:role=\"isToggle ? null : 'radio'\"\n\t\t\tclass=\"cn-segmented-control__option\"\n\t\t\t:class=\"{ 'cn-segmented-control__option--checked': isChecked(option), 'cn-segmented-control__option--icon-only': option.iconOnly }\"\n\t\t\t:aria-checked=\"isToggle ? null : (isChecked(option) ? 'true' : 'false')\"\n\t\t\t:aria-pressed=\"isToggle ? (isChecked(option) ? 'true' : 'false') : null\"\n\t\t\t:tabindex=\"isToggle || index === focusableIndex ? 0 : -1\"\n\t\t\t:disabled=\"option.disabled || null\"\n\t\t\t:aria-controls=\"controls || null\"\n\t\t\t:data-value=\"option.key\"\n\t\t\t@click=\"select(option)\">\n\t\t\t<!-- @slot option Replaces the content of one option. -->\n\t\t\t<!-- @binding {object} option The option: `{ value, label, disabled, count }`. -->\n\t\t\t<!-- @binding {boolean} checked Whether this option is the chosen one. -->\n\t\t\t<slot name=\"option\" :option=\"option\" :checked=\"isChecked(option)\">\n\t\t\t\t<span class=\"cn-segmented-control__label\">{{ option.label }}</span>\n\t\t\t\t<!-- The leading space is not drawn in this flex row; it keeps a\n\t\t\t\t screen reader from reading label and count as one word. -->\n\t\t\t\t<span v-if=\"option.count !== null\" class=\"cn-segmented-control__count\">{{ ' ' + option.count }}</span>\n\t\t\t</slot>\n\t\t</button>\n\t</div>\n</template>\n\n<script>\n/**\n * CnSegmentedControl is a switch between a few views of the same thing, such\n * as \"My work / My team\". One option is always the chosen one.\n *\n * It is a radio group: screen readers announce the group and which option is\n * checked, the arrow keys move the choice, and Tab enters and leaves the group\n * in one step. Give it a name with `aria-label` (or `aria-labelledby`).\n *\n * ```vue\n * <CnSegmentedControl\n * v-model=\"view\"\n * aria-label=\"View\"\n * :options=\"[\n * { value: 'mine', label: 'My work' },\n * { value: 'team', label: 'My team' },\n * ]\" />\n * ```\n *\n * To switch between panels of content, use `CnTabs` with `variant=\"segmented\"`\n * instead: that keeps the tab and panel semantics and looks the same.\n */\nexport default {\n\tname: 'CnSegmentedControl',\n\n\tprops: {\n\t\t/**\n\t\t * The options. Each is `{ value, label, disabled?, count? }`, or a\n\t\t * plain string that is both the value and the label. `count` shows a\n\t\t * number after the label.\n\t\t *\n\t\t * @type {Array<string|{value: (string|number|boolean), label: string, disabled?: boolean, count?: number}>}\n\t\t */\n\t\toptions: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/**\n\t\t * The value of the chosen option (v-model).\n\t\t *\n\t\t * @type {string|number|boolean|null}\n\t\t */\n\t\tmodelValue: {\n\t\t\ttype: [Boolean, String, Number],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Accessible name of the group, read out when focus enters it. */\n\t\tariaLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Id of an element that names the group, as an alternative to `ariaLabel`. */\n\t\tariaLabelledby: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Id of the element the options switch, such as the region holding a\n\t\t * page view's widgets. Set as `aria-controls` on every option.\n\t\t *\n\t\t * @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-the-view-switch-is-accessible\n\t\t */\n\t\tcontrols: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Size of the control: `normal` (default) or `compact` (the board\n\t\t * group: 3px track padding, 2px gap, 34px segments).\n\t\t *\n\t\t * @spec openspec/changes/screens-dashboard-parity/specs/dashboard-page/spec.md#requirement-a-compact-segmented-control\n\t\t */\n\t\tsize: {\n\t\t\ttype: String,\n\t\t\tdefault: 'normal',\n\t\t\tvalidator: (v) => ['normal', 'compact'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * `radio` (default) is a radio group: one option is always chosen and\n\t\t * the arrow keys move the choice. `toggle` is a group of buttons, each\n\t\t * with `aria-pressed`, for a set of presets (the board's period group);\n\t\t * Tab visits every button.\n\t\t *\n\t\t * @spec openspec/changes/screens-dashboard-parity/specs/dashboard-page/spec.md#requirement-the-period-as-a-segmented-group\n\t\t */\n\t\tmode: {\n\t\t\ttype: String,\n\t\t\tdefault: 'radio',\n\t\t\tvalidator: (v) => ['radio', 'toggle'].includes(v),\n\t\t},\n\n\t\t/** Stretch the control to the full width, with options of equal width. */\n\t\tstretch: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * Emitted with the value of the newly chosen option.\n\t\t *\n\t\t * @event update:modelValue\n\t\t * @type {string|number|boolean}\n\t\t */\n\t\t'update:modelValue',\n\t],\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the options are pressable buttons rather than radios.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisToggle() {\n\t\t\treturn this.mode === 'toggle'\n\t\t},\n\n\t\t/**\n\t\t * The options in one shape.\n\t\t *\n\t\t * @return {Array<{key: string, value: (string|number|boolean), label: string, disabled: boolean, count: (number|null)}>}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-segmented-control\n\t\t */\n\t\tnormalizedOptions() {\n\t\t\treturn (Array.isArray(this.options) ? this.options : [])\n\t\t\t\t.filter((option) => option !== null && option !== undefined)\n\t\t\t\t.map((option) => {\n\t\t\t\t\tif (typeof option !== 'object') {\n\t\t\t\t\t\treturn { key: String(option), value: option, label: String(option), disabled: false, count: null, iconOnly: false }\n\t\t\t\t\t}\n\t\t\t\t\treturn {\n\t\t\t\t\t\tkey: String(option.value),\n\t\t\t\t\t\tvalue: option.value,\n\t\t\t\t\t\tlabel: String(option.label ?? option.value),\n\t\t\t\t\t\tdisabled: option.disabled === true,\n\t\t\t\t\t\tcount: Number.isFinite(option.count) ? option.count : null,\n\t\t\t\t\t\ticonOnly: option.iconOnly === true,\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * The option Tab lands on: the chosen one, else the first enabled one.\n\t\t *\n\t\t * @return {number} Its index, or -1 when no option can take focus.\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-segmented-control\n\t\t */\n\t\tfocusableIndex() {\n\t\t\tconst checked = this.normalizedOptions.findIndex((option) => this.isChecked(option) && !option.disabled)\n\t\t\tif (checked !== -1) {\n\t\t\t\treturn checked\n\t\t\t}\n\t\t\treturn this.normalizedOptions.findIndex((option) => !option.disabled)\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Whether an option is the chosen one.\n\t\t *\n\t\t * @param {{value: (string|number|boolean)}} option The option.\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-segmented-control\n\t\t */\n\t\tisChecked(option) {\n\t\t\treturn this.modelValue !== null && String(option.value) === String(this.modelValue)\n\t\t},\n\n\t\t/**\n\t\t * Choose an option.\n\t\t *\n\t\t * @param {{value: (string|number|boolean), disabled: boolean}} option The option.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-segmented-control\n\t\t */\n\t\tselect(option) {\n\t\t\tif (option.disabled || this.isChecked(option)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$emit('update:modelValue', option.value)\n\t\t},\n\n\t\t/**\n\t\t * Arrow keys, Home and End move the choice, as in a native radio\n\t\t * group. Disabled options are passed over and the ends wrap around.\n\t\t *\n\t\t * @param {KeyboardEvent} event The key press.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-segmented-control\n\t\t */\n\t\tonKeydown(event) {\n\t\t\tif (this.isToggle) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst enabled = this.normalizedOptions\n\t\t\t\t.map((option, index) => ({ option, index }))\n\t\t\t\t.filter((entry) => !entry.option.disabled)\n\t\t\tif (enabled.length === 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst buttons = Array.from(this.$el.querySelectorAll('[role=\"radio\"]'))\n\t\t\tconst focused = buttons.indexOf(document.activeElement)\n\t\t\tconst from = focused !== -1 ? focused : this.focusableIndex\n\t\t\tlet position = enabled.findIndex((entry) => entry.index === from)\n\t\t\tif (position === -1) {\n\t\t\t\tposition = 0\n\t\t\t}\n\t\t\tif (event.key === 'ArrowRight' || event.key === 'ArrowDown') {\n\t\t\t\tposition = (position + 1) % enabled.length\n\t\t\t} else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') {\n\t\t\t\tposition = (position - 1 + enabled.length) % enabled.length\n\t\t\t} else if (event.key === 'Home') {\n\t\t\t\tposition = 0\n\t\t\t} else if (event.key === 'End') {\n\t\t\t\tposition = enabled.length - 1\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\tconst target = enabled[position]\n\t\t\tthis.select(target.option)\n\t\t\tif (buttons[target.index] && typeof buttons[target.index].focus === 'function') {\n\t\t\t\tbuttons[target.index].focus()\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-segmented-control {\n\tdisplay: inline-flex;\n\tgap: 4px;\n\tmax-width: 100%;\n\tpadding: 4px;\n\tborder-radius: var(--border-radius-container, var(--border-radius-large, 10px));\n\tbackground-color: var(--color-background-dark);\n\toverflow-x: auto;\n}\n\n.cn-segmented-control--stretch {\n\tdisplay: flex;\n}\n\n.cn-segmented-control--stretch .cn-segmented-control__option {\n\tflex: 1 1 0;\n}\n\n/* The parent class outscores Nextcloud's server rule that gives every plain\n `button` a margin and a border. */\n.cn-segmented-control .cn-segmented-control__option {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\tgap: 8px;\n\tbox-sizing: border-box;\n\tmin-height: 36px;\n\tmargin: 0;\n\tpadding: 0 16px;\n\tborder: 1px solid transparent;\n\tborder-radius: var(--border-radius-element, var(--border-radius-large, 8px));\n\tbackground-color: transparent;\n\t/* Full-contrast label on every option: the choice is carried by the\n\t raised surface, not by dimming the others. */\n\tcolor: var(--color-main-text);\n\tfont: inherit;\n\tfont-weight: 600;\n\twhite-space: nowrap;\n\tcursor: pointer;\n}\n\n.cn-segmented-control .cn-segmented-control__option:hover {\n\tbackground-color: var(--color-background-hover);\n}\n\n.cn-segmented-control .cn-segmented-control__option:focus-visible {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: 2px;\n}\n\n/* The border survives forced-colours mode, which drops background and shadow. */\n.cn-segmented-control .cn-segmented-control__option--checked,\n.cn-segmented-control .cn-segmented-control__option--checked:hover {\n\tborder-color: var(--color-border-dark);\n\tbackground-color: var(--color-main-background);\n\tbox-shadow: 0 1px 2px var(--color-box-shadow);\n}\n\n.cn-segmented-control .cn-segmented-control__option[disabled] {\n\tcursor: default;\n\topacity: 0.5;\n}\n\n.cn-segmented-control__count {\n\tfont-weight: 400;\n}\n\n/* Compact (screens-dashboard-parity): the board's period group. */\n.cn-segmented-control.cn-segmented-control--compact {\n\tgap: 2px;\n\tpadding: 3px;\n\tborder-radius: 8px;\n}\n\n.cn-segmented-control.cn-segmented-control--compact .cn-segmented-control__option {\n\tmin-height: 34px;\n\theight: 34px;\n\tpadding: 0 12px;\n\tborder-radius: 6px;\n\tfont-size: 14px;\n\tfont-weight: 600;\n}\n\n.cn-segmented-control.cn-segmented-control--compact .cn-segmented-control__option--icon-only {\n\tmin-width: 40px;\n\tpadding: 0;\n}\n\n.cn-segmented-control.cn-segmented-control--compact .cn-segmented-control__option--checked,\n.cn-segmented-control.cn-segmented-control--compact .cn-segmented-control__option--checked:hover {\n\tborder-color: transparent;\n\tbox-shadow: 0 1px 2px var(--color-box-shadow);\n}\n</style>\n"],"names":["_normalizeClass","_renderList","_renderSlot","_createElementVNode","_toDisplayString"],"mappings":";;;;AAkCS,MAAA,UAAA,GAAA,EAAA,KAAA,EAAA,6BAAA,GAAA;;;;;;;;AAxBP,IAAA,KAAK,EAACA,cAAA,CAAA,CAAA,sBAAsB,EACR,EAAA,+BAAA,EAAA,MAAA,CAAA,OAAA,mCAAA,MAAA,CAAA,IAAA,KAAA,SAAA,EAAA,CAAA,CAAA;AACnB,IAAA,IAAI,EAAC,QAAA,CAAA,QAAA,GAAA,OAAA,GAAA,YAAA;AACL,IAAA,YAAU,EAAE,MAAS,CAAA,SAAA,IAAA,IAAA;AACrB,IAAA,iBAAe,EAAC,MAAA,CAAA,cAAA,IAAA,IAAA;AAChB,IAAA,SAAO,0CAAE,QAAG,CAAA,SAAA,IAAA,QAAA,CAAA,SAAA,CAAA,GAAA,IAAA,CAAA,CAAA,CAAA;;iDAEc,EAAA,IAAA,EAAAC,UAAA,CAAA,QAAA,CAAA,iBAAS,EAA3B,CAAA,MAAM,EAAE,KAAK,KAAA;;QACpB,GAAG,EAAE,MAAM,CAAC,GAAG;AAChB,QAAA,IAAI,EAAC,QAAQ;AACZ,QAAA,IAAI,EAAE,QAAQ,CAAA,QAAA,GAAA,IAAA,GAAA,OAAA;QACf,KAAK,EAAAD,cAAA,CAAA,CAAC,8BAA8B,EACF,EAAA,uCAAA,EAAA,QAAA,CAAA,SAAA,CAAA,MAAA,CAAA,EAAA,yCAAA,EAAA,MAAA,CAAA,QAAA,EAAA,CAAA,CAAA;AACjC,QAAA,cAAY,EAAE,QAAA,CAAA,QAAQ,GAAS,IAAA,IAAA,QAAA,CAAA,SAAA,CAAA,MAAA,CAAA,GAAA,MAAA,GAAA,OAAA,CAAA;AAC/B,QAAA,cAAY,EAAE,QAAA,CAAA,QAAQ,IAAA,QAAA,CAAA,SAAA,CAAA,MAAA,CAAA,GAAA,MAAA,GAAA,OAAA,IAAA,IAAA;AACtB,QAAA,QAAQ,EAAE,QAAA,CAAA,QAAQ,IAAI,KAAA,KAAA,QAAA,CAAA,cAAA,GAAA,CAAA,GAAA,CAAA,CAAA;QACtB,QAAQ,EAAE,OAAO,QAAO,IAAA,IAAA;AACxB,QAAA,eAAa,EAAE,MAAQ,CAAA,QAAA,IAAA,IAAA;QACvB,YAAU,EAAG,MAAK,CAAC,GAAE;QACrB,OAAK,EAAA,MAAA,KAAE,QAAM,CAAA,MAAA,CAAC,MAAM,CAAA,CAAA;;QAIpBE;AAAoB,UAAA,MAAM,QAAO;UAAI,OAAO,EAAE,QAAS,CAAA,SAAA,CAAC,MAAM,CAAA;WAA9D;AACA,UAAAC,kBAAA,CAAK,MAAL,EAAA,UAAK,EAAAC,eAAA,CAAA,MAAA,CAAA,KAAA,CAAA,EAAA,CAAA,YAAA;;;;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"CnSegmentedControl.vue3.js","sources":["../../../../src/components/CnSegmentedControl/CnSegmentedControl.vue?vue&type=template&id=7b344b4f&lang.js"],"sourcesContent":["<!--\n SPDX-FileCopyrightText: 2026 Conduction B.V.\n SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div\n\t\tclass=\"cn-segmented-control\"\n\t\t:class=\"{ 'cn-segmented-control--stretch': stretch, 'cn-segmented-control--compact': size === 'compact' }\"\n\t\t:role=\"isToggle ? 'group' : 'radiogroup'\"\n\t\t:aria-label=\"ariaLabel || null\"\n\t\t:aria-labelledby=\"ariaLabelledby || null\"\n\t\t@keydown=\"onKeydown\">\n\t\t<button\n\t\t\tv-for=\"(option, index) in normalizedOptions\"\n\t\t\t:key=\"option.key\"\n\t\t\ttype=\"button\"\n\t\t\t:role=\"isToggle ? null : 'radio'\"\n\t\t\tclass=\"cn-segmented-control__option\"\n\t\t\t:class=\"{ 'cn-segmented-control__option--checked': isChecked(option), 'cn-segmented-control__option--icon-only': option.iconOnly }\"\n\t\t\t:aria-checked=\"isToggle ? null : (isChecked(option) ? 'true' : 'false')\"\n\t\t\t:aria-pressed=\"isToggle ? (isChecked(option) ? 'true' : 'false') : null\"\n\t\t\t:tabindex=\"isToggle || index === focusableIndex ? 0 : -1\"\n\t\t\t:disabled=\"option.disabled || null\"\n\t\t\t:aria-controls=\"controls || null\"\n\t\t\t:data-value=\"option.key\"\n\t\t\t@click=\"select(option)\">\n\t\t\t<!-- @slot option Replaces the content of one option. -->\n\t\t\t<!-- @binding {object} option The option: `{ value, label, disabled, count }`. -->\n\t\t\t<!-- @binding {boolean} checked Whether this option is the chosen one. -->\n\t\t\t<slot name=\"option\" :option=\"option\" :checked=\"isChecked(option)\">\n\t\t\t\t<span class=\"cn-segmented-control__label\">{{ option.label }}</span>\n\t\t\t\t<!-- The leading space is not drawn in this flex row; it keeps a\n\t\t\t\t screen reader from reading label and count as one word. -->\n\t\t\t\t<span v-if=\"option.count !== null\" class=\"cn-segmented-control__count\">{{ ' ' + option.count }}</span>\n\t\t\t</slot>\n\t\t</button>\n\t</div>\n</template>\n\n<script>\n/**\n * CnSegmentedControl is a switch between a few views of the same thing, such\n * as \"My work / My team\". One option is always the chosen one.\n *\n * It is a radio group: screen readers announce the group and which option is\n * checked, the arrow keys move the choice, and Tab enters and leaves the group\n * in one step. Give it a name with `aria-label` (or `aria-labelledby`).\n *\n * ```vue\n * <CnSegmentedControl\n * v-model=\"view\"\n * aria-label=\"View\"\n * :options=\"[\n * { value: 'mine', label: 'My work' },\n * { value: 'team', label: 'My team' },\n * ]\" />\n * ```\n *\n * To switch between panels of content, use `CnTabs` with `variant=\"segmented\"`\n * instead: that keeps the tab and panel semantics and looks the same.\n */\nexport default {\n\tname: 'CnSegmentedControl',\n\n\tprops: {\n\t\t/**\n\t\t * The options. Each is `{ value, label, disabled?, count? }`, or a\n\t\t * plain string that is both the value and the label. `count` shows a\n\t\t * number after the label.\n\t\t *\n\t\t * @type {Array<string|{value: (string|number|boolean), label: string, disabled?: boolean, count?: number}>}\n\t\t */\n\t\toptions: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/**\n\t\t * The value of the chosen option (v-model).\n\t\t *\n\t\t * @type {string|number|boolean|null}\n\t\t */\n\t\tmodelValue: {\n\t\t\ttype: [Boolean, String, Number],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Accessible name of the group, read out when focus enters it. */\n\t\tariaLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Id of an element that names the group, as an alternative to `ariaLabel`. */\n\t\tariaLabelledby: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Id of the element the options switch, such as the region holding a\n\t\t * page view's widgets. Set as `aria-controls` on every option.\n\t\t *\n\t\t * @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-the-view-switch-is-accessible\n\t\t */\n\t\tcontrols: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Size of the control: `normal` (default) or `compact` (the board\n\t\t * group: 3px track padding, 2px gap, 34px segments).\n\t\t *\n\t\t * @spec openspec/changes/screens-dashboard-parity/specs/dashboard-page/spec.md#requirement-a-compact-segmented-control\n\t\t */\n\t\tsize: {\n\t\t\ttype: String,\n\t\t\tdefault: 'normal',\n\t\t\tvalidator: (v) => ['normal', 'compact'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * `radio` (default) is a radio group: one option is always chosen and\n\t\t * the arrow keys move the choice. `toggle` is a group of buttons, each\n\t\t * with `aria-pressed`, for a set of presets (the board's period group);\n\t\t * Tab visits every button.\n\t\t *\n\t\t * @spec openspec/changes/screens-dashboard-parity/specs/dashboard-page/spec.md#requirement-the-period-as-a-segmented-group\n\t\t */\n\t\tmode: {\n\t\t\ttype: String,\n\t\t\tdefault: 'radio',\n\t\t\tvalidator: (v) => ['radio', 'toggle'].includes(v),\n\t\t},\n\n\t\t/** Stretch the control to the full width, with options of equal width. */\n\t\tstretch: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * Emitted with the value of the newly chosen option.\n\t\t *\n\t\t * @event update:modelValue\n\t\t * @type {string|number|boolean}\n\t\t */\n\t\t'update:modelValue',\n\t],\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the options are pressable buttons rather than radios.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisToggle() {\n\t\t\treturn this.mode === 'toggle'\n\t\t},\n\n\t\t/**\n\t\t * The options in one shape.\n\t\t *\n\t\t * @return {Array<{key: string, value: (string|number|boolean), label: string, disabled: boolean, count: (number|null)}>}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-segmented-control\n\t\t */\n\t\tnormalizedOptions() {\n\t\t\treturn (Array.isArray(this.options) ? this.options : [])\n\t\t\t\t.filter((option) => option !== null && option !== undefined)\n\t\t\t\t.map((option) => {\n\t\t\t\t\tif (typeof option !== 'object') {\n\t\t\t\t\t\treturn { key: String(option), value: option, label: String(option), disabled: false, count: null, iconOnly: false }\n\t\t\t\t\t}\n\t\t\t\t\treturn {\n\t\t\t\t\t\tkey: String(option.value),\n\t\t\t\t\t\tvalue: option.value,\n\t\t\t\t\t\tlabel: String(option.label ?? option.value),\n\t\t\t\t\t\tdisabled: option.disabled === true,\n\t\t\t\t\t\tcount: Number.isFinite(option.count) ? option.count : null,\n\t\t\t\t\t\ticonOnly: option.iconOnly === true,\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * The option Tab lands on: the chosen one, else the first enabled one.\n\t\t *\n\t\t * @return {number} Its index, or -1 when no option can take focus.\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-segmented-control\n\t\t */\n\t\tfocusableIndex() {\n\t\t\tconst checked = this.normalizedOptions.findIndex((option) => this.isChecked(option) && !option.disabled)\n\t\t\tif (checked !== -1) {\n\t\t\t\treturn checked\n\t\t\t}\n\t\t\treturn this.normalizedOptions.findIndex((option) => !option.disabled)\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Whether an option is the chosen one.\n\t\t *\n\t\t * @param {{value: (string|number|boolean)}} option The option.\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-segmented-control\n\t\t */\n\t\tisChecked(option) {\n\t\t\treturn this.modelValue !== null && String(option.value) === String(this.modelValue)\n\t\t},\n\n\t\t/**\n\t\t * Choose an option.\n\t\t *\n\t\t * @param {{value: (string|number|boolean), disabled: boolean}} option The option.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-segmented-control\n\t\t */\n\t\tselect(option) {\n\t\t\tif (option.disabled || this.isChecked(option)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$emit('update:modelValue', option.value)\n\t\t},\n\n\t\t/**\n\t\t * Arrow keys, Home and End move the choice, as in a native radio\n\t\t * group. Disabled options are passed over and the ends wrap around.\n\t\t *\n\t\t * @param {KeyboardEvent} event The key press.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-segmented-control\n\t\t */\n\t\tonKeydown(event) {\n\t\t\tif (this.isToggle) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst enabled = this.normalizedOptions\n\t\t\t\t.map((option, index) => ({ option, index }))\n\t\t\t\t.filter((entry) => !entry.option.disabled)\n\t\t\tif (enabled.length === 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst buttons = Array.from(this.$el.querySelectorAll('[role=\"radio\"]'))\n\t\t\tconst focused = buttons.indexOf(document.activeElement)\n\t\t\tconst from = focused !== -1 ? focused : this.focusableIndex\n\t\t\tlet position = enabled.findIndex((entry) => entry.index === from)\n\t\t\tif (position === -1) {\n\t\t\t\tposition = 0\n\t\t\t}\n\t\t\tif (event.key === 'ArrowRight' || event.key === 'ArrowDown') {\n\t\t\t\tposition = (position + 1) % enabled.length\n\t\t\t} else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') {\n\t\t\t\tposition = (position - 1 + enabled.length) % enabled.length\n\t\t\t} else if (event.key === 'Home') {\n\t\t\t\tposition = 0\n\t\t\t} else if (event.key === 'End') {\n\t\t\t\tposition = enabled.length - 1\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\tconst target = enabled[position]\n\t\t\tthis.select(target.option)\n\t\t\tif (buttons[target.index] && typeof buttons[target.index].focus === 'function') {\n\t\t\t\tbuttons[target.index].focus()\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-segmented-control {\n\tdisplay: inline-flex;\n\tgap: 4px;\n\tmax-width: 100%;\n\tpadding: 4px;\n\tborder-radius: var(--border-radius-container, var(--border-radius-large, 10px));\n\tbackground-color: var(--color-background-dark);\n\toverflow-x: auto;\n}\n\n.cn-segmented-control--stretch {\n\tdisplay: flex;\n}\n\n.cn-segmented-control--stretch .cn-segmented-control__option {\n\tflex: 1 1 0;\n}\n\n/* The parent class outscores Nextcloud's server rule that gives every plain\n `button` a margin and a border. */\n.cn-segmented-control .cn-segmented-control__option {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\tgap: 8px;\n\tbox-sizing: border-box;\n\tmin-height: 36px;\n\tmargin: 0;\n\tpadding: 0 16px;\n\tborder: 1px solid transparent;\n\tborder-radius: var(--border-radius-element, var(--border-radius-large, 8px));\n\tbackground-color: transparent;\n\t/* Full-contrast label on every option: the choice is carried by the\n\t raised surface, not by dimming the others. */\n\tcolor: var(--color-main-text);\n\tfont: inherit;\n\tfont-weight: 600;\n\twhite-space: nowrap;\n\tcursor: pointer;\n}\n\n.cn-segmented-control .cn-segmented-control__option:hover {\n\tbackground-color: var(--color-background-hover);\n}\n\n.cn-segmented-control .cn-segmented-control__option:focus-visible {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: 2px;\n}\n\n/* The border survives forced-colours mode, which drops background and shadow. */\n.cn-segmented-control .cn-segmented-control__option--checked,\n.cn-segmented-control .cn-segmented-control__option--checked:hover {\n\tborder-color: var(--color-border-dark);\n\tbackground-color: var(--color-main-background);\n\tbox-shadow: 0 1px 2px var(--color-box-shadow);\n}\n\n.cn-segmented-control .cn-segmented-control__option[disabled] {\n\tcursor: default;\n\topacity: 0.5;\n}\n\n.cn-segmented-control__count {\n\tfont-weight: 400;\n}\n\n/* Compact (screens-dashboard-parity): the board's period group. */\n.cn-segmented-control.cn-segmented-control--compact {\n\tgap: 2px;\n\tpadding: 3px;\n\tborder-radius: 8px;\n}\n\n.cn-segmented-control.cn-segmented-control--compact .cn-segmented-control__option {\n\tmin-height: 34px;\n\theight: 34px;\n\tpadding: 0 12px;\n\tborder-radius: 6px;\n\tfont-size: 14px;\n\tfont-weight: 600;\n}\n\n.cn-segmented-control.cn-segmented-control--compact .cn-segmented-control__option--icon-only {\n\tmin-width: 40px;\n\tpadding: 0;\n}\n\n.cn-segmented-control.cn-segmented-control--compact .cn-segmented-control__option--checked,\n.cn-segmented-control.cn-segmented-control--compact .cn-segmented-control__option--checked:hover {\n\tborder-color: transparent;\n\tbox-shadow: 0 1px 2px var(--color-box-shadow);\n}\n\n/* On a phone (screens-dashboard-phone) the compact group's segments are 44px\n high touch targets, and the group wraps onto a second row instead of\n running off the screen. */\n@media (max-width: 640px) {\n\t.cn-segmented-control.cn-segmented-control--compact {\n\t\tflex-wrap: wrap;\n\t}\n\n\t.cn-segmented-control.cn-segmented-control--compact .cn-segmented-control__option {\n\t\tmin-height: 44px;\n\t}\n}\n</style>\n"],"names":["_normalizeClass","_renderList","_renderSlot","_createElementVNode","_toDisplayString"],"mappings":";;;;AAkCS,MAAA,UAAA,GAAA,EAAA,KAAA,EAAA,6BAAA,GAAA;;;;;;;;AAxBP,IAAA,KAAK,EAACA,cAAA,CAAA,CAAA,sBAAsB,EACR,EAAA,+BAAA,EAAA,MAAA,CAAA,OAAA,mCAAA,MAAA,CAAA,IAAA,KAAA,SAAA,EAAA,CAAA,CAAA;AACnB,IAAA,IAAI,EAAC,QAAA,CAAA,QAAA,GAAA,OAAA,GAAA,YAAA;AACL,IAAA,YAAU,EAAE,MAAS,CAAA,SAAA,IAAA,IAAA;AACrB,IAAA,iBAAe,EAAC,MAAA,CAAA,cAAA,IAAA,IAAA;AAChB,IAAA,SAAO,0CAAE,QAAG,CAAA,SAAA,IAAA,QAAA,CAAA,SAAA,CAAA,GAAA,IAAA,CAAA,CAAA,CAAA;;iDAEc,EAAA,IAAA,EAAAC,UAAA,CAAA,QAAA,CAAA,iBAAS,EAA3B,CAAA,MAAM,EAAE,KAAK,KAAA;;QACpB,GAAG,EAAE,MAAM,CAAC,GAAG;AAChB,QAAA,IAAI,EAAC,QAAQ;AACZ,QAAA,IAAI,EAAE,QAAQ,CAAA,QAAA,GAAA,IAAA,GAAA,OAAA;QACf,KAAK,EAAAD,cAAA,CAAA,CAAC,8BAA8B,EACF,EAAA,uCAAA,EAAA,QAAA,CAAA,SAAA,CAAA,MAAA,CAAA,EAAA,yCAAA,EAAA,MAAA,CAAA,QAAA,EAAA,CAAA,CAAA;AACjC,QAAA,cAAY,EAAE,QAAA,CAAA,QAAQ,GAAS,IAAA,IAAA,QAAA,CAAA,SAAA,CAAA,MAAA,CAAA,GAAA,MAAA,GAAA,OAAA,CAAA;AAC/B,QAAA,cAAY,EAAE,QAAA,CAAA,QAAQ,IAAA,QAAA,CAAA,SAAA,CAAA,MAAA,CAAA,GAAA,MAAA,GAAA,OAAA,IAAA,IAAA;AACtB,QAAA,QAAQ,EAAE,QAAA,CAAA,QAAQ,IAAI,KAAA,KAAA,QAAA,CAAA,cAAA,GAAA,CAAA,GAAA,CAAA,CAAA;QACtB,QAAQ,EAAE,OAAO,QAAO,IAAA,IAAA;AACxB,QAAA,eAAa,EAAE,MAAQ,CAAA,QAAA,IAAA,IAAA;QACvB,YAAU,EAAG,MAAK,CAAC,GAAE;QACrB,OAAK,EAAA,MAAA,KAAE,QAAM,CAAA,MAAA,CAAC,MAAM,CAAA,CAAA;;QAIpBE;AAAoB,UAAA,MAAM,QAAO;UAAI,OAAO,EAAE,QAAS,CAAA,SAAA,CAAC,MAAM,CAAA;WAA9D;AACA,UAAAC,kBAAA,CAAK,MAAL,EAAA,UAAK,EAAAC,eAAA,CAAA,MAAA,CAAA,KAAA,CAAA,EAAA,CAAA,YAAA;;;;;;;;;;;;"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import script from './CnSettingsFormWidget.vue2.js';
|
|
2
|
+
import { render } from './CnSettingsFormWidget.vue3.js';
|
|
3
|
+
|
|
4
|
+
script.render = render;
|
|
5
|
+
script.__scopeId = "data-v-731c450f";
|
|
6
|
+
script.__file = "src/components/CnSettingsFormWidget/CnSettingsFormWidget.vue";
|
|
7
|
+
const __sfc_main = script;
|
|
8
|
+
|
|
9
|
+
export { __sfc_main as default };
|
|
10
|
+
//# sourceMappingURL=CnSettingsFormWidget.vue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CnSettingsFormWidget.vue.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;"}
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { o as getFilePickerBuilder, c as FilePickerClosed } from '../../node_modules/@nextcloud/dialogs/dist/chunks/index.js';
|
|
2
|
+
import { translate } from '@nextcloud/l10n';
|
|
3
|
+
import { NcButton, NcCheckboxRadioSwitch, NcNoteCard } from '@nextcloud/vue';
|
|
4
|
+
import { objectUrlOf, changedFields, patchObject } from '../../utils/objectWrite.js';
|
|
5
|
+
import { readPath } from '../../utils/readPath.js';
|
|
6
|
+
import { serverErrorMessage } from '../../utils/serverErrorMessage.js';
|
|
7
|
+
|
|
8
|
+
const TYPES = ['text', 'textarea', 'select', 'switch', 'number', 'file'];
|
|
9
|
+
|
|
10
|
+
let uid = 0;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* CnSettingsFormWidget — the `settings-form` widget: fields of the CURRENT
|
|
14
|
+
* object as a one- or two-column grid of framed inputs with light labels and
|
|
15
|
+
* hints, saved by ONE PATCH of the fields that changed since the last save.
|
|
16
|
+
*
|
|
17
|
+
* "Last save" is the baseline this widget keeps: a save moves it forward, so
|
|
18
|
+
* editing a field back to the value the page loaded with, after a save, still
|
|
19
|
+
* counts as a change (comparing against the loaded object skipped that PATCH).
|
|
20
|
+
*
|
|
21
|
+
* @spec openspec/changes/abstract-settings-form/specs/settings-form/spec.md
|
|
22
|
+
*/
|
|
23
|
+
var script = {
|
|
24
|
+
name: 'CnSettingsFormWidget',
|
|
25
|
+
|
|
26
|
+
components: { NcButton, NcCheckboxRadioSwitch, NcNoteCard },
|
|
27
|
+
|
|
28
|
+
inject: {
|
|
29
|
+
cnTranslate: { default: () => (key) => key },
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
props: {
|
|
33
|
+
/**
|
|
34
|
+
* The widget configuration.
|
|
35
|
+
*
|
|
36
|
+
* - `fields` (required) — `[{ key, label, type?: 'text'|'textarea'|'select'|'switch'|'number'|'file', picker?: { title?, mimeTypes?, startAt?, valueTemplate? }, chooseLabel?, clearLabel?, hint?, placeholder?, options?: [{ value, label }], required?, pattern?, patternMessage?, min?, max?, rows?, wide?, readOnly? }]`; `key` is a top-level field of the object.
|
|
37
|
+
* - `columns` — 1 or 2 (default 2).
|
|
38
|
+
* - `saveLabel`, `savedText`, `readOnly`.
|
|
39
|
+
*
|
|
40
|
+
* @type {object}
|
|
41
|
+
*/
|
|
42
|
+
content: {
|
|
43
|
+
type: Object,
|
|
44
|
+
default: () => ({}),
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
/** The current detail-page object. */
|
|
48
|
+
objectData: {
|
|
49
|
+
type: Object,
|
|
50
|
+
default: null,
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
/** The current object id. */
|
|
54
|
+
objectId: {
|
|
55
|
+
type: [String, Number],
|
|
56
|
+
default: '',
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
/** OpenRegister register slug of the surface. */
|
|
60
|
+
register: {
|
|
61
|
+
type: [String, Object],
|
|
62
|
+
default: '',
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
/** OpenRegister schema slug of the surface. */
|
|
66
|
+
schema: {
|
|
67
|
+
type: [String, Object],
|
|
68
|
+
default: '',
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
emits: ['saved'],
|
|
73
|
+
|
|
74
|
+
data() {
|
|
75
|
+
uid += 1;
|
|
76
|
+
return {
|
|
77
|
+
uid,
|
|
78
|
+
form: {},
|
|
79
|
+
baseline: {},
|
|
80
|
+
errors: {},
|
|
81
|
+
saving: false,
|
|
82
|
+
notice: '',
|
|
83
|
+
noticeType: 'success',
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
computed: {
|
|
88
|
+
/**
|
|
89
|
+
* The configured fields, normalised and translated.
|
|
90
|
+
*
|
|
91
|
+
* @return {Array<object>}
|
|
92
|
+
*/
|
|
93
|
+
fields() {
|
|
94
|
+
const list = Array.isArray(this.content.fields) ? this.content.fields : [];
|
|
95
|
+
return list
|
|
96
|
+
.filter((field) => field && field.key)
|
|
97
|
+
.map((field) => ({
|
|
98
|
+
...field,
|
|
99
|
+
type: TYPES.includes(field.type) ? field.type : 'text',
|
|
100
|
+
label: this.tr(field.label || field.key),
|
|
101
|
+
hint: this.tr(field.hint || ''),
|
|
102
|
+
placeholder: this.tr(field.placeholder || ''),
|
|
103
|
+
chooseLabel: this.tr(field.chooseLabel || 'Choose a file'),
|
|
104
|
+
clearLabel: this.tr(field.clearLabel || 'Remove'),
|
|
105
|
+
rows: field.rows || 3,
|
|
106
|
+
wide: field.wide === true || field.type === 'textarea',
|
|
107
|
+
readOnly: this.readOnly || field.readOnly === true,
|
|
108
|
+
options: (Array.isArray(field.options) ? field.options : []).map((option) => (
|
|
109
|
+
typeof option === 'object' && option !== null
|
|
110
|
+
? { value: option.value, label: this.tr(String(option.label ?? option.value)) }
|
|
111
|
+
: { value: option, label: this.tr(String(option)) }
|
|
112
|
+
)),
|
|
113
|
+
}))
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
columns() {
|
|
117
|
+
return this.content.columns === 1 ? 1 : 2
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
readOnly() {
|
|
121
|
+
return this.content.readOnly === true
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
saveLabel() {
|
|
125
|
+
return this.tr(this.content.saveLabel || 'Save')
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
/** The object URL the form is saved to, or '' when it is not known. */
|
|
129
|
+
objectUrl() {
|
|
130
|
+
return objectUrlOf(this.register, this.schema, this.objectId || (this.objectData && this.objectData.id))
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
watch: {
|
|
135
|
+
objectData() {
|
|
136
|
+
this.adopt();
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
created() {
|
|
141
|
+
this.adopt(true);
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
methods: {
|
|
145
|
+
tr(text) {
|
|
146
|
+
if (typeof text !== 'string' || text === '') {
|
|
147
|
+
return ''
|
|
148
|
+
}
|
|
149
|
+
return typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
inputId(field) {
|
|
153
|
+
return `cn-settings-form-${this.uid}-${field.key}`
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* The name shown for a stored file value: its last path segment.
|
|
158
|
+
*
|
|
159
|
+
* @param {string} value The stored value.
|
|
160
|
+
* @return {string}
|
|
161
|
+
*/
|
|
162
|
+
fileName(value) {
|
|
163
|
+
const text = String(value);
|
|
164
|
+
return text.split('/').filter(Boolean).pop() || text
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Open the Nextcloud file picker and take the chosen file as the value.
|
|
169
|
+
* `field.picker` = `{ title?, mimeTypes?, startAt?, valueTemplate? }`; the
|
|
170
|
+
* template's `{path}`, `{fileid}` and `{name}` are filled from the chosen
|
|
171
|
+
* node (default `{path}`).
|
|
172
|
+
*
|
|
173
|
+
* @param {object} field The field.
|
|
174
|
+
* @return {Promise<void>}
|
|
175
|
+
* @spec openspec/changes/abstract-settings-form-file/specs/settings-form/spec.md#requirement-a-file-field-takes-a-file-from-the-nextcloud-picker
|
|
176
|
+
*/
|
|
177
|
+
async chooseFile(field) {
|
|
178
|
+
const picker = field.picker || {};
|
|
179
|
+
let builder = getFilePickerBuilder(this.tr(picker.title || field.label))
|
|
180
|
+
.setMultiSelect(false)
|
|
181
|
+
.allowDirectories(false)
|
|
182
|
+
.startAt(picker.startAt || '/')
|
|
183
|
+
.addButton({ label: translate('nextcloud-vue', 'Choose'), type: 'primary', callback: () => {} });
|
|
184
|
+
if (Array.isArray(picker.mimeTypes) && picker.mimeTypes.length > 0) {
|
|
185
|
+
builder = builder.setMimeTypeFilter(picker.mimeTypes);
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
const nodes = await builder.build().pickNodes();
|
|
189
|
+
const node = Array.isArray(nodes) ? nodes[0] : nodes;
|
|
190
|
+
if (!node) {
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
this.form[field.key] = String(picker.valueTemplate || '{path}').replace(/\{(path|fileid|name)\}/g, (match, part) => {
|
|
194
|
+
const value = part === 'name' ? (node.basename || this.fileName(node.path)) : node[part];
|
|
195
|
+
return value === undefined || value === null ? '' : String(value)
|
|
196
|
+
});
|
|
197
|
+
} catch (e) {
|
|
198
|
+
if (e instanceof FilePickerClosed) {
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
this.noticeType = 'error';
|
|
202
|
+
this.notice = this.tr('The file picker could not be opened.');
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* The stored value of a field as the input holds it.
|
|
208
|
+
*
|
|
209
|
+
* @param {object} field The field.
|
|
210
|
+
* @return {unknown}
|
|
211
|
+
*/
|
|
212
|
+
stored(field) {
|
|
213
|
+
const value = readPath(this.objectData || {}, field.key);
|
|
214
|
+
if (field.type === 'switch') {
|
|
215
|
+
return value === true
|
|
216
|
+
}
|
|
217
|
+
return value === undefined || value === null ? '' : value
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Take the object's values as the new baseline; a field the person has
|
|
222
|
+
* not touched follows it, a touched one keeps the typed value.
|
|
223
|
+
*
|
|
224
|
+
* @param {boolean} [first] Whether this is the first read.
|
|
225
|
+
*/
|
|
226
|
+
adopt(first = false) {
|
|
227
|
+
const baseline = {};
|
|
228
|
+
const form = {};
|
|
229
|
+
for (const field of this.fields) {
|
|
230
|
+
baseline[field.key] = this.stored(field);
|
|
231
|
+
const untouched = first || JSON.stringify(this.form[field.key]) === JSON.stringify(this.baseline[field.key]);
|
|
232
|
+
form[field.key] = untouched ? baseline[field.key] : this.form[field.key];
|
|
233
|
+
}
|
|
234
|
+
this.baseline = baseline;
|
|
235
|
+
this.form = form;
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* The value to send for a field: numbers as numbers, an empty value as null.
|
|
240
|
+
*
|
|
241
|
+
* @param {object} field The field.
|
|
242
|
+
* @return {unknown}
|
|
243
|
+
*/
|
|
244
|
+
outgoing(field) {
|
|
245
|
+
const value = this.form[field.key];
|
|
246
|
+
if (field.type === 'number') {
|
|
247
|
+
return value === '' || value === null || value === undefined ? null : Number(value)
|
|
248
|
+
}
|
|
249
|
+
if (field.type === 'switch') {
|
|
250
|
+
return value === true
|
|
251
|
+
}
|
|
252
|
+
if (typeof value === 'string') {
|
|
253
|
+
return value.trim() === '' ? null : value.trim()
|
|
254
|
+
}
|
|
255
|
+
return value
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Check required, number range and pattern before any write.
|
|
260
|
+
*
|
|
261
|
+
* @return {object} Error text by field key.
|
|
262
|
+
*/
|
|
263
|
+
validate() {
|
|
264
|
+
const errors = {};
|
|
265
|
+
for (const field of this.fields) {
|
|
266
|
+
if (field.type === 'switch' || field.readOnly) {
|
|
267
|
+
continue
|
|
268
|
+
}
|
|
269
|
+
const value = this.outgoing(field);
|
|
270
|
+
const empty = value === null || value === undefined || value === '';
|
|
271
|
+
if (empty) {
|
|
272
|
+
if (field.required) {
|
|
273
|
+
errors[field.key] = this.tr('Fill this in.');
|
|
274
|
+
}
|
|
275
|
+
continue
|
|
276
|
+
}
|
|
277
|
+
if (field.type === 'number') {
|
|
278
|
+
if (Number.isNaN(value) || (field.min !== undefined && value < field.min) || (field.max !== undefined && value > field.max)) {
|
|
279
|
+
errors[field.key] = this.tr('This number is not valid.');
|
|
280
|
+
}
|
|
281
|
+
} else if (field.pattern && !new RegExp(field.pattern).test(String(value))) {
|
|
282
|
+
errors[field.key] = this.tr(field.patternMessage || 'This value is not valid.');
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return errors
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Save: PATCH the fields that differ from the last saved values.
|
|
290
|
+
*
|
|
291
|
+
* @return {Promise<void>}
|
|
292
|
+
* @spec openspec/changes/abstract-settings-form/specs/settings-form/spec.md#requirement-a-settings-form-saves-only-what-changed
|
|
293
|
+
*/
|
|
294
|
+
async save() {
|
|
295
|
+
this.errors = this.validate();
|
|
296
|
+
this.notice = '';
|
|
297
|
+
if (Object.keys(this.errors).length > 0) {
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
if (!this.objectUrl) {
|
|
301
|
+
this.noticeType = 'error';
|
|
302
|
+
this.notice = this.tr('This form cannot be saved here.');
|
|
303
|
+
return
|
|
304
|
+
}
|
|
305
|
+
const next = {};
|
|
306
|
+
const compare = {};
|
|
307
|
+
for (const field of this.fields) {
|
|
308
|
+
if (!field.readOnly) {
|
|
309
|
+
next[field.key] = this.outgoing(field);
|
|
310
|
+
compare[field.key] = this.baseline[field.key] === '' ? null : this.baseline[field.key];
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
const patch = changedFields(compare, next);
|
|
314
|
+
this.saving = true;
|
|
315
|
+
try {
|
|
316
|
+
if (Object.keys(patch).length > 0) {
|
|
317
|
+
const data = await patchObject(this.objectUrl, patch);
|
|
318
|
+
const baseline = { ...this.baseline };
|
|
319
|
+
const form = { ...this.form };
|
|
320
|
+
for (const key of Object.keys(patch)) {
|
|
321
|
+
const field = this.fields.find((candidate) => candidate.key === key);
|
|
322
|
+
const written = data[key] !== undefined ? data[key] : patch[key];
|
|
323
|
+
baseline[key] = field.type === 'switch' ? written === true : (written === null ? '' : written);
|
|
324
|
+
form[key] = baseline[key];
|
|
325
|
+
}
|
|
326
|
+
this.baseline = baseline;
|
|
327
|
+
this.form = form;
|
|
328
|
+
}
|
|
329
|
+
this.noticeType = 'success';
|
|
330
|
+
this.notice = this.tr(this.content.savedText || 'Saved.');
|
|
331
|
+
/**
|
|
332
|
+
* @event saved The changed fields were written.
|
|
333
|
+
* @type {{patch: object}}
|
|
334
|
+
*/
|
|
335
|
+
this.$emit('saved', { patch });
|
|
336
|
+
} catch (e) {
|
|
337
|
+
this.noticeType = 'error';
|
|
338
|
+
this.notice = serverErrorMessage(e);
|
|
339
|
+
} finally {
|
|
340
|
+
this.saving = false;
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
export { script as default };
|
|
347
|
+
//# sourceMappingURL=CnSettingsFormWidget.vue2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CnSettingsFormWidget.vue2.js","sources":["../../../../src/components/CnSettingsFormWidget/CnSettingsFormWidget.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<form class=\"cn-settings-form\"\n\t\tdata-testid=\"cn-settings-form\"\n\t\tnovalidate\n\t\t@submit.prevent=\"save\">\n\t\t<div class=\"cn-settings-form__grid\" :class=\"`cn-settings-form__grid--${columns}`\">\n\t\t\t<div\n\t\t\t\tv-for=\"field in fields\"\n\t\t\t\t:key=\"field.key\"\n\t\t\t\tclass=\"cn-settings-form__field\"\n\t\t\t\t:class=\"{ 'cn-settings-form__field--wide': field.wide }\"\n\t\t\t\t:data-testid=\"`cn-settings-form-field-${field.key}`\">\n\t\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t\tv-if=\"field.type === 'switch'\"\n\t\t\t\t\tv-model=\"form[field.key]\"\n\t\t\t\t\ttype=\"switch\"\n\t\t\t\t\t:disabled=\"field.readOnly\">\n\t\t\t\t\t{{ field.label }}\n\t\t\t\t</NcCheckboxRadioSwitch>\n\t\t\t\t<template v-else>\n\t\t\t\t\t<label class=\"cn-settings-form__label\" :for=\"inputId(field)\">{{ field.label }}</label>\n\t\t\t\t\t<div v-if=\"field.type === 'file'\" class=\"cn-settings-form__file\" :data-testid=\"`cn-settings-form-file-${field.key}`\">\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t:id=\"inputId(field)\"\n\t\t\t\t\t\t\tclass=\"cn-settings-form__control cn-settings-form__control--file\"\n\t\t\t\t\t\t\t:class=\"{ 'cn-settings-form__control--placeholder': !form[field.key] }\">\n\t\t\t\t\t\t\t{{ form[field.key] ? fileName(form[field.key]) : field.placeholder }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<NcButton\n\t\t\t\t\t\t\tv-if=\"!field.readOnly\"\n\t\t\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t\t\t:aria-describedby=\"inputId(field)\"\n\t\t\t\t\t\t\t:data-testid=\"`cn-settings-form-choose-${field.key}`\"\n\t\t\t\t\t\t\t@click=\"chooseFile(field)\">\n\t\t\t\t\t\t\t{{ field.chooseLabel }}\n\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t<NcButton\n\t\t\t\t\t\t\tv-if=\"!field.readOnly && form[field.key]\"\n\t\t\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t\t\t:data-testid=\"`cn-settings-form-clear-${field.key}`\"\n\t\t\t\t\t\t\t@click=\"form[field.key] = ''\">\n\t\t\t\t\t\t\t{{ field.clearLabel }}\n\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t</div>\n\t\t\t\t\t<select\n\t\t\t\t\t\tv-else-if=\"field.type === 'select'\"\n\t\t\t\t\t\t:id=\"inputId(field)\"\n\t\t\t\t\t\tv-model=\"form[field.key]\"\n\t\t\t\t\t\tclass=\"cn-settings-form__control\"\n\t\t\t\t\t\t:disabled=\"field.readOnly\">\n\t\t\t\t\t\t<option v-for=\"option in field.options\" :key=\"String(option.value)\" :value=\"option.value\">\n\t\t\t\t\t\t\t{{ option.label }}\n\t\t\t\t\t\t</option>\n\t\t\t\t\t</select>\n\t\t\t\t\t<textarea\n\t\t\t\t\t\tv-else-if=\"field.type === 'textarea'\"\n\t\t\t\t\t\t:id=\"inputId(field)\"\n\t\t\t\t\t\tv-model=\"form[field.key]\"\n\t\t\t\t\t\tclass=\"cn-settings-form__control cn-settings-form__control--area\"\n\t\t\t\t\t\t:rows=\"field.rows\"\n\t\t\t\t\t\t:placeholder=\"field.placeholder\"\n\t\t\t\t\t\t:readonly=\"field.readOnly\" />\n\t\t\t\t\t<input\n\t\t\t\t\t\tv-else\n\t\t\t\t\t\t:id=\"inputId(field)\"\n\t\t\t\t\t\tv-model=\"form[field.key]\"\n\t\t\t\t\t\tclass=\"cn-settings-form__control\"\n\t\t\t\t\t\t:type=\"field.type === 'number' ? 'number' : 'text'\"\n\t\t\t\t\t\t:min=\"field.min\"\n\t\t\t\t\t\t:max=\"field.max\"\n\t\t\t\t\t\t:placeholder=\"field.placeholder\"\n\t\t\t\t\t\t:readonly=\"field.readOnly\"\n\t\t\t\t\t\t:aria-invalid=\"errors[field.key] ? 'true' : null\">\n\t\t\t\t</template>\n\t\t\t\t<p v-if=\"errors[field.key]\" class=\"cn-settings-form__error\" role=\"alert\">\n\t\t\t\t\t{{ errors[field.key] }}\n\t\t\t\t</p>\n\t\t\t\t<p v-else-if=\"field.hint\" class=\"cn-settings-form__hint\">\n\t\t\t\t\t{{ field.hint }}\n\t\t\t\t</p>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<NcNoteCard v-if=\"notice\" :type=\"noticeType\" data-testid=\"cn-settings-form-notice\">\n\t\t\t{{ notice }}\n\t\t</NcNoteCard>\n\n\t\t<div v-if=\"!readOnly\" class=\"cn-settings-form__actions\">\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\ttype=\"submit\"\n\t\t\t\t:disabled=\"saving\"\n\t\t\t\tdata-testid=\"cn-settings-form-save\">\n\t\t\t\t{{ saveLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t</form>\n</template>\n\n<script>\nimport { FilePickerClosed, getFilePickerBuilder } from '@nextcloud/dialogs'\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcCheckboxRadioSwitch, NcNoteCard } from '@nextcloud/vue'\nimport { changedFields, objectUrlOf, patchObject } from '../../utils/objectWrite.js'\nimport { readPath } from '../../utils/readPath.js'\nimport { serverErrorMessage } from '../../utils/serverErrorMessage.js'\n\n// The native file-picker modal ships its chrome styles here; without this the\n// spawned picker renders unstyled. Rules are scoped to the picker/dialog classes.\nimport '@nextcloud/dialogs/style.css'\n\nconst TYPES = ['text', 'textarea', 'select', 'switch', 'number', 'file']\n\nlet uid = 0\n\n/**\n * CnSettingsFormWidget — the `settings-form` widget: fields of the CURRENT\n * object as a one- or two-column grid of framed inputs with light labels and\n * hints, saved by ONE PATCH of the fields that changed since the last save.\n *\n * \"Last save\" is the baseline this widget keeps: a save moves it forward, so\n * editing a field back to the value the page loaded with, after a save, still\n * counts as a change (comparing against the loaded object skipped that PATCH).\n *\n * @spec openspec/changes/abstract-settings-form/specs/settings-form/spec.md\n */\nexport default {\n\tname: 'CnSettingsFormWidget',\n\n\tcomponents: { NcButton, NcCheckboxRadioSwitch, NcNoteCard },\n\n\tinject: {\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The widget configuration.\n\t\t *\n\t\t * - `fields` (required) — `[{ key, label, type?: 'text'|'textarea'|'select'|'switch'|'number'|'file', picker?: { title?, mimeTypes?, startAt?, valueTemplate? }, chooseLabel?, clearLabel?, hint?, placeholder?, options?: [{ value, label }], required?, pattern?, patternMessage?, min?, max?, rows?, wide?, readOnly? }]`; `key` is a top-level field of the object.\n\t\t * - `columns` — 1 or 2 (default 2).\n\t\t * - `saveLabel`, `savedText`, `readOnly`.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** The current detail-page object. */\n\t\tobjectData: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The current object id. */\n\t\tobjectId: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** OpenRegister register slug of the surface. */\n\t\tregister: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** OpenRegister schema slug of the surface. */\n\t\tschema: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\temits: ['saved'],\n\n\tdata() {\n\t\tuid += 1\n\t\treturn {\n\t\t\tuid,\n\t\t\tform: {},\n\t\t\tbaseline: {},\n\t\t\terrors: {},\n\t\t\tsaving: false,\n\t\t\tnotice: '',\n\t\t\tnoticeType: 'success',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The configured fields, normalised and translated.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tfields() {\n\t\t\tconst list = Array.isArray(this.content.fields) ? this.content.fields : []\n\t\t\treturn list\n\t\t\t\t.filter((field) => field && field.key)\n\t\t\t\t.map((field) => ({\n\t\t\t\t\t...field,\n\t\t\t\t\ttype: TYPES.includes(field.type) ? field.type : 'text',\n\t\t\t\t\tlabel: this.tr(field.label || field.key),\n\t\t\t\t\thint: this.tr(field.hint || ''),\n\t\t\t\t\tplaceholder: this.tr(field.placeholder || ''),\n\t\t\t\t\tchooseLabel: this.tr(field.chooseLabel || 'Choose a file'),\n\t\t\t\t\tclearLabel: this.tr(field.clearLabel || 'Remove'),\n\t\t\t\t\trows: field.rows || 3,\n\t\t\t\t\twide: field.wide === true || field.type === 'textarea',\n\t\t\t\t\treadOnly: this.readOnly || field.readOnly === true,\n\t\t\t\t\toptions: (Array.isArray(field.options) ? field.options : []).map((option) => (\n\t\t\t\t\t\ttypeof option === 'object' && option !== null\n\t\t\t\t\t\t\t? { value: option.value, label: this.tr(String(option.label ?? option.value)) }\n\t\t\t\t\t\t\t: { value: option, label: this.tr(String(option)) }\n\t\t\t\t\t)),\n\t\t\t\t}))\n\t\t},\n\n\t\tcolumns() {\n\t\t\treturn this.content.columns === 1 ? 1 : 2\n\t\t},\n\n\t\treadOnly() {\n\t\t\treturn this.content.readOnly === true\n\t\t},\n\n\t\tsaveLabel() {\n\t\t\treturn this.tr(this.content.saveLabel || 'Save')\n\t\t},\n\n\t\t/** The object URL the form is saved to, or '' when it is not known. */\n\t\tobjectUrl() {\n\t\t\treturn objectUrlOf(this.register, this.schema, this.objectId || (this.objectData && this.objectData.id))\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectData() {\n\t\t\tthis.adopt()\n\t\t},\n\t},\n\n\tcreated() {\n\t\tthis.adopt(true)\n\t},\n\n\tmethods: {\n\t\ttr(text) {\n\t\t\tif (typeof text !== 'string' || text === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text\n\t\t},\n\n\t\tinputId(field) {\n\t\t\treturn `cn-settings-form-${this.uid}-${field.key}`\n\t\t},\n\n\t\t/**\n\t\t * The name shown for a stored file value: its last path segment.\n\t\t *\n\t\t * @param {string} value The stored value.\n\t\t * @return {string}\n\t\t */\n\t\tfileName(value) {\n\t\t\tconst text = String(value)\n\t\t\treturn text.split('/').filter(Boolean).pop() || text\n\t\t},\n\n\t\t/**\n\t\t * Open the Nextcloud file picker and take the chosen file as the value.\n\t\t * `field.picker` = `{ title?, mimeTypes?, startAt?, valueTemplate? }`; the\n\t\t * template's `{path}`, `{fileid}` and `{name}` are filled from the chosen\n\t\t * node (default `{path}`).\n\t\t *\n\t\t * @param {object} field The field.\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/abstract-settings-form-file/specs/settings-form/spec.md#requirement-a-file-field-takes-a-file-from-the-nextcloud-picker\n\t\t */\n\t\tasync chooseFile(field) {\n\t\t\tconst picker = field.picker || {}\n\t\t\tlet builder = getFilePickerBuilder(this.tr(picker.title || field.label))\n\t\t\t\t.setMultiSelect(false)\n\t\t\t\t.allowDirectories(false)\n\t\t\t\t.startAt(picker.startAt || '/')\n\t\t\t\t.addButton({ label: t('nextcloud-vue', 'Choose'), type: 'primary', callback: () => {} })\n\t\t\tif (Array.isArray(picker.mimeTypes) && picker.mimeTypes.length > 0) {\n\t\t\t\tbuilder = builder.setMimeTypeFilter(picker.mimeTypes)\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst nodes = await builder.build().pickNodes()\n\t\t\t\tconst node = Array.isArray(nodes) ? nodes[0] : nodes\n\t\t\t\tif (!node) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.form[field.key] = String(picker.valueTemplate || '{path}').replace(/\\{(path|fileid|name)\\}/g, (match, part) => {\n\t\t\t\t\tconst value = part === 'name' ? (node.basename || this.fileName(node.path)) : node[part]\n\t\t\t\t\treturn value === undefined || value === null ? '' : String(value)\n\t\t\t\t})\n\t\t\t} catch (e) {\n\t\t\t\tif (e instanceof FilePickerClosed) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.noticeType = 'error'\n\t\t\t\tthis.notice = this.tr('The file picker could not be opened.')\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The stored value of a field as the input holds it.\n\t\t *\n\t\t * @param {object} field The field.\n\t\t * @return {unknown}\n\t\t */\n\t\tstored(field) {\n\t\t\tconst value = readPath(this.objectData || {}, field.key)\n\t\t\tif (field.type === 'switch') {\n\t\t\t\treturn value === true\n\t\t\t}\n\t\t\treturn value === undefined || value === null ? '' : value\n\t\t},\n\n\t\t/**\n\t\t * Take the object's values as the new baseline; a field the person has\n\t\t * not touched follows it, a touched one keeps the typed value.\n\t\t *\n\t\t * @param {boolean} [first] Whether this is the first read.\n\t\t */\n\t\tadopt(first = false) {\n\t\t\tconst baseline = {}\n\t\t\tconst form = {}\n\t\t\tfor (const field of this.fields) {\n\t\t\t\tbaseline[field.key] = this.stored(field)\n\t\t\t\tconst untouched = first || JSON.stringify(this.form[field.key]) === JSON.stringify(this.baseline[field.key])\n\t\t\t\tform[field.key] = untouched ? baseline[field.key] : this.form[field.key]\n\t\t\t}\n\t\t\tthis.baseline = baseline\n\t\t\tthis.form = form\n\t\t},\n\n\t\t/**\n\t\t * The value to send for a field: numbers as numbers, an empty value as null.\n\t\t *\n\t\t * @param {object} field The field.\n\t\t * @return {unknown}\n\t\t */\n\t\toutgoing(field) {\n\t\t\tconst value = this.form[field.key]\n\t\t\tif (field.type === 'number') {\n\t\t\t\treturn value === '' || value === null || value === undefined ? null : Number(value)\n\t\t\t}\n\t\t\tif (field.type === 'switch') {\n\t\t\t\treturn value === true\n\t\t\t}\n\t\t\tif (typeof value === 'string') {\n\t\t\t\treturn value.trim() === '' ? null : value.trim()\n\t\t\t}\n\t\t\treturn value\n\t\t},\n\n\t\t/**\n\t\t * Check required, number range and pattern before any write.\n\t\t *\n\t\t * @return {object} Error text by field key.\n\t\t */\n\t\tvalidate() {\n\t\t\tconst errors = {}\n\t\t\tfor (const field of this.fields) {\n\t\t\t\tif (field.type === 'switch' || field.readOnly) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst value = this.outgoing(field)\n\t\t\t\tconst empty = value === null || value === undefined || value === ''\n\t\t\t\tif (empty) {\n\t\t\t\t\tif (field.required) {\n\t\t\t\t\t\terrors[field.key] = this.tr('Fill this in.')\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (field.type === 'number') {\n\t\t\t\t\tif (Number.isNaN(value) || (field.min !== undefined && value < field.min) || (field.max !== undefined && value > field.max)) {\n\t\t\t\t\t\terrors[field.key] = this.tr('This number is not valid.')\n\t\t\t\t\t}\n\t\t\t\t} else if (field.pattern && !new RegExp(field.pattern).test(String(value))) {\n\t\t\t\t\terrors[field.key] = this.tr(field.patternMessage || 'This value is not valid.')\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn errors\n\t\t},\n\n\t\t/**\n\t\t * Save: PATCH the fields that differ from the last saved values.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/abstract-settings-form/specs/settings-form/spec.md#requirement-a-settings-form-saves-only-what-changed\n\t\t */\n\t\tasync save() {\n\t\t\tthis.errors = this.validate()\n\t\t\tthis.notice = ''\n\t\t\tif (Object.keys(this.errors).length > 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!this.objectUrl) {\n\t\t\t\tthis.noticeType = 'error'\n\t\t\t\tthis.notice = this.tr('This form cannot be saved here.')\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst next = {}\n\t\t\tconst compare = {}\n\t\t\tfor (const field of this.fields) {\n\t\t\t\tif (!field.readOnly) {\n\t\t\t\t\tnext[field.key] = this.outgoing(field)\n\t\t\t\t\tcompare[field.key] = this.baseline[field.key] === '' ? null : this.baseline[field.key]\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst patch = changedFields(compare, next)\n\t\t\tthis.saving = true\n\t\t\ttry {\n\t\t\t\tif (Object.keys(patch).length > 0) {\n\t\t\t\t\tconst data = await patchObject(this.objectUrl, patch)\n\t\t\t\t\tconst baseline = { ...this.baseline }\n\t\t\t\t\tconst form = { ...this.form }\n\t\t\t\t\tfor (const key of Object.keys(patch)) {\n\t\t\t\t\t\tconst field = this.fields.find((candidate) => candidate.key === key)\n\t\t\t\t\t\tconst written = data[key] !== undefined ? data[key] : patch[key]\n\t\t\t\t\t\tbaseline[key] = field.type === 'switch' ? written === true : (written === null ? '' : written)\n\t\t\t\t\t\tform[key] = baseline[key]\n\t\t\t\t\t}\n\t\t\t\t\tthis.baseline = baseline\n\t\t\t\t\tthis.form = form\n\t\t\t\t}\n\t\t\t\tthis.noticeType = 'success'\n\t\t\t\tthis.notice = this.tr(this.content.savedText || 'Saved.')\n\t\t\t\t/**\n\t\t\t\t * @event saved The changed fields were written.\n\t\t\t\t * @type {{patch: object}}\n\t\t\t\t */\n\t\t\t\tthis.$emit('saved', { patch })\n\t\t\t} catch (e) {\n\t\t\t\tthis.noticeType = 'error'\n\t\t\t\tthis.notice = serverErrorMessage(e)\n\t\t\t} finally {\n\t\t\t\tthis.saving = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-settings-form {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 14px;\n\tmin-width: 0;\n}\n\n.cn-settings-form__grid {\n\tdisplay: grid;\n\tgap: 14px 16px;\n\tgrid-template-columns: minmax(0, 1fr);\n}\n\n.cn-settings-form__grid--2 {\n\tgrid-template-columns: repeat(2, minmax(0, 1fr));\n}\n\n.cn-settings-form__field {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\tmin-width: 0;\n}\n\n.cn-settings-form__field--wide {\n\tgrid-column: 1 / -1;\n}\n\n.cn-settings-form__label {\n\tfont-size: 13px;\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-settings-form__control {\n\twidth: 100%;\n\tbox-sizing: border-box;\n\theight: var(--cn-board-control-height, 40px);\n\tpadding: 0 12px;\n\tborder: 1px solid var(--color-border-maxcontrast);\n\tborder-radius: var(--cn-board-control-radius, 8px);\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-main-text);\n\tfont: inherit;\n}\n\n.cn-settings-form__file {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-settings-form__control--file {\n\tdisplay: inline-flex;\n\talign-items: center;\n\twidth: auto;\n\tmin-width: 120px;\n\tflex: 0 1 auto;\n\toverflow-wrap: anywhere;\n}\n\n.cn-settings-form__control--placeholder {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-settings-form__control--area {\n\theight: auto;\n\tpadding: 10px 12px;\n\tresize: vertical;\n}\n\n.cn-settings-form__control:focus-visible {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: 1px;\n}\n\n.cn-settings-form__hint,\n.cn-settings-form__error {\n\tmargin: 0;\n\tfont-size: 12px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-settings-form__error {\n\tcolor: var(--color-error-text);\n}\n\n.cn-settings-form__actions {\n\tdisplay: flex;\n\tjustify-content: flex-start;\n}\n\n@media (max-width: 640px) {\n\t.cn-settings-form__grid--2 {\n\t\tgrid-template-columns: minmax(0, 1fr);\n\t}\n}\n</style>\n"],"names":["t"],"mappings":"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"}
|