@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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../../src/components/CnChoicePickerWidget/index.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * CnChoicePickerWidget — self-registers the `choice-picker` widget type (choice\n * cards with colour swatches and a state pill, and a confirm-with-warning save)\n * into the shared dashboardWidgetRegistry at module load.\n */\n\nimport CnChoicePickerWidget from './CnChoicePickerWidget.vue'\nimport { registerDashboardWidget } from '../CnWidgetGrid/dashboardWidgetRegistry.js'\n\nregisterDashboardWidget('choice-picker', {\n\trenderer: CnChoicePickerWidget,\n\tform: null,\n\tdefaultContent: {\n\t\tfield: '',\n\t\titems: [],\n\t},\n\tdisplayName: 'Choice picker',\n\ticon: 'Palette',\n\tsurfaces: ['detail-page'],\n})\n\nexport { CnChoicePickerWidget }\nexport default CnChoicePickerWidget\n"],"names":["CnChoicePickerWidget"],"mappings":";;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA,uBAAuB,CAAC,eAAe,EAAE;AACzC,CAAC,QAAQ,EAAEA,UAAoB;AAC/B,CAAC,IAAI,EAAE,IAAI;AACX,CAAC,cAAc,EAAE;AACjB,EAAE,KAAK,EAAE,EAAE;AACX,EAAE,KAAK,EAAE,EAAE;AACX,EAAE;AACF,CAAC,WAAW,EAAE,eAAe;AAC7B,CAAC,IAAI,EAAE,SAAS;AAChB,CAAC,QAAQ,EAAE,CAAC,aAAa,CAAC;AAC1B,CAAC;;;;"}
|
|
@@ -8,7 +8,7 @@ import { readPath } from '../../utils/readPath.js';
|
|
|
8
8
|
* `content: { field, endpoint?, authorField?, timeField?, textField?, sideField?, usValue? }`.
|
|
9
9
|
* `@objectId` in `endpoint` is replaced by the record's id. A reply the
|
|
10
10
|
* endpoint accepts is shown at once, until the record is read again.
|
|
11
|
-
* Resolved by its registry key,
|
|
11
|
+
* Resolved by its registry key, and exported for an app that renders it itself.
|
|
12
12
|
*/
|
|
13
13
|
var script = {
|
|
14
14
|
name: 'CnConversationWidget',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnConversationWidget.vue2.js","sources":["../../../../src/components/CnConversationThread/CnConversationWidget.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-case-widget-card\">\n\t\t<CnConversationThread\n\t\t\t:messages=\"messages\"\n\t\t\t:title=\"content.title || ''\"\n\t\t\t:allowReply=\"content.allowReply !== false\"\n\t\t\t:replyLabel=\"content.replyLabel || undefined\"\n\t\t\t:placeholder=\"content.placeholder || ''\"\n\t\t\t:sendLabel=\"content.sendLabel || undefined\"\n\t\t\t:endpoint=\"endpoint\"\n\t\t\t:emptyText=\"content.emptyText || undefined\"\n\t\t\t@sent=\"onSent\" />\n\t</div>\n</template>\n\n<script>\nimport CnConversationThread from './CnConversationThread.vue'\nimport { readPath } from '../../utils/readPath.js'\n\n/**\n * CnConversationWidget: the `conversation` detail widget type.\n *\n * Renders CnConversationThread from a list on the bound record:\n * `content: { field, endpoint?, authorField?, timeField?, textField?, sideField?, usValue? }`.\n * `@objectId` in `endpoint` is replaced by the record's id. A reply the\n * endpoint accepts is shown at once, until the record is read again.\n * Resolved by its registry key,
|
|
1
|
+
{"version":3,"file":"CnConversationWidget.vue2.js","sources":["../../../../src/components/CnConversationThread/CnConversationWidget.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-case-widget-card\">\n\t\t<CnConversationThread\n\t\t\t:messages=\"messages\"\n\t\t\t:title=\"content.title || ''\"\n\t\t\t:allowReply=\"content.allowReply !== false\"\n\t\t\t:replyLabel=\"content.replyLabel || undefined\"\n\t\t\t:placeholder=\"content.placeholder || ''\"\n\t\t\t:sendLabel=\"content.sendLabel || undefined\"\n\t\t\t:endpoint=\"endpoint\"\n\t\t\t:emptyText=\"content.emptyText || undefined\"\n\t\t\t@sent=\"onSent\" />\n\t</div>\n</template>\n\n<script>\nimport CnConversationThread from './CnConversationThread.vue'\nimport { readPath } from '../../utils/readPath.js'\n\n/**\n * CnConversationWidget: the `conversation` detail widget type.\n *\n * Renders CnConversationThread from a list on the bound record:\n * `content: { field, endpoint?, authorField?, timeField?, textField?, sideField?, usValue? }`.\n * `@objectId` in `endpoint` is replaced by the record's id. A reply the\n * endpoint accepts is shown at once, until the record is read again.\n * Resolved by its registry key, and exported for an app that renders it itself.\n */\nexport default {\n\tname: 'CnConversationWidget',\n\n\tcomponents: {\n\t\tCnConversationThread,\n\t},\n\n\tprops: {\n\t\t/** The widget's content. `field` is the dot-path to the messages on the record. */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** The bound record. */\n\t\tobjectData: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The bound record's id. */\n\t\tobjectId: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Replies sent from this widget that the record does not hold yet. */\n\t\t\tsentHere: [],\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The record's messages in the thread's shape, plus the replies sent here.\n\t\t *\n\t\t * @return {Array<object>} The messages.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread\n\t\t */\n\t\tmessages() {\n\t\t\tconst c = this.content\n\t\t\tconst value = readPath(this.objectData || {}, c.field || 'messages')\n\t\t\tconst usValue = String(c.usValue === undefined ? 'us' : c.usValue)\n\t\t\tconst stored = (Array.isArray(value) ? value : [])\n\t\t\t\t.filter((message) => message && typeof message === 'object')\n\t\t\t\t.map((message) => ({\n\t\t\t\t\tid: message.id,\n\t\t\t\t\tauthor: readPath(message, c.authorField || 'author'),\n\t\t\t\t\ttime: readPath(message, c.timeField || 'time'),\n\t\t\t\t\ttext: readPath(message, c.textField || 'text'),\n\t\t\t\t\tside: String(readPath(message, c.sideField || 'side')) === usValue ? 'us' : 'them',\n\t\t\t\t}))\n\t\t\treturn [...stored, ...this.sentHere]\n\t\t},\n\n\t\t/**\n\t\t * The endpoint with the record's id filled in.\n\t\t *\n\t\t * @return {string} The URL, or '' when the content names none.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread\n\t\t */\n\t\tendpoint() {\n\t\t\tconst url = this.content.endpoint\n\t\t\tif (typeof url !== 'string' || url === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn url.replace(/@objectId/g, encodeURIComponent(String(this.objectId ?? '')))\n\t\t},\n\t},\n\n\twatch: {\n\t\t// The record was read again, so it now holds what was sent from here.\n\t\tobjectData() {\n\t\t\tthis.sentHere = []\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Show an accepted reply at once, without waiting for the record.\n\t\t *\n\t\t * @param {object} data The endpoint's response.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread\n\t\t */\n\t\tonSent(data) {\n\t\t\tconst text = data?.message ?? data?.text\n\t\t\tif (typeof text !== 'string' || text === '') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.sentHere.push({\n\t\t\t\tid: `sent-${this.sentHere.length}`,\n\t\t\t\ttime: new Date().toISOString(),\n\t\t\t\ttext,\n\t\t\t\tside: 'us',\n\t\t\t})\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-case-widget-card {\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large);\n\tpadding: calc(5 * var(--default-grid-baseline));\n}\n</style>\n"],"names":["CnConversationThread"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnConversationWidget.vue3.js","sources":["../../../../src/components/CnConversationThread/CnConversationWidget.vue?vue&type=template&id=57081189&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-case-widget-card\">\n\t\t<CnConversationThread\n\t\t\t:messages=\"messages\"\n\t\t\t:title=\"content.title || ''\"\n\t\t\t:allowReply=\"content.allowReply !== false\"\n\t\t\t:replyLabel=\"content.replyLabel || undefined\"\n\t\t\t:placeholder=\"content.placeholder || ''\"\n\t\t\t:sendLabel=\"content.sendLabel || undefined\"\n\t\t\t:endpoint=\"endpoint\"\n\t\t\t:emptyText=\"content.emptyText || undefined\"\n\t\t\t@sent=\"onSent\" />\n\t</div>\n</template>\n\n<script>\nimport CnConversationThread from './CnConversationThread.vue'\nimport { readPath } from '../../utils/readPath.js'\n\n/**\n * CnConversationWidget: the `conversation` detail widget type.\n *\n * Renders CnConversationThread from a list on the bound record:\n * `content: { field, endpoint?, authorField?, timeField?, textField?, sideField?, usValue? }`.\n * `@objectId` in `endpoint` is replaced by the record's id. A reply the\n * endpoint accepts is shown at once, until the record is read again.\n * Resolved by its registry key,
|
|
1
|
+
{"version":3,"file":"CnConversationWidget.vue3.js","sources":["../../../../src/components/CnConversationThread/CnConversationWidget.vue?vue&type=template&id=57081189&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-case-widget-card\">\n\t\t<CnConversationThread\n\t\t\t:messages=\"messages\"\n\t\t\t:title=\"content.title || ''\"\n\t\t\t:allowReply=\"content.allowReply !== false\"\n\t\t\t:replyLabel=\"content.replyLabel || undefined\"\n\t\t\t:placeholder=\"content.placeholder || ''\"\n\t\t\t:sendLabel=\"content.sendLabel || undefined\"\n\t\t\t:endpoint=\"endpoint\"\n\t\t\t:emptyText=\"content.emptyText || undefined\"\n\t\t\t@sent=\"onSent\" />\n\t</div>\n</template>\n\n<script>\nimport CnConversationThread from './CnConversationThread.vue'\nimport { readPath } from '../../utils/readPath.js'\n\n/**\n * CnConversationWidget: the `conversation` detail widget type.\n *\n * Renders CnConversationThread from a list on the bound record:\n * `content: { field, endpoint?, authorField?, timeField?, textField?, sideField?, usValue? }`.\n * `@objectId` in `endpoint` is replaced by the record's id. A reply the\n * endpoint accepts is shown at once, until the record is read again.\n * Resolved by its registry key, and exported for an app that renders it itself.\n */\nexport default {\n\tname: 'CnConversationWidget',\n\n\tcomponents: {\n\t\tCnConversationThread,\n\t},\n\n\tprops: {\n\t\t/** The widget's content. `field` is the dot-path to the messages on the record. */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** The bound record. */\n\t\tobjectData: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The bound record's id. */\n\t\tobjectId: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Replies sent from this widget that the record does not hold yet. */\n\t\t\tsentHere: [],\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The record's messages in the thread's shape, plus the replies sent here.\n\t\t *\n\t\t * @return {Array<object>} The messages.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread\n\t\t */\n\t\tmessages() {\n\t\t\tconst c = this.content\n\t\t\tconst value = readPath(this.objectData || {}, c.field || 'messages')\n\t\t\tconst usValue = String(c.usValue === undefined ? 'us' : c.usValue)\n\t\t\tconst stored = (Array.isArray(value) ? value : [])\n\t\t\t\t.filter((message) => message && typeof message === 'object')\n\t\t\t\t.map((message) => ({\n\t\t\t\t\tid: message.id,\n\t\t\t\t\tauthor: readPath(message, c.authorField || 'author'),\n\t\t\t\t\ttime: readPath(message, c.timeField || 'time'),\n\t\t\t\t\ttext: readPath(message, c.textField || 'text'),\n\t\t\t\t\tside: String(readPath(message, c.sideField || 'side')) === usValue ? 'us' : 'them',\n\t\t\t\t}))\n\t\t\treturn [...stored, ...this.sentHere]\n\t\t},\n\n\t\t/**\n\t\t * The endpoint with the record's id filled in.\n\t\t *\n\t\t * @return {string} The URL, or '' when the content names none.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread\n\t\t */\n\t\tendpoint() {\n\t\t\tconst url = this.content.endpoint\n\t\t\tif (typeof url !== 'string' || url === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn url.replace(/@objectId/g, encodeURIComponent(String(this.objectId ?? '')))\n\t\t},\n\t},\n\n\twatch: {\n\t\t// The record was read again, so it now holds what was sent from here.\n\t\tobjectData() {\n\t\t\tthis.sentHere = []\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Show an accepted reply at once, without waiting for the record.\n\t\t *\n\t\t * @param {object} data The endpoint's response.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread\n\t\t */\n\t\tonSent(data) {\n\t\t\tconst text = data?.message ?? data?.text\n\t\t\tif (typeof text !== 'string' || text === '') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.sentHere.push({\n\t\t\t\tid: `sent-${this.sentHere.length}`,\n\t\t\t\ttime: new Date().toISOString(),\n\t\t\t\ttext,\n\t\t\t\tside: 'us',\n\t\t\t})\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-case-widget-card {\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large);\n\tpadding: calc(5 * var(--default-grid-baseline));\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createVNode"],"mappings":";;AAWM,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,qBAAqB,GAAA;;;;;AAA9B,EAAA,QAAAA,SAAA,EAAA,EAAAC,kBAAA,QAAA;IACAC;AACC,MAAA,QAAQ,EAAE,QAAQ,CAAA,QAAA;MAClB,KAAK,EAAE,MAAO,CAAA,OAAA,CAAC,KAAG,IAAA,EAAA;MAClB,UAAU,EAAE,MAAO,CAAA,OAAA,CAAC,UAAU,KAAA,KAAA;AAC9B,MAAA,UAAU,EAAE,MAAA,CAAA,OAAG,CAAA,UAAA,IAAA,SAAA;MACf,WAAE,EAAA,MAAA,CAAA,OAAA,CAAA,WAAA,IAAA,EAAA;;;;;;;;;;;"}
|
|
@@ -354,14 +354,14 @@ var script = {
|
|
|
354
354
|
* anything whose height is whatever it turned out to have.
|
|
355
355
|
*
|
|
356
356
|
* @param {object} item the layout item.
|
|
357
|
-
* @return {string|undefined} `'true'`, a row
|
|
357
|
+
* @return {string|undefined} `'true'`, a row maximum as a string, or undefined.
|
|
358
358
|
*/
|
|
359
359
|
sizeToContentAttr(item) {
|
|
360
360
|
const v = item && item.sizeToContent;
|
|
361
361
|
if (v === true) {
|
|
362
362
|
return 'true'
|
|
363
363
|
}
|
|
364
|
-
// A number is GridStack's "size to content, but never
|
|
364
|
+
// A number is GridStack's "size to content, but never taller than N rows" (a maximum).
|
|
365
365
|
if (Number.isFinite(v) && v > 0) {
|
|
366
366
|
return String(v)
|
|
367
367
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnDashboardGrid.vue2.js","sources":["../../../../src/components/CnDashboardGrid/CnDashboardGrid.vue"],"sourcesContent":["<!--\n CnDashboardGrid — GridStack-powered drag-and-drop dashboard grid.\n\n Renders widgets in a configurable grid layout. Supports drag, resize,\n and dynamic item addition/removal. Emits layout changes for persistence.\n\n Accessibility: every grid item is an ARIA `group` with an accessible name\n and — in edit mode — a tab stop, so the pointer-only GridStack drag/resize\n gestures have a full keyboard equivalent (WCAG 2.1 SC 2.1.1). Keyboard\n moves are routed through `GridStack.update()`, i.e. the exact same engine\n call drag/resize ends in, so collision handling, persistence and the\n `layout-change` emission are byte-identical between the two input modes.\n-->\n<template>\n\t<div ref=\"gridContainer\" class=\"cn-dashboard-grid\">\n\t\t<!--\n\t\t\tKeyboard instructions, referenced by every grid item through\n\t\t\t`aria-describedby`. Rendered once (not per item) so a screen\n\t\t\treader user hears the key map on the first item they land on\n\t\t\twithout it being duplicated N times in the accessibility tree.\n\t\t-->\n\t\t<p\n\t\t\tv-if=\"keyboardActive\"\n\t\t\t:id=\"keyboardHelpDomId\"\n\t\t\tclass=\"cn-dashboard-grid__sr-only\">\n\t\t\t{{ keyboardHelpText }}\n\t\t</p>\n\n\t\t<div class=\"grid-stack\">\n\t\t\t<div\n\t\t\t\tv-for=\"(item, index) in layout\"\n\t\t\t\t:key=\"resolveItemKey(item)\"\n\t\t\t\tclass=\"grid-stack-item\"\n\t\t\t\t:class=\"{ 'cn-dashboard-grid__item--fit': sizeToContentAttr(item) !== undefined }\"\n\t\t\t\trole=\"group\"\n\t\t\t\t:aria-label=\"resolveItemLabel(item, index)\"\n\t\t\t\t:aria-describedby=\"itemDescribedBy\"\n\t\t\t\t:tabindex=\"itemTabindex\"\n\t\t\t\t:gs-id=\"item.id\"\n\t\t\t\t:gs-x=\"item.gridX\"\n\t\t\t\t:gs-y=\"item.gridY\"\n\t\t\t\t:gs-w=\"item.gridWidth\"\n\t\t\t\t:gs-h=\"item.gridHeight\"\n\t\t\t\t:gs-min-w=\"minWidth\"\n\t\t\t\t:gs-min-h=\"minHeight\"\n\t\t\t\t:gs-size-to-content=\"sizeToContentAttr(item)\"\n\t\t\t\t@keydown=\"onItemKeydown($event, item)\">\n\t\t\t\t<div class=\"grid-stack-item-content\">\n\t\t\t\t\t<slot name=\"widget\" :item=\"item\">\n\t\t\t\t\t\t<!-- Default: render nothing; CnDashboardPage provides content -->\n\t\t\t\t\t</slot>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!--\n\t\t\tPolite live region for keyboard move/resize feedback. A sighted\n\t\t\tuser sees the widget slide; a screen-reader user needs the new\n\t\t\tcoordinates spoken, otherwise the arrow keys are silent.\n\t\t-->\n\t\t<p\n\t\t\tclass=\"cn-dashboard-grid__sr-only\"\n\t\t\trole=\"status\"\n\t\t\taria-live=\"polite\"\n\t\t\taria-atomic=\"true\">\n\t\t\t{{ announcement }}\n\t\t</p>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { GridStack } from 'gridstack'\n// `gridstack` is a peerDependency (see package.json), not bundled — this\n// component's JS and the consumer's GridStack stylesheet MUST come from the\n// exact same installed copy, because GridStack's JS writes layout state\n// (e.g. the `--gs-column-width` CSS custom property) that only its OWN\n// matching CSS reads. Baking a copy of `gridstack/dist/gridstack.min.css`\n// into this library's build (as a previous version did) freezes it at\n// nc-vue's build-time version, which can silently drift from whatever the\n// consumer's peer-installed copy actually is — the exact mismatch that once\n// made every grid item render at 0px width. So nc-vue ships NO GridStack\n// CSS of its own: the consumer MUST install `gridstack` themselves and\n// import `gridstack/dist/gridstack.min.css` (or their preferred GridStack\n// stylesheet) from that same copy.\n\n/**\n * Monotonic counter backing the per-instance DOM ids (the shared\n * `aria-describedby` help text). Module-level so two grids on one page\n * never collide — the same pattern CnCommandPalette uses.\n */\nlet domIdCounter = 0\n\n/**\n * Pixel offset applied to the grid item's top-left corner when synthesising\n * the `contextmenu` coordinates for a keyboard activation. Nudging the anchor\n * inside the item keeps consumer menus (which position at `clientX/clientY`)\n * visually attached to the widget instead of straddling its border.\n */\nconst MENU_ANCHOR_OFFSET = 8\n\n/**\n * CnDashboardGrid — Low-level grid layout engine powered by GridStack.\n *\n * Manages the drag-and-drop grid, syncs positions, and emits layout\n * changes. Does NOT handle widget rendering — that's done by the parent\n * via the `#widget` scoped slot.\n *\n * ```vue\n * <CnDashboardGrid\n * :layout=\"placements\"\n * :editable=\"isEditing\"\n * @layout-change=\"onLayoutChange\">\n * <template #widget=\"{ item }\">\n * <MyWidget :config=\"item\" />\n * </template>\n * </CnDashboardGrid>\n * ```\n *\n * ### Keyboard operation\n *\n * With `keyboardRepositioning` on (the default) and `editable` true, each\n * grid item is a tab stop. With an item focused:\n *\n * | Key | Action |\n * | --- | ------ |\n * | `ArrowLeft` / `ArrowRight` | move one column left / right |\n * | `ArrowUp` / `ArrowDown` | move one row up / down |\n * | `Shift` + `ArrowLeft` / `ArrowRight` | shrink / grow width by one column |\n * | `Shift` + `ArrowUp` / `ArrowDown` | shrink / grow height by one row |\n * | `Home` / `End` | jump to the first / last column of the current row |\n * | `Enter` / `Space` | activate the item — see `item-activate` |\n *\n * Keys are only honoured when the grid item itself holds focus, so\n * interactive content rendered inside the `#widget` slot keeps its own\n * key handling untouched.\n */\nexport default {\n\tname: 'CnDashboardGrid',\n\n\tprops: {\n\t\t/** Array of layout items: { id, gridX, gridY, gridWidth, gridHeight, ...extra } */\n\t\tlayout: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Whether drag and resize are enabled */\n\t\teditable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Number of grid columns */\n\t\tcolumns: {\n\t\t\ttype: Number,\n\t\t\tdefault: 12,\n\t\t},\n\n\t\t/** Cell height in pixels */\n\t\tcellHeight: {\n\t\t\ttype: Number,\n\t\t\tdefault: 80,\n\t\t},\n\n\t\t/** Grid margin in pixels */\n\t\tmargin: {\n\t\t\ttype: Number,\n\t\t\tdefault: 12,\n\t\t},\n\n\t\t/** Minimum widget width in grid units */\n\t\tminWidth: {\n\t\t\ttype: Number,\n\t\t\tdefault: 2,\n\t\t},\n\n\t\t/** Minimum widget height in grid units */\n\t\tminHeight: {\n\t\t\ttype: Number,\n\t\t\tdefault: 2,\n\t\t},\n\n\t\t/**\n\t\t * GridStack v12 responsive `columnOpts` bag (breakpoints + reflow\n\t\t * layout). When set, the grid reflows column count across screen sizes.\n\t\t * Build it with `getDashboardColumnOpts()`. Default `null` = fixed\n\t\t * `columns`, no responsive reflow (backwards-compatible).\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tcolumnOpts: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * When set, `cellHeight` is mirrored into this CSS custom property on\n\t\t * the document root at init (e.g. `'--app-cell-height'`), so app CSS can\n\t\t * align to the grid geometry. Default `null` = no CSS var written.\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\tcellHeightCssVar: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Optional `(item) => string|number` to derive each item's render key.\n\t\t * Use it to force a re-render when an item changes in a way its `id`\n\t\t * doesn't capture (e.g. style edits — return `${item.id}:${item.updatedAt}`).\n\t\t * Default `null` = key on `item.id`.\n\t\t *\n\t\t * @type {((item: object) => (string|number))|null}\n\t\t */\n\t\titemKey: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Whether grid items are keyboard-operable: focusable in edit mode and\n\t\t * repositionable/resizable with the arrow keys (WCAG 2.1 SC 2.1.1 —\n\t\t * the keyboard equivalent of the pointer-only GridStack drag). Turning\n\t\t * it off leaves the items non-focusable and drag-only. Default `true`.\n\t\t */\n\t\tkeyboardRepositioning: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Optional `(item, index) => string` returning the accessible name for\n\t\t * a grid item. Use it when the layout items don't carry a human-readable\n\t\t * field (the built-in fallback walks `title` → `name` → `label` →\n\t\t * `widgetTitle` → `widgetId`, then a positional \"Widget N\"). The returned\n\t\t * string is used verbatim — no coordinates are appended.\n\t\t *\n\t\t * @type {((item: object, index: number) => string)|null}\n\t\t */\n\t\titemLabel: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Whether a keyboard activation (`Enter` / `Space` on a focused grid\n\t\t * item) also dispatches a bubbling `contextmenu` event from inside the\n\t\t * item, so consumers that open a per-widget menu on right-click get the\n\t\t * keyboard path for free — mirroring what browsers do for the `Menu`\n\t\t * key. Set `false` to rely purely on the `item-activate` event.\n\t\t * Default `true`.\n\t\t */\n\t\tactivateOpensContextMenu: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * GridStack's `float`. True (the default) keeps every item on the row\n\t\t * it was placed on, with empty rows above it if need be. False packs\n\t\t * items upward, so a size-to-content cell that shrinks, or a widget\n\t\t * that is not shown, closes its gap instead of leaving a band.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-dashboard-closes-the-gap-a-shrinking-or-hidden-widget-leaves\n\t\t */\n\t\tfloat: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * Fired whenever the grid geometry changed — by drag, by resize, or by\n\t\t * a keyboard nudge. Payload is the full `layout` array with the moved\n\t\t * items' `gridX`/`gridY`/`gridWidth`/`gridHeight` updated.\n\t\t */\n\t\t'layout-change',\n\t\t/**\n\t\t * Fired when the user activates a focused grid item with `Enter` or\n\t\t * `Space`. Payload `{ item, element, clientX, clientY }` — the layout\n\t\t * item, its DOM element, and viewport coordinates anchored to the\n\t\t * item's top-left corner so a consumer menu can position itself the\n\t\t * same way it does for a right-click.\n\t\t */\n\t\t'item-activate',\n\t],\n\n\tdata() {\n\t\treturn {\n\t\t\tgrid: null,\n\t\t\tcontentObserver: null,\n\t\t\t/** True while the grid moves an item to where the layout put it. */\n\t\t\tapplyingLayout: false,\n\t\t\tdomId: `cn-dashboard-grid-${++domIdCounter}`,\n\t\t\t/** Text currently held in the polite live region. */\n\t\t\tannouncement: '',\n\t\t\t/**\n\t\t\t * Whether a `columnOpts` breakpoint is currently in force. Mirrored\n\t\t\t * from the engine by `syncInteractivity` because `isReflowed()` reads\n\t\t\t * `grid.getColumn()` imperatively, and a computed cannot re-evaluate\n\t\t\t * on that.\n\t\t\t */\n\t\t\treflowed: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * @return {boolean} Whether the keyboard lane is live — items are\n\t\t * focusable and the arrow keys reposition. Requires the opt-in prop,\n\t\t * edit mode (a read-only dashboard has nothing to move, so adding N tab\n\t\t * stops there would be pure noise), and a grid that is not reflowed,\n\t\t * which is where a move cannot be kept and so must not be offered.\n\t\t */\n\t\tkeyboardActive() {\n\t\t\treturn this.keyboardRepositioning && this.editable && !this.reflowed\n\t\t},\n\n\t\t/**\n\t\t * @return {number|null} `0` when grid items should be tab stops,\n\t\t * otherwise `null` so the attribute is omitted entirely.\n\t\t */\n\t\titemTabindex() {\n\t\t\treturn this.keyboardActive ? 0 : null\n\t\t},\n\n\t\t/**\n\t\t * @return {string} DOM id of the shared keyboard-instructions element.\n\t\t */\n\t\tkeyboardHelpDomId() {\n\t\t\treturn `${this.domId}-keyboard-help`\n\t\t},\n\n\t\t/**\n\t\t * @return {string|null} `aria-describedby` target for each grid item —\n\t\t * the instructions element while the keyboard lane is live, `null`\n\t\t * otherwise (a dangling id reference is an axe violation).\n\t\t */\n\t\titemDescribedBy() {\n\t\t\treturn this.keyboardActive ? this.keyboardHelpDomId : null\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The spoken key map for the keyboard lane.\n\t\t */\n\t\tkeyboardHelpText() {\n\t\t\treturn t(\n\t\t\t\t'nextcloud-vue',\n\t\t\t\t'Use the arrow keys to move this widget one cell. Hold Shift and use the arrow keys to resize it. Press Home or End to jump to the first or last column. Press Enter to open the widget menu.',\n\t\t\t)\n\t\t},\n\n\t\t/**\n\t\t * @return {number} The column count currently in force. With responsive\n\t\t * `columnOpts` GridStack reflows to fewer columns on small screens, so\n\t\t * clamping must read the live value rather than the `columns` prop.\n\t\t */\n\t\teffectiveColumns() {\n\t\t\tif (this.grid && typeof this.grid.getColumn === 'function') {\n\t\t\t\tconst live = this.grid.getColumn()\n\t\t\t\tif (Number.isFinite(live) && live > 0) {\n\t\t\t\t\treturn live\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn this.columns\n\t\t},\n\t},\n\n\twatch: {\n\t\teditable() {\n\t\t\tthis.syncInteractivity()\n\t\t},\n\n\t\tlayout: {\n\t\t\tdeep: true,\n\t\t\thandler(newLayout) {\n\t\t\t\tif (this.grid) {\n\t\t\t\t\tthis.syncGridItems(newLayout)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.initGrid()\n\t\tthis.$nextTick(() => this.observeContentSizes())\n\t},\n\n\tbeforeUnmount() {\n\t\tthis.disconnectContentObserver()\n\t\tif (this.grid) {\n\t\t\tthis.grid.destroy(false)\n\t\t}\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Derive the v-for render key for a layout item, honouring the optional\n\t\t * `itemKey` prop and falling back to `item.id`.\n\t\t *\n\t\t * @param {object} item the layout item.\n\t\t * @return {string|number} the render key.\n\t\t */\n\t\tresolveItemKey(item) {\n\t\t\treturn this.itemKey ? this.itemKey(item) : item.id\n\t\t},\n\n\t\t/**\n\t\t * The `gs-size-to-content` attribute for a layout item, or `undefined` to\n\t\t * leave the item on its authored `gridHeight`.\n\t\t *\n\t\t * A widget that decides AT RUNTIME it has nothing to show — a banner whose\n\t\t * condition is false, a list that came back empty — still costs its whole\n\t\t * authored row, because the row is reserved from the layout before the\n\t\t * component renders and the engine positions items absolutely, so no CSS\n\t\t * can reclaim it. `sizeToContent` hands the row's height to the content, so\n\t\t * an empty widget collapses and the grid closes up behind it.\n\t\t *\n\t\t * Opt-in per item: it makes `gridHeight` advisory for that widget, which is\n\t\t * wrong for anything with a deliberate size (a map, a chart) and right for\n\t\t * anything whose height is whatever it turned out to have.\n\t\t *\n\t\t * @param {object} item the layout item.\n\t\t * @return {string|undefined} `'true'`, a row minimum as a string, or undefined.\n\t\t */\n\t\tsizeToContentAttr(item) {\n\t\t\tconst v = item && item.sizeToContent\n\t\t\tif (v === true) {\n\t\t\t\treturn 'true'\n\t\t\t}\n\t\t\t// A number is GridStack's \"size to content, but never below N rows\".\n\t\t\tif (Number.isFinite(v) && v > 0) {\n\t\t\t\treturn String(v)\n\t\t\t}\n\t\t\treturn undefined\n\t\t},\n\n\t\t/**\n\t\t * Build the accessible name for a grid item. A custom `itemLabel` wins\n\t\t * outright; otherwise the first human-readable field on the item is used\n\t\t * and, while the keyboard lane is live, the current grid coordinates are\n\t\t * appended — without them a screen-reader user tabbing through an edit-mode\n\t\t * dashboard has no way to tell where anything sits.\n\t\t *\n\t\t * @param {object} item the layout item.\n\t\t * @param {number} index the item's index in `layout`, used for the\n\t\t * last-resort positional name.\n\t\t * @return {string} the accessible name.\n\t\t */\n\t\tresolveItemLabel(item, index) {\n\t\t\tif (this.itemLabel) {\n\t\t\t\treturn String(this.itemLabel(item, index) ?? '')\n\t\t\t}\n\n\t\t\tconst base = item.title\n\t\t\t\t|| item.name\n\t\t\t\t|| item.label\n\t\t\t\t|| item.widgetTitle\n\t\t\t\t|| item.widgetId\n\t\t\t\t|| t('nextcloud-vue', 'Widget {number}', { number: index + 1 })\n\n\t\t\tif (!this.keyboardActive) {\n\t\t\t\treturn String(base)\n\t\t\t}\n\n\t\t\t// Report the rectangle exactly as the reactive `layout` prop states\n\t\t\t// it — unclamped, so the spoken coordinates always match the\n\t\t\t// rendered `gs-*` attributes — and read from the prop (not the\n\t\t\t// GridStack node) so the name re-renders when a move round-trips\n\t\t\t// back through `layout-change`; engine-node mutations are invisible\n\t\t\t// to Vue's reactivity.\n\t\t\tconst rect = {\n\t\t\t\tx: Number.isFinite(item.gridX) ? item.gridX : 0,\n\t\t\t\ty: Number.isFinite(item.gridY) ? item.gridY : 0,\n\t\t\t\tw: Number.isFinite(item.gridWidth) ? item.gridWidth : this.minWidth,\n\t\t\t\th: Number.isFinite(item.gridHeight) ? item.gridHeight : this.minHeight,\n\t\t\t}\n\t\t\treturn t(\n\t\t\t\t'nextcloud-vue',\n\t\t\t\t'{label}, column {column} of {columns}, row {row}, {width} columns wide, {height} rows tall',\n\t\t\t\t{\n\t\t\t\t\tlabel: String(base),\n\t\t\t\t\tcolumn: rect.x + 1,\n\t\t\t\t\tcolumns: this.effectiveColumns,\n\t\t\t\t\trow: rect.y + 1,\n\t\t\t\t\twidth: rect.w,\n\t\t\t\t\theight: rect.h,\n\t\t\t\t},\n\t\t\t)\n\t\t},\n\n\t\tinitGrid() {\n\t\t\tif (this.cellHeightCssVar && typeof document !== 'undefined' && document.documentElement) {\n\t\t\t\tdocument.documentElement.style.setProperty(this.cellHeightCssVar, `${this.cellHeight}px`)\n\t\t\t}\n\t\t\tconst el = this.$refs.gridContainer.querySelector('.grid-stack')\n\t\t\tthis.grid = GridStack.init({\n\t\t\t\tcolumn: this.columns,\n\t\t\t\tcellHeight: this.cellHeight,\n\t\t\t\tmargin: this.margin,\n\t\t\t\tfloat: this.float,\n\t\t\t\tanimate: true,\n\t\t\t\tdisableDrag: !this.editable,\n\t\t\t\tdisableResize: !this.editable,\n\t\t\t\tremovable: false,\n\t\t\t\t...(this.columnOpts ? { columnOpts: this.columnOpts } : {}),\n\t\t\t}, el)\n\n\t\t\tthis.grid.on('change', (_event, items) => {\n\t\t\t\tthis.handleGridChange(items)\n\t\t\t})\n\n\t\t\t// A grid that mounts already narrow is reflowed before anyone touches\n\t\t\t// it, so the handles have to be judged here and not only on a later\n\t\t\t// change.\n\t\t\tthis.syncInteractivity()\n\t\t},\n\n\t\t/**\n\t\t * Keep both editing lanes in step with whether an edit could actually be\n\t\t * kept: the pointer's handles, and — through `reflowed` feeding\n\t\t * `keyboardActive` — the tab stops, the help text and the arrow keys.\n\t\t *\n\t\t * A reflowed grid drops every `change` it receives, for the reason\n\t\t * `handleGridChange` gives. Leaving either lane live there offers an edit\n\t\t * that silently snaps back, which is the same class of surprise as\n\t\t * persisting the reflow — pointed the other way, and harder to explain\n\t\t * because the author did everything right. It is worse on the keyboard,\n\t\t * where the move is also announced as done. The affordance has to say what\n\t\t * the grid will do at this width, not what edit mode does at the authored\n\t\t * one.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tsyncInteractivity() {\n\t\t\t// Ahead of the engine guard: the keyboard lane reads this whether or\n\t\t\t// not the engine can be enabled.\n\t\t\tthis.reflowed = this.isReflowed()\n\t\t\tif (!this.grid || typeof this.grid.enable !== 'function') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.editable && !this.reflowed) {\n\t\t\t\tthis.grid.enable()\n\t\t\t} else {\n\t\t\t\tthis.grid.disable()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Whether the grid is currently rescaled to a column count other than the\n\t\t * authored one, i.e. a `columnOpts` breakpoint is in force.\n\t\t *\n\t\t * The authored count is `columnOpts.columnMax` when there is one, not the\n\t\t * `columns` prop: GridStack widens to `columnMax` above the top breakpoint,\n\t\t * so a consumer whose two disagree is at its widest, not reflowed.\n\t\t *\n\t\t * @return {boolean} True while a responsive reflow is active.\n\t\t */\n\t\tisReflowed() {\n\t\t\tif (!this.grid || typeof this.grid.getColumn !== 'function') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst authored = Number.isFinite(this.columnOpts?.columnMax) ? this.columnOpts.columnMax : this.columns\n\t\t\tconst live = this.grid.getColumn()\n\t\t\treturn Number.isFinite(live) && live > 0 && live !== authored\n\t\t},\n\n\t\thandleGridChange(items) {\n\t\t\t// The rescale that crossed a breakpoint arrives here as a `change`, so\n\t\t\t// this is also where the handles are re-judged in both directions:\n\t\t\t// switched off as the grid narrows, and back on as it widens.\n\t\t\tthis.syncInteractivity()\n\n\t\t\tif (this.applyingLayout) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// GridStack fires `change` for its own responsive rescale too, and at a\n\t\t\t// 12 → 1 breakpoint every item reads `gridX: 0, gridWidth: 1`. Emitting\n\t\t\t// that has the host persist the reflow AS the authored layout, which\n\t\t\t// widening back does not undo. A move made while reflowed is the same\n\t\t\t// rescaled geometry, so it is dropped for the same reason.\n\t\t\tif (this.isReflowed()) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!items || items.length === 0) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst updated = this.layout.map((item) => {\n\t\t\t\tconst gridItem = items.find((gi) => String(gi.id) === String(item.id))\n\t\t\t\tif (gridItem) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\t...item,\n\t\t\t\t\t\tgridX: gridItem.x,\n\t\t\t\t\t\tgridY: gridItem.y,\n\t\t\t\t\t\tgridWidth: gridItem.w,\n\t\t\t\t\t\tgridHeight: gridItem.h,\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn item\n\t\t\t})\n\n\t\t\tthis.$emit('layout-change', updated)\n\t\t},\n\n\t\t/**\n\t\t * Keydown handler bound to every grid item.\n\t\t *\n\t\t * Only reacts when the grid item element itself is the event target:\n\t\t * a button, input or menu rendered inside the `#widget` slot must keep\n\t\t * its own arrow/Enter semantics, and a grid that swallowed them would\n\t\t * be a worse a11y regression than the one this handler fixes.\n\t\t *\n\t\t * @param {KeyboardEvent} event the keydown event.\n\t\t * @param {object} item the layout item the key was pressed on.\n\t\t * @return {void}\n\t\t */\n\t\tonItemKeydown(event, item) {\n\t\t\tif (!this.keyboardRepositioning) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (event.target !== event.currentTarget) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (event.altKey || event.ctrlKey || event.metaKey) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (event.key === 'Enter' || event.key === ' ' || event.key === 'Spacebar') {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tthis.activateItem(item, event.currentTarget)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Repositioning only, and only where it can be kept — an item that\n\t\t\t// still holds focus from before a reflow keeps its Enter, but its\n\t\t\t// arrow keys stop moving something that would snap back.\n\t\t\tif (!this.keyboardActive) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst handled = this.applyKey(event.key, event.shiftKey, item)\n\t\t\tif (handled) {\n\t\t\t\t// Arrows would otherwise scroll the dashboard out from under\n\t\t\t\t// the widget the user is moving.\n\t\t\t\tevent.preventDefault()\n\t\t\t\tevent.stopPropagation()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Translate one repositioning key into a geometry change.\n\t\t *\n\t\t * @param {string} key the `KeyboardEvent.key` value.\n\t\t * @param {boolean} shift whether Shift was held (resize instead of move).\n\t\t * @param {object} item the layout item being operated on.\n\t\t * @return {boolean} `true` when the key belongs to the grid and the\n\t\t * default action must be suppressed.\n\t\t */\n\t\tapplyKey(key, shift, item) {\n\t\t\tconst rect = this.currentRect(item)\n\n\t\t\tswitch (key) {\n\t\t\t\tcase 'ArrowLeft':\n\t\t\t\t\treturn shift\n\t\t\t\t\t\t? this.applyGeometry(item, { w: rect.w - 1 }, 'resize')\n\t\t\t\t\t\t: this.applyGeometry(item, { x: rect.x - 1 }, 'move')\n\t\t\t\tcase 'ArrowRight':\n\t\t\t\t\treturn shift\n\t\t\t\t\t\t? this.applyGeometry(item, { w: rect.w + 1 }, 'resize')\n\t\t\t\t\t\t: this.applyGeometry(item, { x: rect.x + 1 }, 'move')\n\t\t\t\tcase 'ArrowUp':\n\t\t\t\t\treturn shift\n\t\t\t\t\t\t? this.applyGeometry(item, { h: rect.h - 1 }, 'resize')\n\t\t\t\t\t\t: this.applyGeometry(item, { y: rect.y - 1 }, 'move')\n\t\t\t\tcase 'ArrowDown':\n\t\t\t\t\treturn shift\n\t\t\t\t\t\t? this.applyGeometry(item, { h: rect.h + 1 }, 'resize')\n\t\t\t\t\t\t: this.applyGeometry(item, { y: rect.y + 1 }, 'move')\n\t\t\t\tcase 'Home':\n\t\t\t\t\treturn this.applyGeometry(item, { x: 0 }, 'move')\n\t\t\t\tcase 'End':\n\t\t\t\t\treturn this.applyGeometry(item, { x: this.effectiveColumns - rect.w }, 'move')\n\t\t\t\tdefault:\n\t\t\t\t\treturn false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Read an item's live rectangle. The GridStack node is preferred over\n\t\t * the `layout` prop because a drag that has not yet round-tripped\n\t\t * through the consumer's persistence leaves the prop momentarily stale,\n\t\t * and a keyboard nudge must continue from where the widget actually is.\n\t\t *\n\t\t * @param {object} item the layout item.\n\t\t * @return {{x: number, y: number, w: number, h: number}} the rectangle\n\t\t * in grid units.\n\t\t */\n\t\tcurrentRect(item) {\n\t\t\tconst node = this.gridNode(item.id)\n\t\t\tif (node && Number.isFinite(node.x) && Number.isFinite(node.y)) {\n\t\t\t\treturn {\n\t\t\t\t\tx: node.x,\n\t\t\t\t\ty: node.y,\n\t\t\t\t\tw: Number.isFinite(node.w) ? node.w : this.minWidth,\n\t\t\t\t\th: Number.isFinite(node.h) ? node.h : this.minHeight,\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tx: Number.isFinite(item.gridX) ? item.gridX : 0,\n\t\t\t\ty: Number.isFinite(item.gridY) ? item.gridY : 0,\n\t\t\t\tw: Number.isFinite(item.gridWidth) ? item.gridWidth : this.minWidth,\n\t\t\t\th: Number.isFinite(item.gridHeight) ? item.gridHeight : this.minHeight,\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Clamp a candidate rectangle to the grid's bounds and the configured\n\t\t * minimum widget size, so a held-down arrow key parks the widget at the\n\t\t * edge instead of pushing it into negative coordinates.\n\t\t *\n\t\t * @param {{x: number, y: number, w: number, h: number}} rect the candidate.\n\t\t * @return {{x: number, y: number, w: number, h: number}} the clamped rectangle.\n\t\t */\n\t\tclampRect(rect) {\n\t\t\tconst cols = this.effectiveColumns\n\t\t\tconst w = Math.min(Math.max(rect.w, this.minWidth), cols)\n\t\t\tconst h = Math.max(rect.h, this.minHeight)\n\t\t\treturn {\n\t\t\t\tx: Math.min(Math.max(rect.x, 0), Math.max(cols - w, 0)),\n\t\t\t\ty: Math.max(rect.y, 0),\n\t\t\t\tw,\n\t\t\t\th,\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Apply a partial geometry change to an item.\n\t\t *\n\t\t * The change is pushed through `GridStack.update()` — the very call the\n\t\t * drag/resize handlers end in — so collision resolution, the `change`\n\t\t * event and therefore the `layout-change` emission and the consumer's\n\t\t * persistence are identical for keyboard and pointer. There is\n\t\t * deliberately no second, direct-to-`layout` update path.\n\t\t *\n\t\t * @param {object} item the layout item to move or resize.\n\t\t * @param {{x?: number, y?: number, w?: number, h?: number}} patch the\n\t\t * requested change, merged over the item's current rectangle.\n\t\t * @param {'move'|'resize'} kind which announcement to speak.\n\t\t * @return {boolean} `true` when the key was consumed — including when\n\t\t * the widget was already at the edge, because the arrow still belongs\n\t\t * to the grid and must not scroll the page.\n\t\t */\n\t\tapplyGeometry(item, patch, kind) {\n\t\t\tconst el = this.itemElement(item.id)\n\t\t\tif (!el || !this.grid || typeof this.grid.update !== 'function') {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tconst current = this.currentRect(item)\n\t\t\tconst next = this.clampRect({ ...current, ...patch })\n\n\t\t\tif (next.x === current.x && next.y === current.y\n\t\t\t\t&& next.w === current.w && next.h === current.h) {\n\t\t\t\tthis.announce(t('nextcloud-vue', '{label} cannot move further in that direction', {\n\t\t\t\t\tlabel: this.announcementLabel(item),\n\t\t\t\t}))\n\t\t\t\treturn true\n\t\t\t}\n\n\t\t\tthis.grid.update(el, next)\n\n\t\t\tthis.announce(kind === 'resize'\n\t\t\t\t? t('nextcloud-vue', '{label} resized to {width} columns by {height} rows', {\n\t\t\t\t\t\tlabel: this.announcementLabel(item),\n\t\t\t\t\t\twidth: next.w,\n\t\t\t\t\t\theight: next.h,\n\t\t\t\t\t})\n\t\t\t\t: t('nextcloud-vue', '{label} moved to column {column}, row {row}', {\n\t\t\t\t\t\tlabel: this.announcementLabel(item),\n\t\t\t\t\t\tcolumn: next.x + 1,\n\t\t\t\t\t\trow: next.y + 1,\n\t\t\t\t\t}))\n\n\t\t\treturn true\n\t\t},\n\n\t\t/**\n\t\t * The short, coordinate-free name used inside live-region sentences —\n\t\t * the full `aria-label` already carries the coordinates and repeating\n\t\t * them would make every announcement unlistenable.\n\t\t *\n\t\t * @param {object} item the layout item.\n\t\t * @return {string} the short name.\n\t\t */\n\t\tannouncementLabel(item) {\n\t\t\treturn String(item.title\n\t\t\t\t|| item.name\n\t\t\t\t|| item.label\n\t\t\t\t|| item.widgetTitle\n\t\t\t\t|| item.widgetId\n\t\t\t\t|| t('nextcloud-vue', 'Widget'))\n\t\t},\n\n\t\t/**\n\t\t * Push text into the polite live region. The region is cleared first so\n\t\t * two identical consecutive announcements (e.g. two failed nudges at the\n\t\t * grid edge) are both spoken — assistive tech ignores a re-render that\n\t\t * leaves the text unchanged.\n\t\t *\n\t\t * @param {string} text the message to announce.\n\t\t * @return {void}\n\t\t */\n\t\tannounce(text) {\n\t\t\tthis.announcement = ''\n\t\t\tthis.$nextTick(() => {\n\t\t\t\tthis.announcement = text\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Handle `Enter` / `Space` on a focused grid item: tell the consumer via\n\t\t * `item-activate`, and — unless opted out — synthesise the `contextmenu`\n\t\t * event a right-click would have produced, anchored to the item, so\n\t\t * pointer-only widget menus become keyboard-reachable without the\n\t\t * consumer wiring anything new. The synthetic event is dispatched from\n\t\t * inside the item's content so it bubbles through the slot's own root\n\t\t * element (where such handlers live) and on up.\n\t\t *\n\t\t * @param {object} item the layout item being activated.\n\t\t * @param {HTMLElement} el the grid item element.\n\t\t * @return {void}\n\t\t */\n\t\tactivateItem(item, el) {\n\t\t\tconst rect = typeof el.getBoundingClientRect === 'function'\n\t\t\t\t? el.getBoundingClientRect()\n\t\t\t\t: { left: 0, top: 0 }\n\t\t\tconst clientX = Math.round((rect.left || 0) + MENU_ANCHOR_OFFSET)\n\t\t\tconst clientY = Math.round((rect.top || 0) + MENU_ANCHOR_OFFSET)\n\n\t\t\tthis.$emit('item-activate', { item, element: el, clientX, clientY })\n\n\t\t\tif (this.activateOpensContextMenu) {\n\t\t\t\tthis.dispatchContextMenu(el, clientX, clientY)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Dispatch a bubbling, cancelable `contextmenu` MouseEvent from inside a\n\t\t * grid item.\n\t\t *\n\t\t * @param {HTMLElement} el the grid item element.\n\t\t * @param {number} clientX viewport x for the menu anchor.\n\t\t * @param {number} clientY viewport y for the menu anchor.\n\t\t * @return {void}\n\t\t */\n\t\tdispatchContextMenu(el, clientX, clientY) {\n\t\t\tif (typeof MouseEvent !== 'function') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst content = el.querySelector('.grid-stack-item-content')\n\t\t\tconst target = (content && content.firstElementChild) || content || el\n\t\t\ttarget.dispatchEvent(new MouseEvent('contextmenu', {\n\t\t\t\tbubbles: true,\n\t\t\t\tcancelable: true,\n\t\t\t\tclientX,\n\t\t\t\tclientY,\n\t\t\t\tbutton: 2,\n\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Look up a grid item's DOM element by its layout id.\n\t\t *\n\t\t * @param {string|number} id the layout item id.\n\t\t * @return {HTMLElement|null} the element, or `null` when not rendered.\n\t\t */\n\t\titemElement(id) {\n\t\t\tconst container = this.$refs.gridContainer\n\t\t\tif (!container) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn container.querySelector(`[gs-id=\"${id}\"]`)\n\t\t},\n\n\t\t/**\n\t\t * Look up the GridStack engine node backing a layout item.\n\t\t *\n\t\t * @param {string|number} id the layout item id.\n\t\t * @return {object|null} the node, or `null` when GridStack isn't tracking it.\n\t\t */\n\t\t/**\n\t\t * Re-measure a `sizeToContent` item when its content changes height.\n\t\t * GridStack watches only the grid element, so a widget that loaded its data\n\t\t * in `mounted()` keeps the height its empty state had.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tobserveContentSizes() {\n\t\t\tthis.disconnectContentObserver()\n\t\t\tif (typeof ResizeObserver === 'undefined' || !this.$refs.gridContainer) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.contentObserver = new ResizeObserver((entries) => {\n\t\t\t\tconst cells = new Set()\n\t\t\t\tfor (const entry of entries) {\n\t\t\t\t\tconst cell = entry.target.closest('.grid-stack-item')\n\t\t\t\t\tif (cell) {\n\t\t\t\t\t\tcells.add(cell)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (cells.size === 0) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// A frame later, not inline: `resizeToContent` resizes the cell the\n\t\t\t\t// observed child sits in, and a child that stretches to its parent\n\t\t\t\t// grows with it. Resizing from inside the callback makes that the\n\t\t\t\t// classic ResizeObserver loop (\"undelivered notifications\"); deferred,\n\t\t\t\t// it is an ordinary second pass that settles.\n\t\t\t\trequestAnimationFrame(() => {\n\t\t\t\t\tif (!this.contentObserver || !this.grid || typeof this.grid.resizeToContent !== 'function') {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tfor (const cell of cells) {\n\t\t\t\t\t\tthis.grid.resizeToContent(cell)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tconst cells = this.$refs.gridContainer.querySelectorAll('.grid-stack-item[gs-size-to-content]')\n\t\t\tfor (const cell of cells) {\n\t\t\t\tconst measured = cell.querySelector('.grid-stack-item-content')?.firstElementChild\n\t\t\t\tif (measured) {\n\t\t\t\t\tthis.contentObserver.observe(measured)\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Drop the content observer, on unmount or before rebuilding it.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tdisconnectContentObserver() {\n\t\t\tif (this.contentObserver) {\n\t\t\t\tthis.contentObserver.disconnect()\n\t\t\t\tthis.contentObserver = null\n\t\t\t}\n\t\t},\n\n\t\tgridNode(id) {\n\t\t\tconst nodes = this.grid && this.grid.engine && this.grid.engine.nodes\n\t\t\tif (!Array.isArray(nodes)) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn nodes.find((n) => String(n.id) === String(id)) || null\n\t\t},\n\n\t\tsyncGridItems(newLayout) {\n\t\t\t// Add new items, and re-adopt items whose backing DOM element was\n\t\t\t// replaced. When a consumer's `itemKey` changes for an unchanged\n\t\t\t// `id` (e.g. a style edit — the documented use of `itemKey`), Vue\n\t\t\t// swaps the element. GridStack keeps tracking the old, now-detached\n\t\t\t// element, leaving the new one unmanaged (no positioning/sizing) —\n\t\t\t// so re-register it against the new element.\n\t\t\tfor (const item of newLayout) {\n\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\tconst el = this.$refs.gridContainer.querySelector(`[gs-id=\"${item.id}\"]`)\n\t\t\t\t\tif (!el) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tconst node = this.grid.engine.nodes.find((n) => String(n.id) === String(item.id))\n\t\t\t\t\tif (!node) {\n\t\t\t\t\t\tthis.grid.makeWidget(el)\n\t\t\t\t\t} else if (node.el === el) {\n\t\t\t\t\t\t// The layout MOVED an item the grid already tracks: the\n\t\t\t\t\t\t// page re-compacts its display layout when a widget's\n\t\t\t\t\t\t// condition hides it, and the item below was given a new\n\t\t\t\t\t\t// gridY that never reached GridStack, so a band stayed\n\t\t\t\t\t\t// where the hidden widget had been. A drag reports the\n\t\t\t\t\t\t// grid's own position back, which matches and does\n\t\t\t\t\t\t// nothing here.\n\t\t\t\t\t\tconst x = item.gridX ?? node.x\n\t\t\t\t\t\tconst y = item.gridY ?? node.y\n\t\t\t\t\t\tif (x !== node.x || y !== node.y) {\n\t\t\t\t\t\t\t// The layout said where it goes; GridStack's `change`\n\t\t\t\t\t\t\t// for it is not a user's arrangement and is not\n\t\t\t\t\t\t\t// reported back (it would write the display-only\n\t\t\t\t\t\t\t// compaction into the authored layout).\n\t\t\t\t\t\t\tthis.applyingLayout = true\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tthis.grid.update(el, { x, y })\n\t\t\t\t\t\t\t} finally {\n\t\t\t\t\t\t\t\tthis.applyingLayout = false\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Drop the stale node without touching its detached DOM,\n\t\t\t\t\t\t// then adopt the new element so GridStack sizes/places it.\n\t\t\t\t\t\tthis.grid.removeWidget(node.el, false, false)\n\t\t\t\t\t\tthis.grid.makeWidget(el)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\n\t\t\t// Remove items no longer in layout\n\t\t\tconst ids = newLayout.map((i) => String(i.id))\n\t\t\tconst toRemove = this.grid.engine.nodes.filter((n) => !ids.includes(String(n.id)))\n\t\t\tfor (const node of toRemove) {\n\t\t\t\tconst el = this.$refs.gridContainer.querySelector(`[gs-id=\"${node.id}\"]`)\n\t\t\t\tif (el) {\n\t\t\t\t\tthis.grid.removeWidget(el, false)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Elements were added or replaced, so the observer's are stale.\n\t\t\tthis.$nextTick(() => this.observeContentSizes())\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-dashboard-grid {\n\twidth: 100%;\n\tmin-height: 200px;\n}\n\n.grid-stack {\n\tbackground: transparent;\n}\n\n/* Screen-reader-only text: the keyboard key map and the live region. Kept in\n the layout (not `display: none`) so assistive tech still reads it. */\n.cn-dashboard-grid__sr-only {\n\tposition: absolute;\n\twidth: 1px;\n\theight: 1px;\n\tpadding: 0;\n\tmargin: -1px;\n\toverflow: hidden;\n\tclip-path: inset(50%);\n\twhite-space: nowrap;\n\tborder: 0;\n}\n\n/* Grid items become tab stops in edit mode — give them a visible focus ring\n that satisfies WCAG 2.1 SC 2.4.7 without relying on the widget's own\n styling. `outline-offset` keeps the ring outside the rounded card. */\n.grid-stack-item:focus-visible {\n\toutline: 2px solid var(--color-primary-element, #0082c9);\n\toutline-offset: 2px;\n\tborder-radius: var(--border-radius-container-large, 16px);\n}\n\n/* A size-to-content cell (`sizeToContent`) is measured by GridStack from the\n content's first child, and a widget wrapper there filled the cell\n (`height: 100%`), so the measure returned the cell's own height and the\n cell never shrank: \"Waiting for me\" stayed a tall card around one row. In\n such a cell the child takes its own height. A cell without the key keeps\n the filling child. */\n.cn-dashboard-grid__item--fit > :deep(.grid-stack-item-content > *) {\n\theight: auto;\n}\n\n:deep(.grid-stack-item-content) {\n\tbackground: var(--color-main-background);\n\tborder: none;\n\tbox-shadow: none;\n\toverflow: hidden;\n}\n\n:deep(.grid-stack-item-content:has(.cn-widget-wrapper--borderless)) {\n\tbackground: transparent;\n}\n\n:deep(.grid-stack-placeholder > .placeholder-content) {\n\tbackground: var(--color-primary-element-light);\n\tborder: 2px dashed var(--color-primary-element);\n\tborder-radius: var(--border-radius-large);\n}\n</style>\n"],"names":["t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnDashboardGrid.vue2.js","sources":["../../../../src/components/CnDashboardGrid/CnDashboardGrid.vue"],"sourcesContent":["<!--\n CnDashboardGrid — GridStack-powered drag-and-drop dashboard grid.\n\n Renders widgets in a configurable grid layout. Supports drag, resize,\n and dynamic item addition/removal. Emits layout changes for persistence.\n\n Accessibility: every grid item is an ARIA `group` with an accessible name\n and — in edit mode — a tab stop, so the pointer-only GridStack drag/resize\n gestures have a full keyboard equivalent (WCAG 2.1 SC 2.1.1). Keyboard\n moves are routed through `GridStack.update()`, i.e. the exact same engine\n call drag/resize ends in, so collision handling, persistence and the\n `layout-change` emission are byte-identical between the two input modes.\n-->\n<template>\n\t<div ref=\"gridContainer\" class=\"cn-dashboard-grid\">\n\t\t<!--\n\t\t\tKeyboard instructions, referenced by every grid item through\n\t\t\t`aria-describedby`. Rendered once (not per item) so a screen\n\t\t\treader user hears the key map on the first item they land on\n\t\t\twithout it being duplicated N times in the accessibility tree.\n\t\t-->\n\t\t<p\n\t\t\tv-if=\"keyboardActive\"\n\t\t\t:id=\"keyboardHelpDomId\"\n\t\t\tclass=\"cn-dashboard-grid__sr-only\">\n\t\t\t{{ keyboardHelpText }}\n\t\t</p>\n\n\t\t<div class=\"grid-stack\">\n\t\t\t<div\n\t\t\t\tv-for=\"(item, index) in layout\"\n\t\t\t\t:key=\"resolveItemKey(item)\"\n\t\t\t\tclass=\"grid-stack-item\"\n\t\t\t\t:class=\"{ 'cn-dashboard-grid__item--fit': sizeToContentAttr(item) !== undefined }\"\n\t\t\t\trole=\"group\"\n\t\t\t\t:aria-label=\"resolveItemLabel(item, index)\"\n\t\t\t\t:aria-describedby=\"itemDescribedBy\"\n\t\t\t\t:tabindex=\"itemTabindex\"\n\t\t\t\t:gs-id=\"item.id\"\n\t\t\t\t:gs-x=\"item.gridX\"\n\t\t\t\t:gs-y=\"item.gridY\"\n\t\t\t\t:gs-w=\"item.gridWidth\"\n\t\t\t\t:gs-h=\"item.gridHeight\"\n\t\t\t\t:gs-min-w=\"minWidth\"\n\t\t\t\t:gs-min-h=\"minHeight\"\n\t\t\t\t:gs-size-to-content=\"sizeToContentAttr(item)\"\n\t\t\t\t@keydown=\"onItemKeydown($event, item)\">\n\t\t\t\t<div class=\"grid-stack-item-content\">\n\t\t\t\t\t<slot name=\"widget\" :item=\"item\">\n\t\t\t\t\t\t<!-- Default: render nothing; CnDashboardPage provides content -->\n\t\t\t\t\t</slot>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!--\n\t\t\tPolite live region for keyboard move/resize feedback. A sighted\n\t\t\tuser sees the widget slide; a screen-reader user needs the new\n\t\t\tcoordinates spoken, otherwise the arrow keys are silent.\n\t\t-->\n\t\t<p\n\t\t\tclass=\"cn-dashboard-grid__sr-only\"\n\t\t\trole=\"status\"\n\t\t\taria-live=\"polite\"\n\t\t\taria-atomic=\"true\">\n\t\t\t{{ announcement }}\n\t\t</p>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { GridStack } from 'gridstack'\n// `gridstack` is a peerDependency (see package.json), not bundled — this\n// component's JS and the consumer's GridStack stylesheet MUST come from the\n// exact same installed copy, because GridStack's JS writes layout state\n// (e.g. the `--gs-column-width` CSS custom property) that only its OWN\n// matching CSS reads. Baking a copy of `gridstack/dist/gridstack.min.css`\n// into this library's build (as a previous version did) freezes it at\n// nc-vue's build-time version, which can silently drift from whatever the\n// consumer's peer-installed copy actually is — the exact mismatch that once\n// made every grid item render at 0px width. So nc-vue ships NO GridStack\n// CSS of its own: the consumer MUST install `gridstack` themselves and\n// import `gridstack/dist/gridstack.min.css` (or their preferred GridStack\n// stylesheet) from that same copy.\n\n/**\n * Monotonic counter backing the per-instance DOM ids (the shared\n * `aria-describedby` help text). Module-level so two grids on one page\n * never collide — the same pattern CnCommandPalette uses.\n */\nlet domIdCounter = 0\n\n/**\n * Pixel offset applied to the grid item's top-left corner when synthesising\n * the `contextmenu` coordinates for a keyboard activation. Nudging the anchor\n * inside the item keeps consumer menus (which position at `clientX/clientY`)\n * visually attached to the widget instead of straddling its border.\n */\nconst MENU_ANCHOR_OFFSET = 8\n\n/**\n * CnDashboardGrid — Low-level grid layout engine powered by GridStack.\n *\n * Manages the drag-and-drop grid, syncs positions, and emits layout\n * changes. Does NOT handle widget rendering — that's done by the parent\n * via the `#widget` scoped slot.\n *\n * ```vue\n * <CnDashboardGrid\n * :layout=\"placements\"\n * :editable=\"isEditing\"\n * @layout-change=\"onLayoutChange\">\n * <template #widget=\"{ item }\">\n * <MyWidget :config=\"item\" />\n * </template>\n * </CnDashboardGrid>\n * ```\n *\n * ### Keyboard operation\n *\n * With `keyboardRepositioning` on (the default) and `editable` true, each\n * grid item is a tab stop. With an item focused:\n *\n * | Key | Action |\n * | --- | ------ |\n * | `ArrowLeft` / `ArrowRight` | move one column left / right |\n * | `ArrowUp` / `ArrowDown` | move one row up / down |\n * | `Shift` + `ArrowLeft` / `ArrowRight` | shrink / grow width by one column |\n * | `Shift` + `ArrowUp` / `ArrowDown` | shrink / grow height by one row |\n * | `Home` / `End` | jump to the first / last column of the current row |\n * | `Enter` / `Space` | activate the item — see `item-activate` |\n *\n * Keys are only honoured when the grid item itself holds focus, so\n * interactive content rendered inside the `#widget` slot keeps its own\n * key handling untouched.\n */\nexport default {\n\tname: 'CnDashboardGrid',\n\n\tprops: {\n\t\t/** Array of layout items: { id, gridX, gridY, gridWidth, gridHeight, ...extra } */\n\t\tlayout: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Whether drag and resize are enabled */\n\t\teditable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Number of grid columns */\n\t\tcolumns: {\n\t\t\ttype: Number,\n\t\t\tdefault: 12,\n\t\t},\n\n\t\t/** Cell height in pixels */\n\t\tcellHeight: {\n\t\t\ttype: Number,\n\t\t\tdefault: 80,\n\t\t},\n\n\t\t/** Grid margin in pixels */\n\t\tmargin: {\n\t\t\ttype: Number,\n\t\t\tdefault: 12,\n\t\t},\n\n\t\t/** Minimum widget width in grid units */\n\t\tminWidth: {\n\t\t\ttype: Number,\n\t\t\tdefault: 2,\n\t\t},\n\n\t\t/** Minimum widget height in grid units */\n\t\tminHeight: {\n\t\t\ttype: Number,\n\t\t\tdefault: 2,\n\t\t},\n\n\t\t/**\n\t\t * GridStack v12 responsive `columnOpts` bag (breakpoints + reflow\n\t\t * layout). When set, the grid reflows column count across screen sizes.\n\t\t * Build it with `getDashboardColumnOpts()`. Default `null` = fixed\n\t\t * `columns`, no responsive reflow (backwards-compatible).\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tcolumnOpts: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * When set, `cellHeight` is mirrored into this CSS custom property on\n\t\t * the document root at init (e.g. `'--app-cell-height'`), so app CSS can\n\t\t * align to the grid geometry. Default `null` = no CSS var written.\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\tcellHeightCssVar: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Optional `(item) => string|number` to derive each item's render key.\n\t\t * Use it to force a re-render when an item changes in a way its `id`\n\t\t * doesn't capture (e.g. style edits — return `${item.id}:${item.updatedAt}`).\n\t\t * Default `null` = key on `item.id`.\n\t\t *\n\t\t * @type {((item: object) => (string|number))|null}\n\t\t */\n\t\titemKey: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Whether grid items are keyboard-operable: focusable in edit mode and\n\t\t * repositionable/resizable with the arrow keys (WCAG 2.1 SC 2.1.1 —\n\t\t * the keyboard equivalent of the pointer-only GridStack drag). Turning\n\t\t * it off leaves the items non-focusable and drag-only. Default `true`.\n\t\t */\n\t\tkeyboardRepositioning: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Optional `(item, index) => string` returning the accessible name for\n\t\t * a grid item. Use it when the layout items don't carry a human-readable\n\t\t * field (the built-in fallback walks `title` → `name` → `label` →\n\t\t * `widgetTitle` → `widgetId`, then a positional \"Widget N\"). The returned\n\t\t * string is used verbatim — no coordinates are appended.\n\t\t *\n\t\t * @type {((item: object, index: number) => string)|null}\n\t\t */\n\t\titemLabel: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Whether a keyboard activation (`Enter` / `Space` on a focused grid\n\t\t * item) also dispatches a bubbling `contextmenu` event from inside the\n\t\t * item, so consumers that open a per-widget menu on right-click get the\n\t\t * keyboard path for free — mirroring what browsers do for the `Menu`\n\t\t * key. Set `false` to rely purely on the `item-activate` event.\n\t\t * Default `true`.\n\t\t */\n\t\tactivateOpensContextMenu: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * GridStack's `float`. True (the default) keeps every item on the row\n\t\t * it was placed on, with empty rows above it if need be. False packs\n\t\t * items upward, so a size-to-content cell that shrinks, or a widget\n\t\t * that is not shown, closes its gap instead of leaving a band.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-dashboard-closes-the-gap-a-shrinking-or-hidden-widget-leaves\n\t\t */\n\t\tfloat: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * Fired whenever the grid geometry changed — by drag, by resize, or by\n\t\t * a keyboard nudge. Payload is the full `layout` array with the moved\n\t\t * items' `gridX`/`gridY`/`gridWidth`/`gridHeight` updated.\n\t\t */\n\t\t'layout-change',\n\t\t/**\n\t\t * Fired when the user activates a focused grid item with `Enter` or\n\t\t * `Space`. Payload `{ item, element, clientX, clientY }` — the layout\n\t\t * item, its DOM element, and viewport coordinates anchored to the\n\t\t * item's top-left corner so a consumer menu can position itself the\n\t\t * same way it does for a right-click.\n\t\t */\n\t\t'item-activate',\n\t],\n\n\tdata() {\n\t\treturn {\n\t\t\tgrid: null,\n\t\t\tcontentObserver: null,\n\t\t\t/** True while the grid moves an item to where the layout put it. */\n\t\t\tapplyingLayout: false,\n\t\t\tdomId: `cn-dashboard-grid-${++domIdCounter}`,\n\t\t\t/** Text currently held in the polite live region. */\n\t\t\tannouncement: '',\n\t\t\t/**\n\t\t\t * Whether a `columnOpts` breakpoint is currently in force. Mirrored\n\t\t\t * from the engine by `syncInteractivity` because `isReflowed()` reads\n\t\t\t * `grid.getColumn()` imperatively, and a computed cannot re-evaluate\n\t\t\t * on that.\n\t\t\t */\n\t\t\treflowed: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * @return {boolean} Whether the keyboard lane is live — items are\n\t\t * focusable and the arrow keys reposition. Requires the opt-in prop,\n\t\t * edit mode (a read-only dashboard has nothing to move, so adding N tab\n\t\t * stops there would be pure noise), and a grid that is not reflowed,\n\t\t * which is where a move cannot be kept and so must not be offered.\n\t\t */\n\t\tkeyboardActive() {\n\t\t\treturn this.keyboardRepositioning && this.editable && !this.reflowed\n\t\t},\n\n\t\t/**\n\t\t * @return {number|null} `0` when grid items should be tab stops,\n\t\t * otherwise `null` so the attribute is omitted entirely.\n\t\t */\n\t\titemTabindex() {\n\t\t\treturn this.keyboardActive ? 0 : null\n\t\t},\n\n\t\t/**\n\t\t * @return {string} DOM id of the shared keyboard-instructions element.\n\t\t */\n\t\tkeyboardHelpDomId() {\n\t\t\treturn `${this.domId}-keyboard-help`\n\t\t},\n\n\t\t/**\n\t\t * @return {string|null} `aria-describedby` target for each grid item —\n\t\t * the instructions element while the keyboard lane is live, `null`\n\t\t * otherwise (a dangling id reference is an axe violation).\n\t\t */\n\t\titemDescribedBy() {\n\t\t\treturn this.keyboardActive ? this.keyboardHelpDomId : null\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The spoken key map for the keyboard lane.\n\t\t */\n\t\tkeyboardHelpText() {\n\t\t\treturn t(\n\t\t\t\t'nextcloud-vue',\n\t\t\t\t'Use the arrow keys to move this widget one cell. Hold Shift and use the arrow keys to resize it. Press Home or End to jump to the first or last column. Press Enter to open the widget menu.',\n\t\t\t)\n\t\t},\n\n\t\t/**\n\t\t * @return {number} The column count currently in force. With responsive\n\t\t * `columnOpts` GridStack reflows to fewer columns on small screens, so\n\t\t * clamping must read the live value rather than the `columns` prop.\n\t\t */\n\t\teffectiveColumns() {\n\t\t\tif (this.grid && typeof this.grid.getColumn === 'function') {\n\t\t\t\tconst live = this.grid.getColumn()\n\t\t\t\tif (Number.isFinite(live) && live > 0) {\n\t\t\t\t\treturn live\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn this.columns\n\t\t},\n\t},\n\n\twatch: {\n\t\teditable() {\n\t\t\tthis.syncInteractivity()\n\t\t},\n\n\t\tlayout: {\n\t\t\tdeep: true,\n\t\t\thandler(newLayout) {\n\t\t\t\tif (this.grid) {\n\t\t\t\t\tthis.syncGridItems(newLayout)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.initGrid()\n\t\tthis.$nextTick(() => this.observeContentSizes())\n\t},\n\n\tbeforeUnmount() {\n\t\tthis.disconnectContentObserver()\n\t\tif (this.grid) {\n\t\t\tthis.grid.destroy(false)\n\t\t}\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Derive the v-for render key for a layout item, honouring the optional\n\t\t * `itemKey` prop and falling back to `item.id`.\n\t\t *\n\t\t * @param {object} item the layout item.\n\t\t * @return {string|number} the render key.\n\t\t */\n\t\tresolveItemKey(item) {\n\t\t\treturn this.itemKey ? this.itemKey(item) : item.id\n\t\t},\n\n\t\t/**\n\t\t * The `gs-size-to-content` attribute for a layout item, or `undefined` to\n\t\t * leave the item on its authored `gridHeight`.\n\t\t *\n\t\t * A widget that decides AT RUNTIME it has nothing to show — a banner whose\n\t\t * condition is false, a list that came back empty — still costs its whole\n\t\t * authored row, because the row is reserved from the layout before the\n\t\t * component renders and the engine positions items absolutely, so no CSS\n\t\t * can reclaim it. `sizeToContent` hands the row's height to the content, so\n\t\t * an empty widget collapses and the grid closes up behind it.\n\t\t *\n\t\t * Opt-in per item: it makes `gridHeight` advisory for that widget, which is\n\t\t * wrong for anything with a deliberate size (a map, a chart) and right for\n\t\t * anything whose height is whatever it turned out to have.\n\t\t *\n\t\t * @param {object} item the layout item.\n\t\t * @return {string|undefined} `'true'`, a row maximum as a string, or undefined.\n\t\t */\n\t\tsizeToContentAttr(item) {\n\t\t\tconst v = item && item.sizeToContent\n\t\t\tif (v === true) {\n\t\t\t\treturn 'true'\n\t\t\t}\n\t\t\t// A number is GridStack's \"size to content, but never taller than N rows\" (a maximum).\n\t\t\tif (Number.isFinite(v) && v > 0) {\n\t\t\t\treturn String(v)\n\t\t\t}\n\t\t\treturn undefined\n\t\t},\n\n\t\t/**\n\t\t * Build the accessible name for a grid item. A custom `itemLabel` wins\n\t\t * outright; otherwise the first human-readable field on the item is used\n\t\t * and, while the keyboard lane is live, the current grid coordinates are\n\t\t * appended — without them a screen-reader user tabbing through an edit-mode\n\t\t * dashboard has no way to tell where anything sits.\n\t\t *\n\t\t * @param {object} item the layout item.\n\t\t * @param {number} index the item's index in `layout`, used for the\n\t\t * last-resort positional name.\n\t\t * @return {string} the accessible name.\n\t\t */\n\t\tresolveItemLabel(item, index) {\n\t\t\tif (this.itemLabel) {\n\t\t\t\treturn String(this.itemLabel(item, index) ?? '')\n\t\t\t}\n\n\t\t\tconst base = item.title\n\t\t\t\t|| item.name\n\t\t\t\t|| item.label\n\t\t\t\t|| item.widgetTitle\n\t\t\t\t|| item.widgetId\n\t\t\t\t|| t('nextcloud-vue', 'Widget {number}', { number: index + 1 })\n\n\t\t\tif (!this.keyboardActive) {\n\t\t\t\treturn String(base)\n\t\t\t}\n\n\t\t\t// Report the rectangle exactly as the reactive `layout` prop states\n\t\t\t// it — unclamped, so the spoken coordinates always match the\n\t\t\t// rendered `gs-*` attributes — and read from the prop (not the\n\t\t\t// GridStack node) so the name re-renders when a move round-trips\n\t\t\t// back through `layout-change`; engine-node mutations are invisible\n\t\t\t// to Vue's reactivity.\n\t\t\tconst rect = {\n\t\t\t\tx: Number.isFinite(item.gridX) ? item.gridX : 0,\n\t\t\t\ty: Number.isFinite(item.gridY) ? item.gridY : 0,\n\t\t\t\tw: Number.isFinite(item.gridWidth) ? item.gridWidth : this.minWidth,\n\t\t\t\th: Number.isFinite(item.gridHeight) ? item.gridHeight : this.minHeight,\n\t\t\t}\n\t\t\treturn t(\n\t\t\t\t'nextcloud-vue',\n\t\t\t\t'{label}, column {column} of {columns}, row {row}, {width} columns wide, {height} rows tall',\n\t\t\t\t{\n\t\t\t\t\tlabel: String(base),\n\t\t\t\t\tcolumn: rect.x + 1,\n\t\t\t\t\tcolumns: this.effectiveColumns,\n\t\t\t\t\trow: rect.y + 1,\n\t\t\t\t\twidth: rect.w,\n\t\t\t\t\theight: rect.h,\n\t\t\t\t},\n\t\t\t)\n\t\t},\n\n\t\tinitGrid() {\n\t\t\tif (this.cellHeightCssVar && typeof document !== 'undefined' && document.documentElement) {\n\t\t\t\tdocument.documentElement.style.setProperty(this.cellHeightCssVar, `${this.cellHeight}px`)\n\t\t\t}\n\t\t\tconst el = this.$refs.gridContainer.querySelector('.grid-stack')\n\t\t\tthis.grid = GridStack.init({\n\t\t\t\tcolumn: this.columns,\n\t\t\t\tcellHeight: this.cellHeight,\n\t\t\t\tmargin: this.margin,\n\t\t\t\tfloat: this.float,\n\t\t\t\tanimate: true,\n\t\t\t\tdisableDrag: !this.editable,\n\t\t\t\tdisableResize: !this.editable,\n\t\t\t\tremovable: false,\n\t\t\t\t...(this.columnOpts ? { columnOpts: this.columnOpts } : {}),\n\t\t\t}, el)\n\n\t\t\tthis.grid.on('change', (_event, items) => {\n\t\t\t\tthis.handleGridChange(items)\n\t\t\t})\n\n\t\t\t// A grid that mounts already narrow is reflowed before anyone touches\n\t\t\t// it, so the handles have to be judged here and not only on a later\n\t\t\t// change.\n\t\t\tthis.syncInteractivity()\n\t\t},\n\n\t\t/**\n\t\t * Keep both editing lanes in step with whether an edit could actually be\n\t\t * kept: the pointer's handles, and — through `reflowed` feeding\n\t\t * `keyboardActive` — the tab stops, the help text and the arrow keys.\n\t\t *\n\t\t * A reflowed grid drops every `change` it receives, for the reason\n\t\t * `handleGridChange` gives. Leaving either lane live there offers an edit\n\t\t * that silently snaps back, which is the same class of surprise as\n\t\t * persisting the reflow — pointed the other way, and harder to explain\n\t\t * because the author did everything right. It is worse on the keyboard,\n\t\t * where the move is also announced as done. The affordance has to say what\n\t\t * the grid will do at this width, not what edit mode does at the authored\n\t\t * one.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tsyncInteractivity() {\n\t\t\t// Ahead of the engine guard: the keyboard lane reads this whether or\n\t\t\t// not the engine can be enabled.\n\t\t\tthis.reflowed = this.isReflowed()\n\t\t\tif (!this.grid || typeof this.grid.enable !== 'function') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.editable && !this.reflowed) {\n\t\t\t\tthis.grid.enable()\n\t\t\t} else {\n\t\t\t\tthis.grid.disable()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Whether the grid is currently rescaled to a column count other than the\n\t\t * authored one, i.e. a `columnOpts` breakpoint is in force.\n\t\t *\n\t\t * The authored count is `columnOpts.columnMax` when there is one, not the\n\t\t * `columns` prop: GridStack widens to `columnMax` above the top breakpoint,\n\t\t * so a consumer whose two disagree is at its widest, not reflowed.\n\t\t *\n\t\t * @return {boolean} True while a responsive reflow is active.\n\t\t */\n\t\tisReflowed() {\n\t\t\tif (!this.grid || typeof this.grid.getColumn !== 'function') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst authored = Number.isFinite(this.columnOpts?.columnMax) ? this.columnOpts.columnMax : this.columns\n\t\t\tconst live = this.grid.getColumn()\n\t\t\treturn Number.isFinite(live) && live > 0 && live !== authored\n\t\t},\n\n\t\thandleGridChange(items) {\n\t\t\t// The rescale that crossed a breakpoint arrives here as a `change`, so\n\t\t\t// this is also where the handles are re-judged in both directions:\n\t\t\t// switched off as the grid narrows, and back on as it widens.\n\t\t\tthis.syncInteractivity()\n\n\t\t\tif (this.applyingLayout) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// GridStack fires `change` for its own responsive rescale too, and at a\n\t\t\t// 12 → 1 breakpoint every item reads `gridX: 0, gridWidth: 1`. Emitting\n\t\t\t// that has the host persist the reflow AS the authored layout, which\n\t\t\t// widening back does not undo. A move made while reflowed is the same\n\t\t\t// rescaled geometry, so it is dropped for the same reason.\n\t\t\tif (this.isReflowed()) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!items || items.length === 0) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst updated = this.layout.map((item) => {\n\t\t\t\tconst gridItem = items.find((gi) => String(gi.id) === String(item.id))\n\t\t\t\tif (gridItem) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\t...item,\n\t\t\t\t\t\tgridX: gridItem.x,\n\t\t\t\t\t\tgridY: gridItem.y,\n\t\t\t\t\t\tgridWidth: gridItem.w,\n\t\t\t\t\t\tgridHeight: gridItem.h,\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn item\n\t\t\t})\n\n\t\t\tthis.$emit('layout-change', updated)\n\t\t},\n\n\t\t/**\n\t\t * Keydown handler bound to every grid item.\n\t\t *\n\t\t * Only reacts when the grid item element itself is the event target:\n\t\t * a button, input or menu rendered inside the `#widget` slot must keep\n\t\t * its own arrow/Enter semantics, and a grid that swallowed them would\n\t\t * be a worse a11y regression than the one this handler fixes.\n\t\t *\n\t\t * @param {KeyboardEvent} event the keydown event.\n\t\t * @param {object} item the layout item the key was pressed on.\n\t\t * @return {void}\n\t\t */\n\t\tonItemKeydown(event, item) {\n\t\t\tif (!this.keyboardRepositioning) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (event.target !== event.currentTarget) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (event.altKey || event.ctrlKey || event.metaKey) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (event.key === 'Enter' || event.key === ' ' || event.key === 'Spacebar') {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tthis.activateItem(item, event.currentTarget)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Repositioning only, and only where it can be kept — an item that\n\t\t\t// still holds focus from before a reflow keeps its Enter, but its\n\t\t\t// arrow keys stop moving something that would snap back.\n\t\t\tif (!this.keyboardActive) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst handled = this.applyKey(event.key, event.shiftKey, item)\n\t\t\tif (handled) {\n\t\t\t\t// Arrows would otherwise scroll the dashboard out from under\n\t\t\t\t// the widget the user is moving.\n\t\t\t\tevent.preventDefault()\n\t\t\t\tevent.stopPropagation()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Translate one repositioning key into a geometry change.\n\t\t *\n\t\t * @param {string} key the `KeyboardEvent.key` value.\n\t\t * @param {boolean} shift whether Shift was held (resize instead of move).\n\t\t * @param {object} item the layout item being operated on.\n\t\t * @return {boolean} `true` when the key belongs to the grid and the\n\t\t * default action must be suppressed.\n\t\t */\n\t\tapplyKey(key, shift, item) {\n\t\t\tconst rect = this.currentRect(item)\n\n\t\t\tswitch (key) {\n\t\t\t\tcase 'ArrowLeft':\n\t\t\t\t\treturn shift\n\t\t\t\t\t\t? this.applyGeometry(item, { w: rect.w - 1 }, 'resize')\n\t\t\t\t\t\t: this.applyGeometry(item, { x: rect.x - 1 }, 'move')\n\t\t\t\tcase 'ArrowRight':\n\t\t\t\t\treturn shift\n\t\t\t\t\t\t? this.applyGeometry(item, { w: rect.w + 1 }, 'resize')\n\t\t\t\t\t\t: this.applyGeometry(item, { x: rect.x + 1 }, 'move')\n\t\t\t\tcase 'ArrowUp':\n\t\t\t\t\treturn shift\n\t\t\t\t\t\t? this.applyGeometry(item, { h: rect.h - 1 }, 'resize')\n\t\t\t\t\t\t: this.applyGeometry(item, { y: rect.y - 1 }, 'move')\n\t\t\t\tcase 'ArrowDown':\n\t\t\t\t\treturn shift\n\t\t\t\t\t\t? this.applyGeometry(item, { h: rect.h + 1 }, 'resize')\n\t\t\t\t\t\t: this.applyGeometry(item, { y: rect.y + 1 }, 'move')\n\t\t\t\tcase 'Home':\n\t\t\t\t\treturn this.applyGeometry(item, { x: 0 }, 'move')\n\t\t\t\tcase 'End':\n\t\t\t\t\treturn this.applyGeometry(item, { x: this.effectiveColumns - rect.w }, 'move')\n\t\t\t\tdefault:\n\t\t\t\t\treturn false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Read an item's live rectangle. The GridStack node is preferred over\n\t\t * the `layout` prop because a drag that has not yet round-tripped\n\t\t * through the consumer's persistence leaves the prop momentarily stale,\n\t\t * and a keyboard nudge must continue from where the widget actually is.\n\t\t *\n\t\t * @param {object} item the layout item.\n\t\t * @return {{x: number, y: number, w: number, h: number}} the rectangle\n\t\t * in grid units.\n\t\t */\n\t\tcurrentRect(item) {\n\t\t\tconst node = this.gridNode(item.id)\n\t\t\tif (node && Number.isFinite(node.x) && Number.isFinite(node.y)) {\n\t\t\t\treturn {\n\t\t\t\t\tx: node.x,\n\t\t\t\t\ty: node.y,\n\t\t\t\t\tw: Number.isFinite(node.w) ? node.w : this.minWidth,\n\t\t\t\t\th: Number.isFinite(node.h) ? node.h : this.minHeight,\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tx: Number.isFinite(item.gridX) ? item.gridX : 0,\n\t\t\t\ty: Number.isFinite(item.gridY) ? item.gridY : 0,\n\t\t\t\tw: Number.isFinite(item.gridWidth) ? item.gridWidth : this.minWidth,\n\t\t\t\th: Number.isFinite(item.gridHeight) ? item.gridHeight : this.minHeight,\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Clamp a candidate rectangle to the grid's bounds and the configured\n\t\t * minimum widget size, so a held-down arrow key parks the widget at the\n\t\t * edge instead of pushing it into negative coordinates.\n\t\t *\n\t\t * @param {{x: number, y: number, w: number, h: number}} rect the candidate.\n\t\t * @return {{x: number, y: number, w: number, h: number}} the clamped rectangle.\n\t\t */\n\t\tclampRect(rect) {\n\t\t\tconst cols = this.effectiveColumns\n\t\t\tconst w = Math.min(Math.max(rect.w, this.minWidth), cols)\n\t\t\tconst h = Math.max(rect.h, this.minHeight)\n\t\t\treturn {\n\t\t\t\tx: Math.min(Math.max(rect.x, 0), Math.max(cols - w, 0)),\n\t\t\t\ty: Math.max(rect.y, 0),\n\t\t\t\tw,\n\t\t\t\th,\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Apply a partial geometry change to an item.\n\t\t *\n\t\t * The change is pushed through `GridStack.update()` — the very call the\n\t\t * drag/resize handlers end in — so collision resolution, the `change`\n\t\t * event and therefore the `layout-change` emission and the consumer's\n\t\t * persistence are identical for keyboard and pointer. There is\n\t\t * deliberately no second, direct-to-`layout` update path.\n\t\t *\n\t\t * @param {object} item the layout item to move or resize.\n\t\t * @param {{x?: number, y?: number, w?: number, h?: number}} patch the\n\t\t * requested change, merged over the item's current rectangle.\n\t\t * @param {'move'|'resize'} kind which announcement to speak.\n\t\t * @return {boolean} `true` when the key was consumed — including when\n\t\t * the widget was already at the edge, because the arrow still belongs\n\t\t * to the grid and must not scroll the page.\n\t\t */\n\t\tapplyGeometry(item, patch, kind) {\n\t\t\tconst el = this.itemElement(item.id)\n\t\t\tif (!el || !this.grid || typeof this.grid.update !== 'function') {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tconst current = this.currentRect(item)\n\t\t\tconst next = this.clampRect({ ...current, ...patch })\n\n\t\t\tif (next.x === current.x && next.y === current.y\n\t\t\t\t&& next.w === current.w && next.h === current.h) {\n\t\t\t\tthis.announce(t('nextcloud-vue', '{label} cannot move further in that direction', {\n\t\t\t\t\tlabel: this.announcementLabel(item),\n\t\t\t\t}))\n\t\t\t\treturn true\n\t\t\t}\n\n\t\t\tthis.grid.update(el, next)\n\n\t\t\tthis.announce(kind === 'resize'\n\t\t\t\t? t('nextcloud-vue', '{label} resized to {width} columns by {height} rows', {\n\t\t\t\t\t\tlabel: this.announcementLabel(item),\n\t\t\t\t\t\twidth: next.w,\n\t\t\t\t\t\theight: next.h,\n\t\t\t\t\t})\n\t\t\t\t: t('nextcloud-vue', '{label} moved to column {column}, row {row}', {\n\t\t\t\t\t\tlabel: this.announcementLabel(item),\n\t\t\t\t\t\tcolumn: next.x + 1,\n\t\t\t\t\t\trow: next.y + 1,\n\t\t\t\t\t}))\n\n\t\t\treturn true\n\t\t},\n\n\t\t/**\n\t\t * The short, coordinate-free name used inside live-region sentences —\n\t\t * the full `aria-label` already carries the coordinates and repeating\n\t\t * them would make every announcement unlistenable.\n\t\t *\n\t\t * @param {object} item the layout item.\n\t\t * @return {string} the short name.\n\t\t */\n\t\tannouncementLabel(item) {\n\t\t\treturn String(item.title\n\t\t\t\t|| item.name\n\t\t\t\t|| item.label\n\t\t\t\t|| item.widgetTitle\n\t\t\t\t|| item.widgetId\n\t\t\t\t|| t('nextcloud-vue', 'Widget'))\n\t\t},\n\n\t\t/**\n\t\t * Push text into the polite live region. The region is cleared first so\n\t\t * two identical consecutive announcements (e.g. two failed nudges at the\n\t\t * grid edge) are both spoken — assistive tech ignores a re-render that\n\t\t * leaves the text unchanged.\n\t\t *\n\t\t * @param {string} text the message to announce.\n\t\t * @return {void}\n\t\t */\n\t\tannounce(text) {\n\t\t\tthis.announcement = ''\n\t\t\tthis.$nextTick(() => {\n\t\t\t\tthis.announcement = text\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Handle `Enter` / `Space` on a focused grid item: tell the consumer via\n\t\t * `item-activate`, and — unless opted out — synthesise the `contextmenu`\n\t\t * event a right-click would have produced, anchored to the item, so\n\t\t * pointer-only widget menus become keyboard-reachable without the\n\t\t * consumer wiring anything new. The synthetic event is dispatched from\n\t\t * inside the item's content so it bubbles through the slot's own root\n\t\t * element (where such handlers live) and on up.\n\t\t *\n\t\t * @param {object} item the layout item being activated.\n\t\t * @param {HTMLElement} el the grid item element.\n\t\t * @return {void}\n\t\t */\n\t\tactivateItem(item, el) {\n\t\t\tconst rect = typeof el.getBoundingClientRect === 'function'\n\t\t\t\t? el.getBoundingClientRect()\n\t\t\t\t: { left: 0, top: 0 }\n\t\t\tconst clientX = Math.round((rect.left || 0) + MENU_ANCHOR_OFFSET)\n\t\t\tconst clientY = Math.round((rect.top || 0) + MENU_ANCHOR_OFFSET)\n\n\t\t\tthis.$emit('item-activate', { item, element: el, clientX, clientY })\n\n\t\t\tif (this.activateOpensContextMenu) {\n\t\t\t\tthis.dispatchContextMenu(el, clientX, clientY)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Dispatch a bubbling, cancelable `contextmenu` MouseEvent from inside a\n\t\t * grid item.\n\t\t *\n\t\t * @param {HTMLElement} el the grid item element.\n\t\t * @param {number} clientX viewport x for the menu anchor.\n\t\t * @param {number} clientY viewport y for the menu anchor.\n\t\t * @return {void}\n\t\t */\n\t\tdispatchContextMenu(el, clientX, clientY) {\n\t\t\tif (typeof MouseEvent !== 'function') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst content = el.querySelector('.grid-stack-item-content')\n\t\t\tconst target = (content && content.firstElementChild) || content || el\n\t\t\ttarget.dispatchEvent(new MouseEvent('contextmenu', {\n\t\t\t\tbubbles: true,\n\t\t\t\tcancelable: true,\n\t\t\t\tclientX,\n\t\t\t\tclientY,\n\t\t\t\tbutton: 2,\n\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Look up a grid item's DOM element by its layout id.\n\t\t *\n\t\t * @param {string|number} id the layout item id.\n\t\t * @return {HTMLElement|null} the element, or `null` when not rendered.\n\t\t */\n\t\titemElement(id) {\n\t\t\tconst container = this.$refs.gridContainer\n\t\t\tif (!container) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn container.querySelector(`[gs-id=\"${id}\"]`)\n\t\t},\n\n\t\t/**\n\t\t * Look up the GridStack engine node backing a layout item.\n\t\t *\n\t\t * @param {string|number} id the layout item id.\n\t\t * @return {object|null} the node, or `null` when GridStack isn't tracking it.\n\t\t */\n\t\t/**\n\t\t * Re-measure a `sizeToContent` item when its content changes height.\n\t\t * GridStack watches only the grid element, so a widget that loaded its data\n\t\t * in `mounted()` keeps the height its empty state had.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tobserveContentSizes() {\n\t\t\tthis.disconnectContentObserver()\n\t\t\tif (typeof ResizeObserver === 'undefined' || !this.$refs.gridContainer) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.contentObserver = new ResizeObserver((entries) => {\n\t\t\t\tconst cells = new Set()\n\t\t\t\tfor (const entry of entries) {\n\t\t\t\t\tconst cell = entry.target.closest('.grid-stack-item')\n\t\t\t\t\tif (cell) {\n\t\t\t\t\t\tcells.add(cell)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (cells.size === 0) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// A frame later, not inline: `resizeToContent` resizes the cell the\n\t\t\t\t// observed child sits in, and a child that stretches to its parent\n\t\t\t\t// grows with it. Resizing from inside the callback makes that the\n\t\t\t\t// classic ResizeObserver loop (\"undelivered notifications\"); deferred,\n\t\t\t\t// it is an ordinary second pass that settles.\n\t\t\t\trequestAnimationFrame(() => {\n\t\t\t\t\tif (!this.contentObserver || !this.grid || typeof this.grid.resizeToContent !== 'function') {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tfor (const cell of cells) {\n\t\t\t\t\t\tthis.grid.resizeToContent(cell)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tconst cells = this.$refs.gridContainer.querySelectorAll('.grid-stack-item[gs-size-to-content]')\n\t\t\tfor (const cell of cells) {\n\t\t\t\tconst measured = cell.querySelector('.grid-stack-item-content')?.firstElementChild\n\t\t\t\tif (measured) {\n\t\t\t\t\tthis.contentObserver.observe(measured)\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Drop the content observer, on unmount or before rebuilding it.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tdisconnectContentObserver() {\n\t\t\tif (this.contentObserver) {\n\t\t\t\tthis.contentObserver.disconnect()\n\t\t\t\tthis.contentObserver = null\n\t\t\t}\n\t\t},\n\n\t\tgridNode(id) {\n\t\t\tconst nodes = this.grid && this.grid.engine && this.grid.engine.nodes\n\t\t\tif (!Array.isArray(nodes)) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn nodes.find((n) => String(n.id) === String(id)) || null\n\t\t},\n\n\t\tsyncGridItems(newLayout) {\n\t\t\t// Add new items, and re-adopt items whose backing DOM element was\n\t\t\t// replaced. When a consumer's `itemKey` changes for an unchanged\n\t\t\t// `id` (e.g. a style edit — the documented use of `itemKey`), Vue\n\t\t\t// swaps the element. GridStack keeps tracking the old, now-detached\n\t\t\t// element, leaving the new one unmanaged (no positioning/sizing) —\n\t\t\t// so re-register it against the new element.\n\t\t\tfor (const item of newLayout) {\n\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\tconst el = this.$refs.gridContainer.querySelector(`[gs-id=\"${item.id}\"]`)\n\t\t\t\t\tif (!el) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tconst node = this.grid.engine.nodes.find((n) => String(n.id) === String(item.id))\n\t\t\t\t\tif (!node) {\n\t\t\t\t\t\tthis.grid.makeWidget(el)\n\t\t\t\t\t} else if (node.el === el) {\n\t\t\t\t\t\t// The layout MOVED an item the grid already tracks: the\n\t\t\t\t\t\t// page re-compacts its display layout when a widget's\n\t\t\t\t\t\t// condition hides it, and the item below was given a new\n\t\t\t\t\t\t// gridY that never reached GridStack, so a band stayed\n\t\t\t\t\t\t// where the hidden widget had been. A drag reports the\n\t\t\t\t\t\t// grid's own position back, which matches and does\n\t\t\t\t\t\t// nothing here.\n\t\t\t\t\t\tconst x = item.gridX ?? node.x\n\t\t\t\t\t\tconst y = item.gridY ?? node.y\n\t\t\t\t\t\tif (x !== node.x || y !== node.y) {\n\t\t\t\t\t\t\t// The layout said where it goes; GridStack's `change`\n\t\t\t\t\t\t\t// for it is not a user's arrangement and is not\n\t\t\t\t\t\t\t// reported back (it would write the display-only\n\t\t\t\t\t\t\t// compaction into the authored layout).\n\t\t\t\t\t\t\tthis.applyingLayout = true\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tthis.grid.update(el, { x, y })\n\t\t\t\t\t\t\t} finally {\n\t\t\t\t\t\t\t\tthis.applyingLayout = false\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Drop the stale node without touching its detached DOM,\n\t\t\t\t\t\t// then adopt the new element so GridStack sizes/places it.\n\t\t\t\t\t\tthis.grid.removeWidget(node.el, false, false)\n\t\t\t\t\t\tthis.grid.makeWidget(el)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\n\t\t\t// Remove items no longer in layout\n\t\t\tconst ids = newLayout.map((i) => String(i.id))\n\t\t\tconst toRemove = this.grid.engine.nodes.filter((n) => !ids.includes(String(n.id)))\n\t\t\tfor (const node of toRemove) {\n\t\t\t\tconst el = this.$refs.gridContainer.querySelector(`[gs-id=\"${node.id}\"]`)\n\t\t\t\tif (el) {\n\t\t\t\t\tthis.grid.removeWidget(el, false)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Elements were added or replaced, so the observer's are stale.\n\t\t\tthis.$nextTick(() => this.observeContentSizes())\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-dashboard-grid {\n\twidth: 100%;\n\tmin-height: 200px;\n}\n\n.grid-stack {\n\tbackground: transparent;\n}\n\n/* Screen-reader-only text: the keyboard key map and the live region. Kept in\n the layout (not `display: none`) so assistive tech still reads it. */\n.cn-dashboard-grid__sr-only {\n\tposition: absolute;\n\twidth: 1px;\n\theight: 1px;\n\tpadding: 0;\n\tmargin: -1px;\n\toverflow: hidden;\n\tclip-path: inset(50%);\n\twhite-space: nowrap;\n\tborder: 0;\n}\n\n/* Grid items become tab stops in edit mode — give them a visible focus ring\n that satisfies WCAG 2.1 SC 2.4.7 without relying on the widget's own\n styling. `outline-offset` keeps the ring outside the rounded card. */\n.grid-stack-item:focus-visible {\n\toutline: 2px solid var(--color-primary-element, #0082c9);\n\toutline-offset: 2px;\n\tborder-radius: var(--border-radius-container-large, 16px);\n}\n\n/* A size-to-content cell (`sizeToContent`) is measured by GridStack from the\n content's first child, and a widget wrapper there filled the cell\n (`height: 100%`), so the measure returned the cell's own height and the\n cell never shrank: \"Waiting for me\" stayed a tall card around one row. In\n such a cell the child takes its own height. A cell without the key keeps\n the filling child. */\n.cn-dashboard-grid__item--fit > :deep(.grid-stack-item-content > *) {\n\theight: auto;\n}\n\n:deep(.grid-stack-item-content) {\n\tbackground: var(--color-main-background);\n\tborder: none;\n\tbox-shadow: none;\n\toverflow: hidden;\n}\n\n:deep(.grid-stack-item-content:has(.cn-widget-wrapper--borderless)) {\n\tbackground: transparent;\n}\n\n:deep(.grid-stack-placeholder > .placeholder-content) {\n\tbackground: var(--color-primary-element-light);\n\tborder: 2px dashed var(--color-primary-element);\n\tborder-radius: var(--border-radius-large);\n}\n</style>\n"],"names":["t"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnDashboardGrid.vue3.js","sources":["../../../../src/components/CnDashboardGrid/CnDashboardGrid.vue?vue&type=template&id=9f6b1a22&lang.js"],"sourcesContent":["<!--\n CnDashboardGrid — GridStack-powered drag-and-drop dashboard grid.\n\n Renders widgets in a configurable grid layout. Supports drag, resize,\n and dynamic item addition/removal. Emits layout changes for persistence.\n\n Accessibility: every grid item is an ARIA `group` with an accessible name\n and — in edit mode — a tab stop, so the pointer-only GridStack drag/resize\n gestures have a full keyboard equivalent (WCAG 2.1 SC 2.1.1). Keyboard\n moves are routed through `GridStack.update()`, i.e. the exact same engine\n call drag/resize ends in, so collision handling, persistence and the\n `layout-change` emission are byte-identical between the two input modes.\n-->\n<template>\n\t<div ref=\"gridContainer\" class=\"cn-dashboard-grid\">\n\t\t<!--\n\t\t\tKeyboard instructions, referenced by every grid item through\n\t\t\t`aria-describedby`. Rendered once (not per item) so a screen\n\t\t\treader user hears the key map on the first item they land on\n\t\t\twithout it being duplicated N times in the accessibility tree.\n\t\t-->\n\t\t<p\n\t\t\tv-if=\"keyboardActive\"\n\t\t\t:id=\"keyboardHelpDomId\"\n\t\t\tclass=\"cn-dashboard-grid__sr-only\">\n\t\t\t{{ keyboardHelpText }}\n\t\t</p>\n\n\t\t<div class=\"grid-stack\">\n\t\t\t<div\n\t\t\t\tv-for=\"(item, index) in layout\"\n\t\t\t\t:key=\"resolveItemKey(item)\"\n\t\t\t\tclass=\"grid-stack-item\"\n\t\t\t\t:class=\"{ 'cn-dashboard-grid__item--fit': sizeToContentAttr(item) !== undefined }\"\n\t\t\t\trole=\"group\"\n\t\t\t\t:aria-label=\"resolveItemLabel(item, index)\"\n\t\t\t\t:aria-describedby=\"itemDescribedBy\"\n\t\t\t\t:tabindex=\"itemTabindex\"\n\t\t\t\t:gs-id=\"item.id\"\n\t\t\t\t:gs-x=\"item.gridX\"\n\t\t\t\t:gs-y=\"item.gridY\"\n\t\t\t\t:gs-w=\"item.gridWidth\"\n\t\t\t\t:gs-h=\"item.gridHeight\"\n\t\t\t\t:gs-min-w=\"minWidth\"\n\t\t\t\t:gs-min-h=\"minHeight\"\n\t\t\t\t:gs-size-to-content=\"sizeToContentAttr(item)\"\n\t\t\t\t@keydown=\"onItemKeydown($event, item)\">\n\t\t\t\t<div class=\"grid-stack-item-content\">\n\t\t\t\t\t<slot name=\"widget\" :item=\"item\">\n\t\t\t\t\t\t<!-- Default: render nothing; CnDashboardPage provides content -->\n\t\t\t\t\t</slot>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!--\n\t\t\tPolite live region for keyboard move/resize feedback. A sighted\n\t\t\tuser sees the widget slide; a screen-reader user needs the new\n\t\t\tcoordinates spoken, otherwise the arrow keys are silent.\n\t\t-->\n\t\t<p\n\t\t\tclass=\"cn-dashboard-grid__sr-only\"\n\t\t\trole=\"status\"\n\t\t\taria-live=\"polite\"\n\t\t\taria-atomic=\"true\">\n\t\t\t{{ announcement }}\n\t\t</p>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { GridStack } from 'gridstack'\n// `gridstack` is a peerDependency (see package.json), not bundled — this\n// component's JS and the consumer's GridStack stylesheet MUST come from the\n// exact same installed copy, because GridStack's JS writes layout state\n// (e.g. the `--gs-column-width` CSS custom property) that only its OWN\n// matching CSS reads. Baking a copy of `gridstack/dist/gridstack.min.css`\n// into this library's build (as a previous version did) freezes it at\n// nc-vue's build-time version, which can silently drift from whatever the\n// consumer's peer-installed copy actually is — the exact mismatch that once\n// made every grid item render at 0px width. So nc-vue ships NO GridStack\n// CSS of its own: the consumer MUST install `gridstack` themselves and\n// import `gridstack/dist/gridstack.min.css` (or their preferred GridStack\n// stylesheet) from that same copy.\n\n/**\n * Monotonic counter backing the per-instance DOM ids (the shared\n * `aria-describedby` help text). Module-level so two grids on one page\n * never collide — the same pattern CnCommandPalette uses.\n */\nlet domIdCounter = 0\n\n/**\n * Pixel offset applied to the grid item's top-left corner when synthesising\n * the `contextmenu` coordinates for a keyboard activation. Nudging the anchor\n * inside the item keeps consumer menus (which position at `clientX/clientY`)\n * visually attached to the widget instead of straddling its border.\n */\nconst MENU_ANCHOR_OFFSET = 8\n\n/**\n * CnDashboardGrid — Low-level grid layout engine powered by GridStack.\n *\n * Manages the drag-and-drop grid, syncs positions, and emits layout\n * changes. Does NOT handle widget rendering — that's done by the parent\n * via the `#widget` scoped slot.\n *\n * ```vue\n * <CnDashboardGrid\n * :layout=\"placements\"\n * :editable=\"isEditing\"\n * @layout-change=\"onLayoutChange\">\n * <template #widget=\"{ item }\">\n * <MyWidget :config=\"item\" />\n * </template>\n * </CnDashboardGrid>\n * ```\n *\n * ### Keyboard operation\n *\n * With `keyboardRepositioning` on (the default) and `editable` true, each\n * grid item is a tab stop. With an item focused:\n *\n * | Key | Action |\n * | --- | ------ |\n * | `ArrowLeft` / `ArrowRight` | move one column left / right |\n * | `ArrowUp` / `ArrowDown` | move one row up / down |\n * | `Shift` + `ArrowLeft` / `ArrowRight` | shrink / grow width by one column |\n * | `Shift` + `ArrowUp` / `ArrowDown` | shrink / grow height by one row |\n * | `Home` / `End` | jump to the first / last column of the current row |\n * | `Enter` / `Space` | activate the item — see `item-activate` |\n *\n * Keys are only honoured when the grid item itself holds focus, so\n * interactive content rendered inside the `#widget` slot keeps its own\n * key handling untouched.\n */\nexport default {\n\tname: 'CnDashboardGrid',\n\n\tprops: {\n\t\t/** Array of layout items: { id, gridX, gridY, gridWidth, gridHeight, ...extra } */\n\t\tlayout: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Whether drag and resize are enabled */\n\t\teditable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Number of grid columns */\n\t\tcolumns: {\n\t\t\ttype: Number,\n\t\t\tdefault: 12,\n\t\t},\n\n\t\t/** Cell height in pixels */\n\t\tcellHeight: {\n\t\t\ttype: Number,\n\t\t\tdefault: 80,\n\t\t},\n\n\t\t/** Grid margin in pixels */\n\t\tmargin: {\n\t\t\ttype: Number,\n\t\t\tdefault: 12,\n\t\t},\n\n\t\t/** Minimum widget width in grid units */\n\t\tminWidth: {\n\t\t\ttype: Number,\n\t\t\tdefault: 2,\n\t\t},\n\n\t\t/** Minimum widget height in grid units */\n\t\tminHeight: {\n\t\t\ttype: Number,\n\t\t\tdefault: 2,\n\t\t},\n\n\t\t/**\n\t\t * GridStack v12 responsive `columnOpts` bag (breakpoints + reflow\n\t\t * layout). When set, the grid reflows column count across screen sizes.\n\t\t * Build it with `getDashboardColumnOpts()`. Default `null` = fixed\n\t\t * `columns`, no responsive reflow (backwards-compatible).\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tcolumnOpts: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * When set, `cellHeight` is mirrored into this CSS custom property on\n\t\t * the document root at init (e.g. `'--app-cell-height'`), so app CSS can\n\t\t * align to the grid geometry. Default `null` = no CSS var written.\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\tcellHeightCssVar: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Optional `(item) => string|number` to derive each item's render key.\n\t\t * Use it to force a re-render when an item changes in a way its `id`\n\t\t * doesn't capture (e.g. style edits — return `${item.id}:${item.updatedAt}`).\n\t\t * Default `null` = key on `item.id`.\n\t\t *\n\t\t * @type {((item: object) => (string|number))|null}\n\t\t */\n\t\titemKey: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Whether grid items are keyboard-operable: focusable in edit mode and\n\t\t * repositionable/resizable with the arrow keys (WCAG 2.1 SC 2.1.1 —\n\t\t * the keyboard equivalent of the pointer-only GridStack drag). Turning\n\t\t * it off leaves the items non-focusable and drag-only. Default `true`.\n\t\t */\n\t\tkeyboardRepositioning: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Optional `(item, index) => string` returning the accessible name for\n\t\t * a grid item. Use it when the layout items don't carry a human-readable\n\t\t * field (the built-in fallback walks `title` → `name` → `label` →\n\t\t * `widgetTitle` → `widgetId`, then a positional \"Widget N\"). The returned\n\t\t * string is used verbatim — no coordinates are appended.\n\t\t *\n\t\t * @type {((item: object, index: number) => string)|null}\n\t\t */\n\t\titemLabel: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Whether a keyboard activation (`Enter` / `Space` on a focused grid\n\t\t * item) also dispatches a bubbling `contextmenu` event from inside the\n\t\t * item, so consumers that open a per-widget menu on right-click get the\n\t\t * keyboard path for free — mirroring what browsers do for the `Menu`\n\t\t * key. Set `false` to rely purely on the `item-activate` event.\n\t\t * Default `true`.\n\t\t */\n\t\tactivateOpensContextMenu: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * GridStack's `float`. True (the default) keeps every item on the row\n\t\t * it was placed on, with empty rows above it if need be. False packs\n\t\t * items upward, so a size-to-content cell that shrinks, or a widget\n\t\t * that is not shown, closes its gap instead of leaving a band.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-dashboard-closes-the-gap-a-shrinking-or-hidden-widget-leaves\n\t\t */\n\t\tfloat: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * Fired whenever the grid geometry changed — by drag, by resize, or by\n\t\t * a keyboard nudge. Payload is the full `layout` array with the moved\n\t\t * items' `gridX`/`gridY`/`gridWidth`/`gridHeight` updated.\n\t\t */\n\t\t'layout-change',\n\t\t/**\n\t\t * Fired when the user activates a focused grid item with `Enter` or\n\t\t * `Space`. Payload `{ item, element, clientX, clientY }` — the layout\n\t\t * item, its DOM element, and viewport coordinates anchored to the\n\t\t * item's top-left corner so a consumer menu can position itself the\n\t\t * same way it does for a right-click.\n\t\t */\n\t\t'item-activate',\n\t],\n\n\tdata() {\n\t\treturn {\n\t\t\tgrid: null,\n\t\t\tcontentObserver: null,\n\t\t\t/** True while the grid moves an item to where the layout put it. */\n\t\t\tapplyingLayout: false,\n\t\t\tdomId: `cn-dashboard-grid-${++domIdCounter}`,\n\t\t\t/** Text currently held in the polite live region. */\n\t\t\tannouncement: '',\n\t\t\t/**\n\t\t\t * Whether a `columnOpts` breakpoint is currently in force. Mirrored\n\t\t\t * from the engine by `syncInteractivity` because `isReflowed()` reads\n\t\t\t * `grid.getColumn()` imperatively, and a computed cannot re-evaluate\n\t\t\t * on that.\n\t\t\t */\n\t\t\treflowed: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * @return {boolean} Whether the keyboard lane is live — items are\n\t\t * focusable and the arrow keys reposition. Requires the opt-in prop,\n\t\t * edit mode (a read-only dashboard has nothing to move, so adding N tab\n\t\t * stops there would be pure noise), and a grid that is not reflowed,\n\t\t * which is where a move cannot be kept and so must not be offered.\n\t\t */\n\t\tkeyboardActive() {\n\t\t\treturn this.keyboardRepositioning && this.editable && !this.reflowed\n\t\t},\n\n\t\t/**\n\t\t * @return {number|null} `0` when grid items should be tab stops,\n\t\t * otherwise `null` so the attribute is omitted entirely.\n\t\t */\n\t\titemTabindex() {\n\t\t\treturn this.keyboardActive ? 0 : null\n\t\t},\n\n\t\t/**\n\t\t * @return {string} DOM id of the shared keyboard-instructions element.\n\t\t */\n\t\tkeyboardHelpDomId() {\n\t\t\treturn `${this.domId}-keyboard-help`\n\t\t},\n\n\t\t/**\n\t\t * @return {string|null} `aria-describedby` target for each grid item —\n\t\t * the instructions element while the keyboard lane is live, `null`\n\t\t * otherwise (a dangling id reference is an axe violation).\n\t\t */\n\t\titemDescribedBy() {\n\t\t\treturn this.keyboardActive ? this.keyboardHelpDomId : null\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The spoken key map for the keyboard lane.\n\t\t */\n\t\tkeyboardHelpText() {\n\t\t\treturn t(\n\t\t\t\t'nextcloud-vue',\n\t\t\t\t'Use the arrow keys to move this widget one cell. Hold Shift and use the arrow keys to resize it. Press Home or End to jump to the first or last column. Press Enter to open the widget menu.',\n\t\t\t)\n\t\t},\n\n\t\t/**\n\t\t * @return {number} The column count currently in force. With responsive\n\t\t * `columnOpts` GridStack reflows to fewer columns on small screens, so\n\t\t * clamping must read the live value rather than the `columns` prop.\n\t\t */\n\t\teffectiveColumns() {\n\t\t\tif (this.grid && typeof this.grid.getColumn === 'function') {\n\t\t\t\tconst live = this.grid.getColumn()\n\t\t\t\tif (Number.isFinite(live) && live > 0) {\n\t\t\t\t\treturn live\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn this.columns\n\t\t},\n\t},\n\n\twatch: {\n\t\teditable() {\n\t\t\tthis.syncInteractivity()\n\t\t},\n\n\t\tlayout: {\n\t\t\tdeep: true,\n\t\t\thandler(newLayout) {\n\t\t\t\tif (this.grid) {\n\t\t\t\t\tthis.syncGridItems(newLayout)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.initGrid()\n\t\tthis.$nextTick(() => this.observeContentSizes())\n\t},\n\n\tbeforeUnmount() {\n\t\tthis.disconnectContentObserver()\n\t\tif (this.grid) {\n\t\t\tthis.grid.destroy(false)\n\t\t}\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Derive the v-for render key for a layout item, honouring the optional\n\t\t * `itemKey` prop and falling back to `item.id`.\n\t\t *\n\t\t * @param {object} item the layout item.\n\t\t * @return {string|number} the render key.\n\t\t */\n\t\tresolveItemKey(item) {\n\t\t\treturn this.itemKey ? this.itemKey(item) : item.id\n\t\t},\n\n\t\t/**\n\t\t * The `gs-size-to-content` attribute for a layout item, or `undefined` to\n\t\t * leave the item on its authored `gridHeight`.\n\t\t *\n\t\t * A widget that decides AT RUNTIME it has nothing to show — a banner whose\n\t\t * condition is false, a list that came back empty — still costs its whole\n\t\t * authored row, because the row is reserved from the layout before the\n\t\t * component renders and the engine positions items absolutely, so no CSS\n\t\t * can reclaim it. `sizeToContent` hands the row's height to the content, so\n\t\t * an empty widget collapses and the grid closes up behind it.\n\t\t *\n\t\t * Opt-in per item: it makes `gridHeight` advisory for that widget, which is\n\t\t * wrong for anything with a deliberate size (a map, a chart) and right for\n\t\t * anything whose height is whatever it turned out to have.\n\t\t *\n\t\t * @param {object} item the layout item.\n\t\t * @return {string|undefined} `'true'`, a row minimum as a string, or undefined.\n\t\t */\n\t\tsizeToContentAttr(item) {\n\t\t\tconst v = item && item.sizeToContent\n\t\t\tif (v === true) {\n\t\t\t\treturn 'true'\n\t\t\t}\n\t\t\t// A number is GridStack's \"size to content, but never below N rows\".\n\t\t\tif (Number.isFinite(v) && v > 0) {\n\t\t\t\treturn String(v)\n\t\t\t}\n\t\t\treturn undefined\n\t\t},\n\n\t\t/**\n\t\t * Build the accessible name for a grid item. A custom `itemLabel` wins\n\t\t * outright; otherwise the first human-readable field on the item is used\n\t\t * and, while the keyboard lane is live, the current grid coordinates are\n\t\t * appended — without them a screen-reader user tabbing through an edit-mode\n\t\t * dashboard has no way to tell where anything sits.\n\t\t *\n\t\t * @param {object} item the layout item.\n\t\t * @param {number} index the item's index in `layout`, used for the\n\t\t * last-resort positional name.\n\t\t * @return {string} the accessible name.\n\t\t */\n\t\tresolveItemLabel(item, index) {\n\t\t\tif (this.itemLabel) {\n\t\t\t\treturn String(this.itemLabel(item, index) ?? '')\n\t\t\t}\n\n\t\t\tconst base = item.title\n\t\t\t\t|| item.name\n\t\t\t\t|| item.label\n\t\t\t\t|| item.widgetTitle\n\t\t\t\t|| item.widgetId\n\t\t\t\t|| t('nextcloud-vue', 'Widget {number}', { number: index + 1 })\n\n\t\t\tif (!this.keyboardActive) {\n\t\t\t\treturn String(base)\n\t\t\t}\n\n\t\t\t// Report the rectangle exactly as the reactive `layout` prop states\n\t\t\t// it — unclamped, so the spoken coordinates always match the\n\t\t\t// rendered `gs-*` attributes — and read from the prop (not the\n\t\t\t// GridStack node) so the name re-renders when a move round-trips\n\t\t\t// back through `layout-change`; engine-node mutations are invisible\n\t\t\t// to Vue's reactivity.\n\t\t\tconst rect = {\n\t\t\t\tx: Number.isFinite(item.gridX) ? item.gridX : 0,\n\t\t\t\ty: Number.isFinite(item.gridY) ? item.gridY : 0,\n\t\t\t\tw: Number.isFinite(item.gridWidth) ? item.gridWidth : this.minWidth,\n\t\t\t\th: Number.isFinite(item.gridHeight) ? item.gridHeight : this.minHeight,\n\t\t\t}\n\t\t\treturn t(\n\t\t\t\t'nextcloud-vue',\n\t\t\t\t'{label}, column {column} of {columns}, row {row}, {width} columns wide, {height} rows tall',\n\t\t\t\t{\n\t\t\t\t\tlabel: String(base),\n\t\t\t\t\tcolumn: rect.x + 1,\n\t\t\t\t\tcolumns: this.effectiveColumns,\n\t\t\t\t\trow: rect.y + 1,\n\t\t\t\t\twidth: rect.w,\n\t\t\t\t\theight: rect.h,\n\t\t\t\t},\n\t\t\t)\n\t\t},\n\n\t\tinitGrid() {\n\t\t\tif (this.cellHeightCssVar && typeof document !== 'undefined' && document.documentElement) {\n\t\t\t\tdocument.documentElement.style.setProperty(this.cellHeightCssVar, `${this.cellHeight}px`)\n\t\t\t}\n\t\t\tconst el = this.$refs.gridContainer.querySelector('.grid-stack')\n\t\t\tthis.grid = GridStack.init({\n\t\t\t\tcolumn: this.columns,\n\t\t\t\tcellHeight: this.cellHeight,\n\t\t\t\tmargin: this.margin,\n\t\t\t\tfloat: this.float,\n\t\t\t\tanimate: true,\n\t\t\t\tdisableDrag: !this.editable,\n\t\t\t\tdisableResize: !this.editable,\n\t\t\t\tremovable: false,\n\t\t\t\t...(this.columnOpts ? { columnOpts: this.columnOpts } : {}),\n\t\t\t}, el)\n\n\t\t\tthis.grid.on('change', (_event, items) => {\n\t\t\t\tthis.handleGridChange(items)\n\t\t\t})\n\n\t\t\t// A grid that mounts already narrow is reflowed before anyone touches\n\t\t\t// it, so the handles have to be judged here and not only on a later\n\t\t\t// change.\n\t\t\tthis.syncInteractivity()\n\t\t},\n\n\t\t/**\n\t\t * Keep both editing lanes in step with whether an edit could actually be\n\t\t * kept: the pointer's handles, and — through `reflowed` feeding\n\t\t * `keyboardActive` — the tab stops, the help text and the arrow keys.\n\t\t *\n\t\t * A reflowed grid drops every `change` it receives, for the reason\n\t\t * `handleGridChange` gives. Leaving either lane live there offers an edit\n\t\t * that silently snaps back, which is the same class of surprise as\n\t\t * persisting the reflow — pointed the other way, and harder to explain\n\t\t * because the author did everything right. It is worse on the keyboard,\n\t\t * where the move is also announced as done. The affordance has to say what\n\t\t * the grid will do at this width, not what edit mode does at the authored\n\t\t * one.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tsyncInteractivity() {\n\t\t\t// Ahead of the engine guard: the keyboard lane reads this whether or\n\t\t\t// not the engine can be enabled.\n\t\t\tthis.reflowed = this.isReflowed()\n\t\t\tif (!this.grid || typeof this.grid.enable !== 'function') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.editable && !this.reflowed) {\n\t\t\t\tthis.grid.enable()\n\t\t\t} else {\n\t\t\t\tthis.grid.disable()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Whether the grid is currently rescaled to a column count other than the\n\t\t * authored one, i.e. a `columnOpts` breakpoint is in force.\n\t\t *\n\t\t * The authored count is `columnOpts.columnMax` when there is one, not the\n\t\t * `columns` prop: GridStack widens to `columnMax` above the top breakpoint,\n\t\t * so a consumer whose two disagree is at its widest, not reflowed.\n\t\t *\n\t\t * @return {boolean} True while a responsive reflow is active.\n\t\t */\n\t\tisReflowed() {\n\t\t\tif (!this.grid || typeof this.grid.getColumn !== 'function') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst authored = Number.isFinite(this.columnOpts?.columnMax) ? this.columnOpts.columnMax : this.columns\n\t\t\tconst live = this.grid.getColumn()\n\t\t\treturn Number.isFinite(live) && live > 0 && live !== authored\n\t\t},\n\n\t\thandleGridChange(items) {\n\t\t\t// The rescale that crossed a breakpoint arrives here as a `change`, so\n\t\t\t// this is also where the handles are re-judged in both directions:\n\t\t\t// switched off as the grid narrows, and back on as it widens.\n\t\t\tthis.syncInteractivity()\n\n\t\t\tif (this.applyingLayout) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// GridStack fires `change` for its own responsive rescale too, and at a\n\t\t\t// 12 → 1 breakpoint every item reads `gridX: 0, gridWidth: 1`. Emitting\n\t\t\t// that has the host persist the reflow AS the authored layout, which\n\t\t\t// widening back does not undo. A move made while reflowed is the same\n\t\t\t// rescaled geometry, so it is dropped for the same reason.\n\t\t\tif (this.isReflowed()) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!items || items.length === 0) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst updated = this.layout.map((item) => {\n\t\t\t\tconst gridItem = items.find((gi) => String(gi.id) === String(item.id))\n\t\t\t\tif (gridItem) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\t...item,\n\t\t\t\t\t\tgridX: gridItem.x,\n\t\t\t\t\t\tgridY: gridItem.y,\n\t\t\t\t\t\tgridWidth: gridItem.w,\n\t\t\t\t\t\tgridHeight: gridItem.h,\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn item\n\t\t\t})\n\n\t\t\tthis.$emit('layout-change', updated)\n\t\t},\n\n\t\t/**\n\t\t * Keydown handler bound to every grid item.\n\t\t *\n\t\t * Only reacts when the grid item element itself is the event target:\n\t\t * a button, input or menu rendered inside the `#widget` slot must keep\n\t\t * its own arrow/Enter semantics, and a grid that swallowed them would\n\t\t * be a worse a11y regression than the one this handler fixes.\n\t\t *\n\t\t * @param {KeyboardEvent} event the keydown event.\n\t\t * @param {object} item the layout item the key was pressed on.\n\t\t * @return {void}\n\t\t */\n\t\tonItemKeydown(event, item) {\n\t\t\tif (!this.keyboardRepositioning) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (event.target !== event.currentTarget) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (event.altKey || event.ctrlKey || event.metaKey) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (event.key === 'Enter' || event.key === ' ' || event.key === 'Spacebar') {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tthis.activateItem(item, event.currentTarget)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Repositioning only, and only where it can be kept — an item that\n\t\t\t// still holds focus from before a reflow keeps its Enter, but its\n\t\t\t// arrow keys stop moving something that would snap back.\n\t\t\tif (!this.keyboardActive) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst handled = this.applyKey(event.key, event.shiftKey, item)\n\t\t\tif (handled) {\n\t\t\t\t// Arrows would otherwise scroll the dashboard out from under\n\t\t\t\t// the widget the user is moving.\n\t\t\t\tevent.preventDefault()\n\t\t\t\tevent.stopPropagation()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Translate one repositioning key into a geometry change.\n\t\t *\n\t\t * @param {string} key the `KeyboardEvent.key` value.\n\t\t * @param {boolean} shift whether Shift was held (resize instead of move).\n\t\t * @param {object} item the layout item being operated on.\n\t\t * @return {boolean} `true` when the key belongs to the grid and the\n\t\t * default action must be suppressed.\n\t\t */\n\t\tapplyKey(key, shift, item) {\n\t\t\tconst rect = this.currentRect(item)\n\n\t\t\tswitch (key) {\n\t\t\t\tcase 'ArrowLeft':\n\t\t\t\t\treturn shift\n\t\t\t\t\t\t? this.applyGeometry(item, { w: rect.w - 1 }, 'resize')\n\t\t\t\t\t\t: this.applyGeometry(item, { x: rect.x - 1 }, 'move')\n\t\t\t\tcase 'ArrowRight':\n\t\t\t\t\treturn shift\n\t\t\t\t\t\t? this.applyGeometry(item, { w: rect.w + 1 }, 'resize')\n\t\t\t\t\t\t: this.applyGeometry(item, { x: rect.x + 1 }, 'move')\n\t\t\t\tcase 'ArrowUp':\n\t\t\t\t\treturn shift\n\t\t\t\t\t\t? this.applyGeometry(item, { h: rect.h - 1 }, 'resize')\n\t\t\t\t\t\t: this.applyGeometry(item, { y: rect.y - 1 }, 'move')\n\t\t\t\tcase 'ArrowDown':\n\t\t\t\t\treturn shift\n\t\t\t\t\t\t? this.applyGeometry(item, { h: rect.h + 1 }, 'resize')\n\t\t\t\t\t\t: this.applyGeometry(item, { y: rect.y + 1 }, 'move')\n\t\t\t\tcase 'Home':\n\t\t\t\t\treturn this.applyGeometry(item, { x: 0 }, 'move')\n\t\t\t\tcase 'End':\n\t\t\t\t\treturn this.applyGeometry(item, { x: this.effectiveColumns - rect.w }, 'move')\n\t\t\t\tdefault:\n\t\t\t\t\treturn false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Read an item's live rectangle. The GridStack node is preferred over\n\t\t * the `layout` prop because a drag that has not yet round-tripped\n\t\t * through the consumer's persistence leaves the prop momentarily stale,\n\t\t * and a keyboard nudge must continue from where the widget actually is.\n\t\t *\n\t\t * @param {object} item the layout item.\n\t\t * @return {{x: number, y: number, w: number, h: number}} the rectangle\n\t\t * in grid units.\n\t\t */\n\t\tcurrentRect(item) {\n\t\t\tconst node = this.gridNode(item.id)\n\t\t\tif (node && Number.isFinite(node.x) && Number.isFinite(node.y)) {\n\t\t\t\treturn {\n\t\t\t\t\tx: node.x,\n\t\t\t\t\ty: node.y,\n\t\t\t\t\tw: Number.isFinite(node.w) ? node.w : this.minWidth,\n\t\t\t\t\th: Number.isFinite(node.h) ? node.h : this.minHeight,\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tx: Number.isFinite(item.gridX) ? item.gridX : 0,\n\t\t\t\ty: Number.isFinite(item.gridY) ? item.gridY : 0,\n\t\t\t\tw: Number.isFinite(item.gridWidth) ? item.gridWidth : this.minWidth,\n\t\t\t\th: Number.isFinite(item.gridHeight) ? item.gridHeight : this.minHeight,\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Clamp a candidate rectangle to the grid's bounds and the configured\n\t\t * minimum widget size, so a held-down arrow key parks the widget at the\n\t\t * edge instead of pushing it into negative coordinates.\n\t\t *\n\t\t * @param {{x: number, y: number, w: number, h: number}} rect the candidate.\n\t\t * @return {{x: number, y: number, w: number, h: number}} the clamped rectangle.\n\t\t */\n\t\tclampRect(rect) {\n\t\t\tconst cols = this.effectiveColumns\n\t\t\tconst w = Math.min(Math.max(rect.w, this.minWidth), cols)\n\t\t\tconst h = Math.max(rect.h, this.minHeight)\n\t\t\treturn {\n\t\t\t\tx: Math.min(Math.max(rect.x, 0), Math.max(cols - w, 0)),\n\t\t\t\ty: Math.max(rect.y, 0),\n\t\t\t\tw,\n\t\t\t\th,\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Apply a partial geometry change to an item.\n\t\t *\n\t\t * The change is pushed through `GridStack.update()` — the very call the\n\t\t * drag/resize handlers end in — so collision resolution, the `change`\n\t\t * event and therefore the `layout-change` emission and the consumer's\n\t\t * persistence are identical for keyboard and pointer. There is\n\t\t * deliberately no second, direct-to-`layout` update path.\n\t\t *\n\t\t * @param {object} item the layout item to move or resize.\n\t\t * @param {{x?: number, y?: number, w?: number, h?: number}} patch the\n\t\t * requested change, merged over the item's current rectangle.\n\t\t * @param {'move'|'resize'} kind which announcement to speak.\n\t\t * @return {boolean} `true` when the key was consumed — including when\n\t\t * the widget was already at the edge, because the arrow still belongs\n\t\t * to the grid and must not scroll the page.\n\t\t */\n\t\tapplyGeometry(item, patch, kind) {\n\t\t\tconst el = this.itemElement(item.id)\n\t\t\tif (!el || !this.grid || typeof this.grid.update !== 'function') {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tconst current = this.currentRect(item)\n\t\t\tconst next = this.clampRect({ ...current, ...patch })\n\n\t\t\tif (next.x === current.x && next.y === current.y\n\t\t\t\t&& next.w === current.w && next.h === current.h) {\n\t\t\t\tthis.announce(t('nextcloud-vue', '{label} cannot move further in that direction', {\n\t\t\t\t\tlabel: this.announcementLabel(item),\n\t\t\t\t}))\n\t\t\t\treturn true\n\t\t\t}\n\n\t\t\tthis.grid.update(el, next)\n\n\t\t\tthis.announce(kind === 'resize'\n\t\t\t\t? t('nextcloud-vue', '{label} resized to {width} columns by {height} rows', {\n\t\t\t\t\t\tlabel: this.announcementLabel(item),\n\t\t\t\t\t\twidth: next.w,\n\t\t\t\t\t\theight: next.h,\n\t\t\t\t\t})\n\t\t\t\t: t('nextcloud-vue', '{label} moved to column {column}, row {row}', {\n\t\t\t\t\t\tlabel: this.announcementLabel(item),\n\t\t\t\t\t\tcolumn: next.x + 1,\n\t\t\t\t\t\trow: next.y + 1,\n\t\t\t\t\t}))\n\n\t\t\treturn true\n\t\t},\n\n\t\t/**\n\t\t * The short, coordinate-free name used inside live-region sentences —\n\t\t * the full `aria-label` already carries the coordinates and repeating\n\t\t * them would make every announcement unlistenable.\n\t\t *\n\t\t * @param {object} item the layout item.\n\t\t * @return {string} the short name.\n\t\t */\n\t\tannouncementLabel(item) {\n\t\t\treturn String(item.title\n\t\t\t\t|| item.name\n\t\t\t\t|| item.label\n\t\t\t\t|| item.widgetTitle\n\t\t\t\t|| item.widgetId\n\t\t\t\t|| t('nextcloud-vue', 'Widget'))\n\t\t},\n\n\t\t/**\n\t\t * Push text into the polite live region. The region is cleared first so\n\t\t * two identical consecutive announcements (e.g. two failed nudges at the\n\t\t * grid edge) are both spoken — assistive tech ignores a re-render that\n\t\t * leaves the text unchanged.\n\t\t *\n\t\t * @param {string} text the message to announce.\n\t\t * @return {void}\n\t\t */\n\t\tannounce(text) {\n\t\t\tthis.announcement = ''\n\t\t\tthis.$nextTick(() => {\n\t\t\t\tthis.announcement = text\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Handle `Enter` / `Space` on a focused grid item: tell the consumer via\n\t\t * `item-activate`, and — unless opted out — synthesise the `contextmenu`\n\t\t * event a right-click would have produced, anchored to the item, so\n\t\t * pointer-only widget menus become keyboard-reachable without the\n\t\t * consumer wiring anything new. The synthetic event is dispatched from\n\t\t * inside the item's content so it bubbles through the slot's own root\n\t\t * element (where such handlers live) and on up.\n\t\t *\n\t\t * @param {object} item the layout item being activated.\n\t\t * @param {HTMLElement} el the grid item element.\n\t\t * @return {void}\n\t\t */\n\t\tactivateItem(item, el) {\n\t\t\tconst rect = typeof el.getBoundingClientRect === 'function'\n\t\t\t\t? el.getBoundingClientRect()\n\t\t\t\t: { left: 0, top: 0 }\n\t\t\tconst clientX = Math.round((rect.left || 0) + MENU_ANCHOR_OFFSET)\n\t\t\tconst clientY = Math.round((rect.top || 0) + MENU_ANCHOR_OFFSET)\n\n\t\t\tthis.$emit('item-activate', { item, element: el, clientX, clientY })\n\n\t\t\tif (this.activateOpensContextMenu) {\n\t\t\t\tthis.dispatchContextMenu(el, clientX, clientY)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Dispatch a bubbling, cancelable `contextmenu` MouseEvent from inside a\n\t\t * grid item.\n\t\t *\n\t\t * @param {HTMLElement} el the grid item element.\n\t\t * @param {number} clientX viewport x for the menu anchor.\n\t\t * @param {number} clientY viewport y for the menu anchor.\n\t\t * @return {void}\n\t\t */\n\t\tdispatchContextMenu(el, clientX, clientY) {\n\t\t\tif (typeof MouseEvent !== 'function') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst content = el.querySelector('.grid-stack-item-content')\n\t\t\tconst target = (content && content.firstElementChild) || content || el\n\t\t\ttarget.dispatchEvent(new MouseEvent('contextmenu', {\n\t\t\t\tbubbles: true,\n\t\t\t\tcancelable: true,\n\t\t\t\tclientX,\n\t\t\t\tclientY,\n\t\t\t\tbutton: 2,\n\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Look up a grid item's DOM element by its layout id.\n\t\t *\n\t\t * @param {string|number} id the layout item id.\n\t\t * @return {HTMLElement|null} the element, or `null` when not rendered.\n\t\t */\n\t\titemElement(id) {\n\t\t\tconst container = this.$refs.gridContainer\n\t\t\tif (!container) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn container.querySelector(`[gs-id=\"${id}\"]`)\n\t\t},\n\n\t\t/**\n\t\t * Look up the GridStack engine node backing a layout item.\n\t\t *\n\t\t * @param {string|number} id the layout item id.\n\t\t * @return {object|null} the node, or `null` when GridStack isn't tracking it.\n\t\t */\n\t\t/**\n\t\t * Re-measure a `sizeToContent` item when its content changes height.\n\t\t * GridStack watches only the grid element, so a widget that loaded its data\n\t\t * in `mounted()` keeps the height its empty state had.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tobserveContentSizes() {\n\t\t\tthis.disconnectContentObserver()\n\t\t\tif (typeof ResizeObserver === 'undefined' || !this.$refs.gridContainer) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.contentObserver = new ResizeObserver((entries) => {\n\t\t\t\tconst cells = new Set()\n\t\t\t\tfor (const entry of entries) {\n\t\t\t\t\tconst cell = entry.target.closest('.grid-stack-item')\n\t\t\t\t\tif (cell) {\n\t\t\t\t\t\tcells.add(cell)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (cells.size === 0) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// A frame later, not inline: `resizeToContent` resizes the cell the\n\t\t\t\t// observed child sits in, and a child that stretches to its parent\n\t\t\t\t// grows with it. Resizing from inside the callback makes that the\n\t\t\t\t// classic ResizeObserver loop (\"undelivered notifications\"); deferred,\n\t\t\t\t// it is an ordinary second pass that settles.\n\t\t\t\trequestAnimationFrame(() => {\n\t\t\t\t\tif (!this.contentObserver || !this.grid || typeof this.grid.resizeToContent !== 'function') {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tfor (const cell of cells) {\n\t\t\t\t\t\tthis.grid.resizeToContent(cell)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tconst cells = this.$refs.gridContainer.querySelectorAll('.grid-stack-item[gs-size-to-content]')\n\t\t\tfor (const cell of cells) {\n\t\t\t\tconst measured = cell.querySelector('.grid-stack-item-content')?.firstElementChild\n\t\t\t\tif (measured) {\n\t\t\t\t\tthis.contentObserver.observe(measured)\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Drop the content observer, on unmount or before rebuilding it.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tdisconnectContentObserver() {\n\t\t\tif (this.contentObserver) {\n\t\t\t\tthis.contentObserver.disconnect()\n\t\t\t\tthis.contentObserver = null\n\t\t\t}\n\t\t},\n\n\t\tgridNode(id) {\n\t\t\tconst nodes = this.grid && this.grid.engine && this.grid.engine.nodes\n\t\t\tif (!Array.isArray(nodes)) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn nodes.find((n) => String(n.id) === String(id)) || null\n\t\t},\n\n\t\tsyncGridItems(newLayout) {\n\t\t\t// Add new items, and re-adopt items whose backing DOM element was\n\t\t\t// replaced. When a consumer's `itemKey` changes for an unchanged\n\t\t\t// `id` (e.g. a style edit — the documented use of `itemKey`), Vue\n\t\t\t// swaps the element. GridStack keeps tracking the old, now-detached\n\t\t\t// element, leaving the new one unmanaged (no positioning/sizing) —\n\t\t\t// so re-register it against the new element.\n\t\t\tfor (const item of newLayout) {\n\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\tconst el = this.$refs.gridContainer.querySelector(`[gs-id=\"${item.id}\"]`)\n\t\t\t\t\tif (!el) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tconst node = this.grid.engine.nodes.find((n) => String(n.id) === String(item.id))\n\t\t\t\t\tif (!node) {\n\t\t\t\t\t\tthis.grid.makeWidget(el)\n\t\t\t\t\t} else if (node.el === el) {\n\t\t\t\t\t\t// The layout MOVED an item the grid already tracks: the\n\t\t\t\t\t\t// page re-compacts its display layout when a widget's\n\t\t\t\t\t\t// condition hides it, and the item below was given a new\n\t\t\t\t\t\t// gridY that never reached GridStack, so a band stayed\n\t\t\t\t\t\t// where the hidden widget had been. A drag reports the\n\t\t\t\t\t\t// grid's own position back, which matches and does\n\t\t\t\t\t\t// nothing here.\n\t\t\t\t\t\tconst x = item.gridX ?? node.x\n\t\t\t\t\t\tconst y = item.gridY ?? node.y\n\t\t\t\t\t\tif (x !== node.x || y !== node.y) {\n\t\t\t\t\t\t\t// The layout said where it goes; GridStack's `change`\n\t\t\t\t\t\t\t// for it is not a user's arrangement and is not\n\t\t\t\t\t\t\t// reported back (it would write the display-only\n\t\t\t\t\t\t\t// compaction into the authored layout).\n\t\t\t\t\t\t\tthis.applyingLayout = true\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tthis.grid.update(el, { x, y })\n\t\t\t\t\t\t\t} finally {\n\t\t\t\t\t\t\t\tthis.applyingLayout = false\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Drop the stale node without touching its detached DOM,\n\t\t\t\t\t\t// then adopt the new element so GridStack sizes/places it.\n\t\t\t\t\t\tthis.grid.removeWidget(node.el, false, false)\n\t\t\t\t\t\tthis.grid.makeWidget(el)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\n\t\t\t// Remove items no longer in layout\n\t\t\tconst ids = newLayout.map((i) => String(i.id))\n\t\t\tconst toRemove = this.grid.engine.nodes.filter((n) => !ids.includes(String(n.id)))\n\t\t\tfor (const node of toRemove) {\n\t\t\t\tconst el = this.$refs.gridContainer.querySelector(`[gs-id=\"${node.id}\"]`)\n\t\t\t\tif (el) {\n\t\t\t\t\tthis.grid.removeWidget(el, false)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Elements were added or replaced, so the observer's are stale.\n\t\t\tthis.$nextTick(() => this.observeContentSizes())\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-dashboard-grid {\n\twidth: 100%;\n\tmin-height: 200px;\n}\n\n.grid-stack {\n\tbackground: transparent;\n}\n\n/* Screen-reader-only text: the keyboard key map and the live region. Kept in\n the layout (not `display: none`) so assistive tech still reads it. */\n.cn-dashboard-grid__sr-only {\n\tposition: absolute;\n\twidth: 1px;\n\theight: 1px;\n\tpadding: 0;\n\tmargin: -1px;\n\toverflow: hidden;\n\tclip-path: inset(50%);\n\twhite-space: nowrap;\n\tborder: 0;\n}\n\n/* Grid items become tab stops in edit mode — give them a visible focus ring\n that satisfies WCAG 2.1 SC 2.4.7 without relying on the widget's own\n styling. `outline-offset` keeps the ring outside the rounded card. */\n.grid-stack-item:focus-visible {\n\toutline: 2px solid var(--color-primary-element, #0082c9);\n\toutline-offset: 2px;\n\tborder-radius: var(--border-radius-container-large, 16px);\n}\n\n/* A size-to-content cell (`sizeToContent`) is measured by GridStack from the\n content's first child, and a widget wrapper there filled the cell\n (`height: 100%`), so the measure returned the cell's own height and the\n cell never shrank: \"Waiting for me\" stayed a tall card around one row. In\n such a cell the child takes its own height. A cell without the key keeps\n the filling child. */\n.cn-dashboard-grid__item--fit > :deep(.grid-stack-item-content > *) {\n\theight: auto;\n}\n\n:deep(.grid-stack-item-content) {\n\tbackground: var(--color-main-background);\n\tborder: none;\n\tbox-shadow: none;\n\toverflow: hidden;\n}\n\n:deep(.grid-stack-item-content:has(.cn-widget-wrapper--borderless)) {\n\tbackground: transparent;\n}\n\n:deep(.grid-stack-placeholder > .placeholder-content) {\n\tbackground: var(--color-primary-element-light);\n\tborder: 2px dashed var(--color-primary-element);\n\tborder-radius: var(--border-radius-large);\n}\n</style>\n"],"names":["_createElementVNode","_Fragment","_renderList","_renderSlot"],"mappings":";;;;;;;AAyCO,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,YAAY,GAAA;;AAmBtB,MAAA,UAAA,GAAA,EAAA,KAAA,EAAA,yBAAA,GAAA;;;;;;;;;;KAzBM,QAAc,CAAA,cAAA;wCAIjB,CAAA,GAAA,EAAA;;AAHF,UAAA,EAAE,EAAE,QAAiB,CAAA,iBAAA;eACjB,EAAC,4BAAkB;2BACrB,QAAa,CAAA,gBAAA,CAAA,EAAA,CAAA,oBAAA,UAAA,CAAA;;AAGf,IAAAA,kBAAA,CAyBC,OAzBD,UAyBC,EAAA;2CADIC,QAtBc,EAAA,IAAA,EAAAC,UAAA,CAAA,MAAA,CAAA,MAAA,EAAX,CAAA,IAAI,EAAE,KAAK,KAAA;+CAsBd,CAAA,KAAA,EAAA;UArBJ,GAAG,EAAE,QAAc,CAAA,cAAA,CAAA,IAAA,CAAA;AACpB,UAAA,KAAK,kBAAC,iBAAiB,EAAA,EAAA,8BAAA,EACe,0BAAA,CAAA,IAAA,MAAA,SAAA,EAAA,CAAA,CAAA;AACtC,UAAA,IAAK,EAAA,OAAO;AACX,UAAA,YAAU,EAAE,QAAA,CAAA,gBAAgB,CAAC,IAAG,EAAA,KAAA,CAAA;4BAChB,EAAE,QAAe,CAAA,eAAA;AACjC,UAAA,QAAM,EAAA,QAAA,CAAA,YAAA;UACN,OAAI,EAAA,IAAA,CAAA,EAAA;UACJ,MAAG,EAAA,IAAA,CAAA,KAAA;UACH,MAAE,EAAA,IAAA,CAAA,KAAA;;UAEF,MAAA,EAAA,IAAA,CAAA,UAAA;AACA,UAAA,UAAQ,EAAE,MAAQ,CAAA,QAAA;AAClB,UAAA,UAAQ,EAAE,MAAS,CAAA,SAAA;UACnB,oBAAkB,EAAE,QAAkB,CAAA,iBAAA,CAAA,IAAG,CAAA;AAC1C,UAAA,SAAA,EAAA,MAAA,KAAA,QAAA,CAAA,aAAA,CAAA,MAAA,EAAA,IAAA,CAAA,CAAA;;AACD,UAAAF,kBAAA,CAIO,OAJP,UAIO,EAAA;YAHLG,UAEO,CAAA,IAAA,CAAA,MAAA,EAAA,QAAA,EAAA,EAFc,IAAI,EAAE,IAAI,EAAA,CAAA;;;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"CnDashboardGrid.vue3.js","sources":["../../../../src/components/CnDashboardGrid/CnDashboardGrid.vue?vue&type=template&id=9f6b1a22&lang.js"],"sourcesContent":["<!--\n CnDashboardGrid — GridStack-powered drag-and-drop dashboard grid.\n\n Renders widgets in a configurable grid layout. Supports drag, resize,\n and dynamic item addition/removal. Emits layout changes for persistence.\n\n Accessibility: every grid item is an ARIA `group` with an accessible name\n and — in edit mode — a tab stop, so the pointer-only GridStack drag/resize\n gestures have a full keyboard equivalent (WCAG 2.1 SC 2.1.1). Keyboard\n moves are routed through `GridStack.update()`, i.e. the exact same engine\n call drag/resize ends in, so collision handling, persistence and the\n `layout-change` emission are byte-identical between the two input modes.\n-->\n<template>\n\t<div ref=\"gridContainer\" class=\"cn-dashboard-grid\">\n\t\t<!--\n\t\t\tKeyboard instructions, referenced by every grid item through\n\t\t\t`aria-describedby`. Rendered once (not per item) so a screen\n\t\t\treader user hears the key map on the first item they land on\n\t\t\twithout it being duplicated N times in the accessibility tree.\n\t\t-->\n\t\t<p\n\t\t\tv-if=\"keyboardActive\"\n\t\t\t:id=\"keyboardHelpDomId\"\n\t\t\tclass=\"cn-dashboard-grid__sr-only\">\n\t\t\t{{ keyboardHelpText }}\n\t\t</p>\n\n\t\t<div class=\"grid-stack\">\n\t\t\t<div\n\t\t\t\tv-for=\"(item, index) in layout\"\n\t\t\t\t:key=\"resolveItemKey(item)\"\n\t\t\t\tclass=\"grid-stack-item\"\n\t\t\t\t:class=\"{ 'cn-dashboard-grid__item--fit': sizeToContentAttr(item) !== undefined }\"\n\t\t\t\trole=\"group\"\n\t\t\t\t:aria-label=\"resolveItemLabel(item, index)\"\n\t\t\t\t:aria-describedby=\"itemDescribedBy\"\n\t\t\t\t:tabindex=\"itemTabindex\"\n\t\t\t\t:gs-id=\"item.id\"\n\t\t\t\t:gs-x=\"item.gridX\"\n\t\t\t\t:gs-y=\"item.gridY\"\n\t\t\t\t:gs-w=\"item.gridWidth\"\n\t\t\t\t:gs-h=\"item.gridHeight\"\n\t\t\t\t:gs-min-w=\"minWidth\"\n\t\t\t\t:gs-min-h=\"minHeight\"\n\t\t\t\t:gs-size-to-content=\"sizeToContentAttr(item)\"\n\t\t\t\t@keydown=\"onItemKeydown($event, item)\">\n\t\t\t\t<div class=\"grid-stack-item-content\">\n\t\t\t\t\t<slot name=\"widget\" :item=\"item\">\n\t\t\t\t\t\t<!-- Default: render nothing; CnDashboardPage provides content -->\n\t\t\t\t\t</slot>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!--\n\t\t\tPolite live region for keyboard move/resize feedback. A sighted\n\t\t\tuser sees the widget slide; a screen-reader user needs the new\n\t\t\tcoordinates spoken, otherwise the arrow keys are silent.\n\t\t-->\n\t\t<p\n\t\t\tclass=\"cn-dashboard-grid__sr-only\"\n\t\t\trole=\"status\"\n\t\t\taria-live=\"polite\"\n\t\t\taria-atomic=\"true\">\n\t\t\t{{ announcement }}\n\t\t</p>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { GridStack } from 'gridstack'\n// `gridstack` is a peerDependency (see package.json), not bundled — this\n// component's JS and the consumer's GridStack stylesheet MUST come from the\n// exact same installed copy, because GridStack's JS writes layout state\n// (e.g. the `--gs-column-width` CSS custom property) that only its OWN\n// matching CSS reads. Baking a copy of `gridstack/dist/gridstack.min.css`\n// into this library's build (as a previous version did) freezes it at\n// nc-vue's build-time version, which can silently drift from whatever the\n// consumer's peer-installed copy actually is — the exact mismatch that once\n// made every grid item render at 0px width. So nc-vue ships NO GridStack\n// CSS of its own: the consumer MUST install `gridstack` themselves and\n// import `gridstack/dist/gridstack.min.css` (or their preferred GridStack\n// stylesheet) from that same copy.\n\n/**\n * Monotonic counter backing the per-instance DOM ids (the shared\n * `aria-describedby` help text). Module-level so two grids on one page\n * never collide — the same pattern CnCommandPalette uses.\n */\nlet domIdCounter = 0\n\n/**\n * Pixel offset applied to the grid item's top-left corner when synthesising\n * the `contextmenu` coordinates for a keyboard activation. Nudging the anchor\n * inside the item keeps consumer menus (which position at `clientX/clientY`)\n * visually attached to the widget instead of straddling its border.\n */\nconst MENU_ANCHOR_OFFSET = 8\n\n/**\n * CnDashboardGrid — Low-level grid layout engine powered by GridStack.\n *\n * Manages the drag-and-drop grid, syncs positions, and emits layout\n * changes. Does NOT handle widget rendering — that's done by the parent\n * via the `#widget` scoped slot.\n *\n * ```vue\n * <CnDashboardGrid\n * :layout=\"placements\"\n * :editable=\"isEditing\"\n * @layout-change=\"onLayoutChange\">\n * <template #widget=\"{ item }\">\n * <MyWidget :config=\"item\" />\n * </template>\n * </CnDashboardGrid>\n * ```\n *\n * ### Keyboard operation\n *\n * With `keyboardRepositioning` on (the default) and `editable` true, each\n * grid item is a tab stop. With an item focused:\n *\n * | Key | Action |\n * | --- | ------ |\n * | `ArrowLeft` / `ArrowRight` | move one column left / right |\n * | `ArrowUp` / `ArrowDown` | move one row up / down |\n * | `Shift` + `ArrowLeft` / `ArrowRight` | shrink / grow width by one column |\n * | `Shift` + `ArrowUp` / `ArrowDown` | shrink / grow height by one row |\n * | `Home` / `End` | jump to the first / last column of the current row |\n * | `Enter` / `Space` | activate the item — see `item-activate` |\n *\n * Keys are only honoured when the grid item itself holds focus, so\n * interactive content rendered inside the `#widget` slot keeps its own\n * key handling untouched.\n */\nexport default {\n\tname: 'CnDashboardGrid',\n\n\tprops: {\n\t\t/** Array of layout items: { id, gridX, gridY, gridWidth, gridHeight, ...extra } */\n\t\tlayout: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Whether drag and resize are enabled */\n\t\teditable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Number of grid columns */\n\t\tcolumns: {\n\t\t\ttype: Number,\n\t\t\tdefault: 12,\n\t\t},\n\n\t\t/** Cell height in pixels */\n\t\tcellHeight: {\n\t\t\ttype: Number,\n\t\t\tdefault: 80,\n\t\t},\n\n\t\t/** Grid margin in pixels */\n\t\tmargin: {\n\t\t\ttype: Number,\n\t\t\tdefault: 12,\n\t\t},\n\n\t\t/** Minimum widget width in grid units */\n\t\tminWidth: {\n\t\t\ttype: Number,\n\t\t\tdefault: 2,\n\t\t},\n\n\t\t/** Minimum widget height in grid units */\n\t\tminHeight: {\n\t\t\ttype: Number,\n\t\t\tdefault: 2,\n\t\t},\n\n\t\t/**\n\t\t * GridStack v12 responsive `columnOpts` bag (breakpoints + reflow\n\t\t * layout). When set, the grid reflows column count across screen sizes.\n\t\t * Build it with `getDashboardColumnOpts()`. Default `null` = fixed\n\t\t * `columns`, no responsive reflow (backwards-compatible).\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tcolumnOpts: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * When set, `cellHeight` is mirrored into this CSS custom property on\n\t\t * the document root at init (e.g. `'--app-cell-height'`), so app CSS can\n\t\t * align to the grid geometry. Default `null` = no CSS var written.\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\tcellHeightCssVar: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Optional `(item) => string|number` to derive each item's render key.\n\t\t * Use it to force a re-render when an item changes in a way its `id`\n\t\t * doesn't capture (e.g. style edits — return `${item.id}:${item.updatedAt}`).\n\t\t * Default `null` = key on `item.id`.\n\t\t *\n\t\t * @type {((item: object) => (string|number))|null}\n\t\t */\n\t\titemKey: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Whether grid items are keyboard-operable: focusable in edit mode and\n\t\t * repositionable/resizable with the arrow keys (WCAG 2.1 SC 2.1.1 —\n\t\t * the keyboard equivalent of the pointer-only GridStack drag). Turning\n\t\t * it off leaves the items non-focusable and drag-only. Default `true`.\n\t\t */\n\t\tkeyboardRepositioning: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Optional `(item, index) => string` returning the accessible name for\n\t\t * a grid item. Use it when the layout items don't carry a human-readable\n\t\t * field (the built-in fallback walks `title` → `name` → `label` →\n\t\t * `widgetTitle` → `widgetId`, then a positional \"Widget N\"). The returned\n\t\t * string is used verbatim — no coordinates are appended.\n\t\t *\n\t\t * @type {((item: object, index: number) => string)|null}\n\t\t */\n\t\titemLabel: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Whether a keyboard activation (`Enter` / `Space` on a focused grid\n\t\t * item) also dispatches a bubbling `contextmenu` event from inside the\n\t\t * item, so consumers that open a per-widget menu on right-click get the\n\t\t * keyboard path for free — mirroring what browsers do for the `Menu`\n\t\t * key. Set `false` to rely purely on the `item-activate` event.\n\t\t * Default `true`.\n\t\t */\n\t\tactivateOpensContextMenu: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * GridStack's `float`. True (the default) keeps every item on the row\n\t\t * it was placed on, with empty rows above it if need be. False packs\n\t\t * items upward, so a size-to-content cell that shrinks, or a widget\n\t\t * that is not shown, closes its gap instead of leaving a band.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-dashboard-closes-the-gap-a-shrinking-or-hidden-widget-leaves\n\t\t */\n\t\tfloat: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * Fired whenever the grid geometry changed — by drag, by resize, or by\n\t\t * a keyboard nudge. Payload is the full `layout` array with the moved\n\t\t * items' `gridX`/`gridY`/`gridWidth`/`gridHeight` updated.\n\t\t */\n\t\t'layout-change',\n\t\t/**\n\t\t * Fired when the user activates a focused grid item with `Enter` or\n\t\t * `Space`. Payload `{ item, element, clientX, clientY }` — the layout\n\t\t * item, its DOM element, and viewport coordinates anchored to the\n\t\t * item's top-left corner so a consumer menu can position itself the\n\t\t * same way it does for a right-click.\n\t\t */\n\t\t'item-activate',\n\t],\n\n\tdata() {\n\t\treturn {\n\t\t\tgrid: null,\n\t\t\tcontentObserver: null,\n\t\t\t/** True while the grid moves an item to where the layout put it. */\n\t\t\tapplyingLayout: false,\n\t\t\tdomId: `cn-dashboard-grid-${++domIdCounter}`,\n\t\t\t/** Text currently held in the polite live region. */\n\t\t\tannouncement: '',\n\t\t\t/**\n\t\t\t * Whether a `columnOpts` breakpoint is currently in force. Mirrored\n\t\t\t * from the engine by `syncInteractivity` because `isReflowed()` reads\n\t\t\t * `grid.getColumn()` imperatively, and a computed cannot re-evaluate\n\t\t\t * on that.\n\t\t\t */\n\t\t\treflowed: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * @return {boolean} Whether the keyboard lane is live — items are\n\t\t * focusable and the arrow keys reposition. Requires the opt-in prop,\n\t\t * edit mode (a read-only dashboard has nothing to move, so adding N tab\n\t\t * stops there would be pure noise), and a grid that is not reflowed,\n\t\t * which is where a move cannot be kept and so must not be offered.\n\t\t */\n\t\tkeyboardActive() {\n\t\t\treturn this.keyboardRepositioning && this.editable && !this.reflowed\n\t\t},\n\n\t\t/**\n\t\t * @return {number|null} `0` when grid items should be tab stops,\n\t\t * otherwise `null` so the attribute is omitted entirely.\n\t\t */\n\t\titemTabindex() {\n\t\t\treturn this.keyboardActive ? 0 : null\n\t\t},\n\n\t\t/**\n\t\t * @return {string} DOM id of the shared keyboard-instructions element.\n\t\t */\n\t\tkeyboardHelpDomId() {\n\t\t\treturn `${this.domId}-keyboard-help`\n\t\t},\n\n\t\t/**\n\t\t * @return {string|null} `aria-describedby` target for each grid item —\n\t\t * the instructions element while the keyboard lane is live, `null`\n\t\t * otherwise (a dangling id reference is an axe violation).\n\t\t */\n\t\titemDescribedBy() {\n\t\t\treturn this.keyboardActive ? this.keyboardHelpDomId : null\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The spoken key map for the keyboard lane.\n\t\t */\n\t\tkeyboardHelpText() {\n\t\t\treturn t(\n\t\t\t\t'nextcloud-vue',\n\t\t\t\t'Use the arrow keys to move this widget one cell. Hold Shift and use the arrow keys to resize it. Press Home or End to jump to the first or last column. Press Enter to open the widget menu.',\n\t\t\t)\n\t\t},\n\n\t\t/**\n\t\t * @return {number} The column count currently in force. With responsive\n\t\t * `columnOpts` GridStack reflows to fewer columns on small screens, so\n\t\t * clamping must read the live value rather than the `columns` prop.\n\t\t */\n\t\teffectiveColumns() {\n\t\t\tif (this.grid && typeof this.grid.getColumn === 'function') {\n\t\t\t\tconst live = this.grid.getColumn()\n\t\t\t\tif (Number.isFinite(live) && live > 0) {\n\t\t\t\t\treturn live\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn this.columns\n\t\t},\n\t},\n\n\twatch: {\n\t\teditable() {\n\t\t\tthis.syncInteractivity()\n\t\t},\n\n\t\tlayout: {\n\t\t\tdeep: true,\n\t\t\thandler(newLayout) {\n\t\t\t\tif (this.grid) {\n\t\t\t\t\tthis.syncGridItems(newLayout)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.initGrid()\n\t\tthis.$nextTick(() => this.observeContentSizes())\n\t},\n\n\tbeforeUnmount() {\n\t\tthis.disconnectContentObserver()\n\t\tif (this.grid) {\n\t\t\tthis.grid.destroy(false)\n\t\t}\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Derive the v-for render key for a layout item, honouring the optional\n\t\t * `itemKey` prop and falling back to `item.id`.\n\t\t *\n\t\t * @param {object} item the layout item.\n\t\t * @return {string|number} the render key.\n\t\t */\n\t\tresolveItemKey(item) {\n\t\t\treturn this.itemKey ? this.itemKey(item) : item.id\n\t\t},\n\n\t\t/**\n\t\t * The `gs-size-to-content` attribute for a layout item, or `undefined` to\n\t\t * leave the item on its authored `gridHeight`.\n\t\t *\n\t\t * A widget that decides AT RUNTIME it has nothing to show — a banner whose\n\t\t * condition is false, a list that came back empty — still costs its whole\n\t\t * authored row, because the row is reserved from the layout before the\n\t\t * component renders and the engine positions items absolutely, so no CSS\n\t\t * can reclaim it. `sizeToContent` hands the row's height to the content, so\n\t\t * an empty widget collapses and the grid closes up behind it.\n\t\t *\n\t\t * Opt-in per item: it makes `gridHeight` advisory for that widget, which is\n\t\t * wrong for anything with a deliberate size (a map, a chart) and right for\n\t\t * anything whose height is whatever it turned out to have.\n\t\t *\n\t\t * @param {object} item the layout item.\n\t\t * @return {string|undefined} `'true'`, a row maximum as a string, or undefined.\n\t\t */\n\t\tsizeToContentAttr(item) {\n\t\t\tconst v = item && item.sizeToContent\n\t\t\tif (v === true) {\n\t\t\t\treturn 'true'\n\t\t\t}\n\t\t\t// A number is GridStack's \"size to content, but never taller than N rows\" (a maximum).\n\t\t\tif (Number.isFinite(v) && v > 0) {\n\t\t\t\treturn String(v)\n\t\t\t}\n\t\t\treturn undefined\n\t\t},\n\n\t\t/**\n\t\t * Build the accessible name for a grid item. A custom `itemLabel` wins\n\t\t * outright; otherwise the first human-readable field on the item is used\n\t\t * and, while the keyboard lane is live, the current grid coordinates are\n\t\t * appended — without them a screen-reader user tabbing through an edit-mode\n\t\t * dashboard has no way to tell where anything sits.\n\t\t *\n\t\t * @param {object} item the layout item.\n\t\t * @param {number} index the item's index in `layout`, used for the\n\t\t * last-resort positional name.\n\t\t * @return {string} the accessible name.\n\t\t */\n\t\tresolveItemLabel(item, index) {\n\t\t\tif (this.itemLabel) {\n\t\t\t\treturn String(this.itemLabel(item, index) ?? '')\n\t\t\t}\n\n\t\t\tconst base = item.title\n\t\t\t\t|| item.name\n\t\t\t\t|| item.label\n\t\t\t\t|| item.widgetTitle\n\t\t\t\t|| item.widgetId\n\t\t\t\t|| t('nextcloud-vue', 'Widget {number}', { number: index + 1 })\n\n\t\t\tif (!this.keyboardActive) {\n\t\t\t\treturn String(base)\n\t\t\t}\n\n\t\t\t// Report the rectangle exactly as the reactive `layout` prop states\n\t\t\t// it — unclamped, so the spoken coordinates always match the\n\t\t\t// rendered `gs-*` attributes — and read from the prop (not the\n\t\t\t// GridStack node) so the name re-renders when a move round-trips\n\t\t\t// back through `layout-change`; engine-node mutations are invisible\n\t\t\t// to Vue's reactivity.\n\t\t\tconst rect = {\n\t\t\t\tx: Number.isFinite(item.gridX) ? item.gridX : 0,\n\t\t\t\ty: Number.isFinite(item.gridY) ? item.gridY : 0,\n\t\t\t\tw: Number.isFinite(item.gridWidth) ? item.gridWidth : this.minWidth,\n\t\t\t\th: Number.isFinite(item.gridHeight) ? item.gridHeight : this.minHeight,\n\t\t\t}\n\t\t\treturn t(\n\t\t\t\t'nextcloud-vue',\n\t\t\t\t'{label}, column {column} of {columns}, row {row}, {width} columns wide, {height} rows tall',\n\t\t\t\t{\n\t\t\t\t\tlabel: String(base),\n\t\t\t\t\tcolumn: rect.x + 1,\n\t\t\t\t\tcolumns: this.effectiveColumns,\n\t\t\t\t\trow: rect.y + 1,\n\t\t\t\t\twidth: rect.w,\n\t\t\t\t\theight: rect.h,\n\t\t\t\t},\n\t\t\t)\n\t\t},\n\n\t\tinitGrid() {\n\t\t\tif (this.cellHeightCssVar && typeof document !== 'undefined' && document.documentElement) {\n\t\t\t\tdocument.documentElement.style.setProperty(this.cellHeightCssVar, `${this.cellHeight}px`)\n\t\t\t}\n\t\t\tconst el = this.$refs.gridContainer.querySelector('.grid-stack')\n\t\t\tthis.grid = GridStack.init({\n\t\t\t\tcolumn: this.columns,\n\t\t\t\tcellHeight: this.cellHeight,\n\t\t\t\tmargin: this.margin,\n\t\t\t\tfloat: this.float,\n\t\t\t\tanimate: true,\n\t\t\t\tdisableDrag: !this.editable,\n\t\t\t\tdisableResize: !this.editable,\n\t\t\t\tremovable: false,\n\t\t\t\t...(this.columnOpts ? { columnOpts: this.columnOpts } : {}),\n\t\t\t}, el)\n\n\t\t\tthis.grid.on('change', (_event, items) => {\n\t\t\t\tthis.handleGridChange(items)\n\t\t\t})\n\n\t\t\t// A grid that mounts already narrow is reflowed before anyone touches\n\t\t\t// it, so the handles have to be judged here and not only on a later\n\t\t\t// change.\n\t\t\tthis.syncInteractivity()\n\t\t},\n\n\t\t/**\n\t\t * Keep both editing lanes in step with whether an edit could actually be\n\t\t * kept: the pointer's handles, and — through `reflowed` feeding\n\t\t * `keyboardActive` — the tab stops, the help text and the arrow keys.\n\t\t *\n\t\t * A reflowed grid drops every `change` it receives, for the reason\n\t\t * `handleGridChange` gives. Leaving either lane live there offers an edit\n\t\t * that silently snaps back, which is the same class of surprise as\n\t\t * persisting the reflow — pointed the other way, and harder to explain\n\t\t * because the author did everything right. It is worse on the keyboard,\n\t\t * where the move is also announced as done. The affordance has to say what\n\t\t * the grid will do at this width, not what edit mode does at the authored\n\t\t * one.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tsyncInteractivity() {\n\t\t\t// Ahead of the engine guard: the keyboard lane reads this whether or\n\t\t\t// not the engine can be enabled.\n\t\t\tthis.reflowed = this.isReflowed()\n\t\t\tif (!this.grid || typeof this.grid.enable !== 'function') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.editable && !this.reflowed) {\n\t\t\t\tthis.grid.enable()\n\t\t\t} else {\n\t\t\t\tthis.grid.disable()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Whether the grid is currently rescaled to a column count other than the\n\t\t * authored one, i.e. a `columnOpts` breakpoint is in force.\n\t\t *\n\t\t * The authored count is `columnOpts.columnMax` when there is one, not the\n\t\t * `columns` prop: GridStack widens to `columnMax` above the top breakpoint,\n\t\t * so a consumer whose two disagree is at its widest, not reflowed.\n\t\t *\n\t\t * @return {boolean} True while a responsive reflow is active.\n\t\t */\n\t\tisReflowed() {\n\t\t\tif (!this.grid || typeof this.grid.getColumn !== 'function') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst authored = Number.isFinite(this.columnOpts?.columnMax) ? this.columnOpts.columnMax : this.columns\n\t\t\tconst live = this.grid.getColumn()\n\t\t\treturn Number.isFinite(live) && live > 0 && live !== authored\n\t\t},\n\n\t\thandleGridChange(items) {\n\t\t\t// The rescale that crossed a breakpoint arrives here as a `change`, so\n\t\t\t// this is also where the handles are re-judged in both directions:\n\t\t\t// switched off as the grid narrows, and back on as it widens.\n\t\t\tthis.syncInteractivity()\n\n\t\t\tif (this.applyingLayout) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// GridStack fires `change` for its own responsive rescale too, and at a\n\t\t\t// 12 → 1 breakpoint every item reads `gridX: 0, gridWidth: 1`. Emitting\n\t\t\t// that has the host persist the reflow AS the authored layout, which\n\t\t\t// widening back does not undo. A move made while reflowed is the same\n\t\t\t// rescaled geometry, so it is dropped for the same reason.\n\t\t\tif (this.isReflowed()) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!items || items.length === 0) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst updated = this.layout.map((item) => {\n\t\t\t\tconst gridItem = items.find((gi) => String(gi.id) === String(item.id))\n\t\t\t\tif (gridItem) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\t...item,\n\t\t\t\t\t\tgridX: gridItem.x,\n\t\t\t\t\t\tgridY: gridItem.y,\n\t\t\t\t\t\tgridWidth: gridItem.w,\n\t\t\t\t\t\tgridHeight: gridItem.h,\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn item\n\t\t\t})\n\n\t\t\tthis.$emit('layout-change', updated)\n\t\t},\n\n\t\t/**\n\t\t * Keydown handler bound to every grid item.\n\t\t *\n\t\t * Only reacts when the grid item element itself is the event target:\n\t\t * a button, input or menu rendered inside the `#widget` slot must keep\n\t\t * its own arrow/Enter semantics, and a grid that swallowed them would\n\t\t * be a worse a11y regression than the one this handler fixes.\n\t\t *\n\t\t * @param {KeyboardEvent} event the keydown event.\n\t\t * @param {object} item the layout item the key was pressed on.\n\t\t * @return {void}\n\t\t */\n\t\tonItemKeydown(event, item) {\n\t\t\tif (!this.keyboardRepositioning) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (event.target !== event.currentTarget) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (event.altKey || event.ctrlKey || event.metaKey) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (event.key === 'Enter' || event.key === ' ' || event.key === 'Spacebar') {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tthis.activateItem(item, event.currentTarget)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Repositioning only, and only where it can be kept — an item that\n\t\t\t// still holds focus from before a reflow keeps its Enter, but its\n\t\t\t// arrow keys stop moving something that would snap back.\n\t\t\tif (!this.keyboardActive) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst handled = this.applyKey(event.key, event.shiftKey, item)\n\t\t\tif (handled) {\n\t\t\t\t// Arrows would otherwise scroll the dashboard out from under\n\t\t\t\t// the widget the user is moving.\n\t\t\t\tevent.preventDefault()\n\t\t\t\tevent.stopPropagation()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Translate one repositioning key into a geometry change.\n\t\t *\n\t\t * @param {string} key the `KeyboardEvent.key` value.\n\t\t * @param {boolean} shift whether Shift was held (resize instead of move).\n\t\t * @param {object} item the layout item being operated on.\n\t\t * @return {boolean} `true` when the key belongs to the grid and the\n\t\t * default action must be suppressed.\n\t\t */\n\t\tapplyKey(key, shift, item) {\n\t\t\tconst rect = this.currentRect(item)\n\n\t\t\tswitch (key) {\n\t\t\t\tcase 'ArrowLeft':\n\t\t\t\t\treturn shift\n\t\t\t\t\t\t? this.applyGeometry(item, { w: rect.w - 1 }, 'resize')\n\t\t\t\t\t\t: this.applyGeometry(item, { x: rect.x - 1 }, 'move')\n\t\t\t\tcase 'ArrowRight':\n\t\t\t\t\treturn shift\n\t\t\t\t\t\t? this.applyGeometry(item, { w: rect.w + 1 }, 'resize')\n\t\t\t\t\t\t: this.applyGeometry(item, { x: rect.x + 1 }, 'move')\n\t\t\t\tcase 'ArrowUp':\n\t\t\t\t\treturn shift\n\t\t\t\t\t\t? this.applyGeometry(item, { h: rect.h - 1 }, 'resize')\n\t\t\t\t\t\t: this.applyGeometry(item, { y: rect.y - 1 }, 'move')\n\t\t\t\tcase 'ArrowDown':\n\t\t\t\t\treturn shift\n\t\t\t\t\t\t? this.applyGeometry(item, { h: rect.h + 1 }, 'resize')\n\t\t\t\t\t\t: this.applyGeometry(item, { y: rect.y + 1 }, 'move')\n\t\t\t\tcase 'Home':\n\t\t\t\t\treturn this.applyGeometry(item, { x: 0 }, 'move')\n\t\t\t\tcase 'End':\n\t\t\t\t\treturn this.applyGeometry(item, { x: this.effectiveColumns - rect.w }, 'move')\n\t\t\t\tdefault:\n\t\t\t\t\treturn false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Read an item's live rectangle. The GridStack node is preferred over\n\t\t * the `layout` prop because a drag that has not yet round-tripped\n\t\t * through the consumer's persistence leaves the prop momentarily stale,\n\t\t * and a keyboard nudge must continue from where the widget actually is.\n\t\t *\n\t\t * @param {object} item the layout item.\n\t\t * @return {{x: number, y: number, w: number, h: number}} the rectangle\n\t\t * in grid units.\n\t\t */\n\t\tcurrentRect(item) {\n\t\t\tconst node = this.gridNode(item.id)\n\t\t\tif (node && Number.isFinite(node.x) && Number.isFinite(node.y)) {\n\t\t\t\treturn {\n\t\t\t\t\tx: node.x,\n\t\t\t\t\ty: node.y,\n\t\t\t\t\tw: Number.isFinite(node.w) ? node.w : this.minWidth,\n\t\t\t\t\th: Number.isFinite(node.h) ? node.h : this.minHeight,\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tx: Number.isFinite(item.gridX) ? item.gridX : 0,\n\t\t\t\ty: Number.isFinite(item.gridY) ? item.gridY : 0,\n\t\t\t\tw: Number.isFinite(item.gridWidth) ? item.gridWidth : this.minWidth,\n\t\t\t\th: Number.isFinite(item.gridHeight) ? item.gridHeight : this.minHeight,\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Clamp a candidate rectangle to the grid's bounds and the configured\n\t\t * minimum widget size, so a held-down arrow key parks the widget at the\n\t\t * edge instead of pushing it into negative coordinates.\n\t\t *\n\t\t * @param {{x: number, y: number, w: number, h: number}} rect the candidate.\n\t\t * @return {{x: number, y: number, w: number, h: number}} the clamped rectangle.\n\t\t */\n\t\tclampRect(rect) {\n\t\t\tconst cols = this.effectiveColumns\n\t\t\tconst w = Math.min(Math.max(rect.w, this.minWidth), cols)\n\t\t\tconst h = Math.max(rect.h, this.minHeight)\n\t\t\treturn {\n\t\t\t\tx: Math.min(Math.max(rect.x, 0), Math.max(cols - w, 0)),\n\t\t\t\ty: Math.max(rect.y, 0),\n\t\t\t\tw,\n\t\t\t\th,\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Apply a partial geometry change to an item.\n\t\t *\n\t\t * The change is pushed through `GridStack.update()` — the very call the\n\t\t * drag/resize handlers end in — so collision resolution, the `change`\n\t\t * event and therefore the `layout-change` emission and the consumer's\n\t\t * persistence are identical for keyboard and pointer. There is\n\t\t * deliberately no second, direct-to-`layout` update path.\n\t\t *\n\t\t * @param {object} item the layout item to move or resize.\n\t\t * @param {{x?: number, y?: number, w?: number, h?: number}} patch the\n\t\t * requested change, merged over the item's current rectangle.\n\t\t * @param {'move'|'resize'} kind which announcement to speak.\n\t\t * @return {boolean} `true` when the key was consumed — including when\n\t\t * the widget was already at the edge, because the arrow still belongs\n\t\t * to the grid and must not scroll the page.\n\t\t */\n\t\tapplyGeometry(item, patch, kind) {\n\t\t\tconst el = this.itemElement(item.id)\n\t\t\tif (!el || !this.grid || typeof this.grid.update !== 'function') {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tconst current = this.currentRect(item)\n\t\t\tconst next = this.clampRect({ ...current, ...patch })\n\n\t\t\tif (next.x === current.x && next.y === current.y\n\t\t\t\t&& next.w === current.w && next.h === current.h) {\n\t\t\t\tthis.announce(t('nextcloud-vue', '{label} cannot move further in that direction', {\n\t\t\t\t\tlabel: this.announcementLabel(item),\n\t\t\t\t}))\n\t\t\t\treturn true\n\t\t\t}\n\n\t\t\tthis.grid.update(el, next)\n\n\t\t\tthis.announce(kind === 'resize'\n\t\t\t\t? t('nextcloud-vue', '{label} resized to {width} columns by {height} rows', {\n\t\t\t\t\t\tlabel: this.announcementLabel(item),\n\t\t\t\t\t\twidth: next.w,\n\t\t\t\t\t\theight: next.h,\n\t\t\t\t\t})\n\t\t\t\t: t('nextcloud-vue', '{label} moved to column {column}, row {row}', {\n\t\t\t\t\t\tlabel: this.announcementLabel(item),\n\t\t\t\t\t\tcolumn: next.x + 1,\n\t\t\t\t\t\trow: next.y + 1,\n\t\t\t\t\t}))\n\n\t\t\treturn true\n\t\t},\n\n\t\t/**\n\t\t * The short, coordinate-free name used inside live-region sentences —\n\t\t * the full `aria-label` already carries the coordinates and repeating\n\t\t * them would make every announcement unlistenable.\n\t\t *\n\t\t * @param {object} item the layout item.\n\t\t * @return {string} the short name.\n\t\t */\n\t\tannouncementLabel(item) {\n\t\t\treturn String(item.title\n\t\t\t\t|| item.name\n\t\t\t\t|| item.label\n\t\t\t\t|| item.widgetTitle\n\t\t\t\t|| item.widgetId\n\t\t\t\t|| t('nextcloud-vue', 'Widget'))\n\t\t},\n\n\t\t/**\n\t\t * Push text into the polite live region. The region is cleared first so\n\t\t * two identical consecutive announcements (e.g. two failed nudges at the\n\t\t * grid edge) are both spoken — assistive tech ignores a re-render that\n\t\t * leaves the text unchanged.\n\t\t *\n\t\t * @param {string} text the message to announce.\n\t\t * @return {void}\n\t\t */\n\t\tannounce(text) {\n\t\t\tthis.announcement = ''\n\t\t\tthis.$nextTick(() => {\n\t\t\t\tthis.announcement = text\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Handle `Enter` / `Space` on a focused grid item: tell the consumer via\n\t\t * `item-activate`, and — unless opted out — synthesise the `contextmenu`\n\t\t * event a right-click would have produced, anchored to the item, so\n\t\t * pointer-only widget menus become keyboard-reachable without the\n\t\t * consumer wiring anything new. The synthetic event is dispatched from\n\t\t * inside the item's content so it bubbles through the slot's own root\n\t\t * element (where such handlers live) and on up.\n\t\t *\n\t\t * @param {object} item the layout item being activated.\n\t\t * @param {HTMLElement} el the grid item element.\n\t\t * @return {void}\n\t\t */\n\t\tactivateItem(item, el) {\n\t\t\tconst rect = typeof el.getBoundingClientRect === 'function'\n\t\t\t\t? el.getBoundingClientRect()\n\t\t\t\t: { left: 0, top: 0 }\n\t\t\tconst clientX = Math.round((rect.left || 0) + MENU_ANCHOR_OFFSET)\n\t\t\tconst clientY = Math.round((rect.top || 0) + MENU_ANCHOR_OFFSET)\n\n\t\t\tthis.$emit('item-activate', { item, element: el, clientX, clientY })\n\n\t\t\tif (this.activateOpensContextMenu) {\n\t\t\t\tthis.dispatchContextMenu(el, clientX, clientY)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Dispatch a bubbling, cancelable `contextmenu` MouseEvent from inside a\n\t\t * grid item.\n\t\t *\n\t\t * @param {HTMLElement} el the grid item element.\n\t\t * @param {number} clientX viewport x for the menu anchor.\n\t\t * @param {number} clientY viewport y for the menu anchor.\n\t\t * @return {void}\n\t\t */\n\t\tdispatchContextMenu(el, clientX, clientY) {\n\t\t\tif (typeof MouseEvent !== 'function') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst content = el.querySelector('.grid-stack-item-content')\n\t\t\tconst target = (content && content.firstElementChild) || content || el\n\t\t\ttarget.dispatchEvent(new MouseEvent('contextmenu', {\n\t\t\t\tbubbles: true,\n\t\t\t\tcancelable: true,\n\t\t\t\tclientX,\n\t\t\t\tclientY,\n\t\t\t\tbutton: 2,\n\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Look up a grid item's DOM element by its layout id.\n\t\t *\n\t\t * @param {string|number} id the layout item id.\n\t\t * @return {HTMLElement|null} the element, or `null` when not rendered.\n\t\t */\n\t\titemElement(id) {\n\t\t\tconst container = this.$refs.gridContainer\n\t\t\tif (!container) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn container.querySelector(`[gs-id=\"${id}\"]`)\n\t\t},\n\n\t\t/**\n\t\t * Look up the GridStack engine node backing a layout item.\n\t\t *\n\t\t * @param {string|number} id the layout item id.\n\t\t * @return {object|null} the node, or `null` when GridStack isn't tracking it.\n\t\t */\n\t\t/**\n\t\t * Re-measure a `sizeToContent` item when its content changes height.\n\t\t * GridStack watches only the grid element, so a widget that loaded its data\n\t\t * in `mounted()` keeps the height its empty state had.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tobserveContentSizes() {\n\t\t\tthis.disconnectContentObserver()\n\t\t\tif (typeof ResizeObserver === 'undefined' || !this.$refs.gridContainer) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.contentObserver = new ResizeObserver((entries) => {\n\t\t\t\tconst cells = new Set()\n\t\t\t\tfor (const entry of entries) {\n\t\t\t\t\tconst cell = entry.target.closest('.grid-stack-item')\n\t\t\t\t\tif (cell) {\n\t\t\t\t\t\tcells.add(cell)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (cells.size === 0) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// A frame later, not inline: `resizeToContent` resizes the cell the\n\t\t\t\t// observed child sits in, and a child that stretches to its parent\n\t\t\t\t// grows with it. Resizing from inside the callback makes that the\n\t\t\t\t// classic ResizeObserver loop (\"undelivered notifications\"); deferred,\n\t\t\t\t// it is an ordinary second pass that settles.\n\t\t\t\trequestAnimationFrame(() => {\n\t\t\t\t\tif (!this.contentObserver || !this.grid || typeof this.grid.resizeToContent !== 'function') {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tfor (const cell of cells) {\n\t\t\t\t\t\tthis.grid.resizeToContent(cell)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tconst cells = this.$refs.gridContainer.querySelectorAll('.grid-stack-item[gs-size-to-content]')\n\t\t\tfor (const cell of cells) {\n\t\t\t\tconst measured = cell.querySelector('.grid-stack-item-content')?.firstElementChild\n\t\t\t\tif (measured) {\n\t\t\t\t\tthis.contentObserver.observe(measured)\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Drop the content observer, on unmount or before rebuilding it.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tdisconnectContentObserver() {\n\t\t\tif (this.contentObserver) {\n\t\t\t\tthis.contentObserver.disconnect()\n\t\t\t\tthis.contentObserver = null\n\t\t\t}\n\t\t},\n\n\t\tgridNode(id) {\n\t\t\tconst nodes = this.grid && this.grid.engine && this.grid.engine.nodes\n\t\t\tif (!Array.isArray(nodes)) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn nodes.find((n) => String(n.id) === String(id)) || null\n\t\t},\n\n\t\tsyncGridItems(newLayout) {\n\t\t\t// Add new items, and re-adopt items whose backing DOM element was\n\t\t\t// replaced. When a consumer's `itemKey` changes for an unchanged\n\t\t\t// `id` (e.g. a style edit — the documented use of `itemKey`), Vue\n\t\t\t// swaps the element. GridStack keeps tracking the old, now-detached\n\t\t\t// element, leaving the new one unmanaged (no positioning/sizing) —\n\t\t\t// so re-register it against the new element.\n\t\t\tfor (const item of newLayout) {\n\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\tconst el = this.$refs.gridContainer.querySelector(`[gs-id=\"${item.id}\"]`)\n\t\t\t\t\tif (!el) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tconst node = this.grid.engine.nodes.find((n) => String(n.id) === String(item.id))\n\t\t\t\t\tif (!node) {\n\t\t\t\t\t\tthis.grid.makeWidget(el)\n\t\t\t\t\t} else if (node.el === el) {\n\t\t\t\t\t\t// The layout MOVED an item the grid already tracks: the\n\t\t\t\t\t\t// page re-compacts its display layout when a widget's\n\t\t\t\t\t\t// condition hides it, and the item below was given a new\n\t\t\t\t\t\t// gridY that never reached GridStack, so a band stayed\n\t\t\t\t\t\t// where the hidden widget had been. A drag reports the\n\t\t\t\t\t\t// grid's own position back, which matches and does\n\t\t\t\t\t\t// nothing here.\n\t\t\t\t\t\tconst x = item.gridX ?? node.x\n\t\t\t\t\t\tconst y = item.gridY ?? node.y\n\t\t\t\t\t\tif (x !== node.x || y !== node.y) {\n\t\t\t\t\t\t\t// The layout said where it goes; GridStack's `change`\n\t\t\t\t\t\t\t// for it is not a user's arrangement and is not\n\t\t\t\t\t\t\t// reported back (it would write the display-only\n\t\t\t\t\t\t\t// compaction into the authored layout).\n\t\t\t\t\t\t\tthis.applyingLayout = true\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tthis.grid.update(el, { x, y })\n\t\t\t\t\t\t\t} finally {\n\t\t\t\t\t\t\t\tthis.applyingLayout = false\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Drop the stale node without touching its detached DOM,\n\t\t\t\t\t\t// then adopt the new element so GridStack sizes/places it.\n\t\t\t\t\t\tthis.grid.removeWidget(node.el, false, false)\n\t\t\t\t\t\tthis.grid.makeWidget(el)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\n\t\t\t// Remove items no longer in layout\n\t\t\tconst ids = newLayout.map((i) => String(i.id))\n\t\t\tconst toRemove = this.grid.engine.nodes.filter((n) => !ids.includes(String(n.id)))\n\t\t\tfor (const node of toRemove) {\n\t\t\t\tconst el = this.$refs.gridContainer.querySelector(`[gs-id=\"${node.id}\"]`)\n\t\t\t\tif (el) {\n\t\t\t\t\tthis.grid.removeWidget(el, false)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Elements were added or replaced, so the observer's are stale.\n\t\t\tthis.$nextTick(() => this.observeContentSizes())\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-dashboard-grid {\n\twidth: 100%;\n\tmin-height: 200px;\n}\n\n.grid-stack {\n\tbackground: transparent;\n}\n\n/* Screen-reader-only text: the keyboard key map and the live region. Kept in\n the layout (not `display: none`) so assistive tech still reads it. */\n.cn-dashboard-grid__sr-only {\n\tposition: absolute;\n\twidth: 1px;\n\theight: 1px;\n\tpadding: 0;\n\tmargin: -1px;\n\toverflow: hidden;\n\tclip-path: inset(50%);\n\twhite-space: nowrap;\n\tborder: 0;\n}\n\n/* Grid items become tab stops in edit mode — give them a visible focus ring\n that satisfies WCAG 2.1 SC 2.4.7 without relying on the widget's own\n styling. `outline-offset` keeps the ring outside the rounded card. */\n.grid-stack-item:focus-visible {\n\toutline: 2px solid var(--color-primary-element, #0082c9);\n\toutline-offset: 2px;\n\tborder-radius: var(--border-radius-container-large, 16px);\n}\n\n/* A size-to-content cell (`sizeToContent`) is measured by GridStack from the\n content's first child, and a widget wrapper there filled the cell\n (`height: 100%`), so the measure returned the cell's own height and the\n cell never shrank: \"Waiting for me\" stayed a tall card around one row. In\n such a cell the child takes its own height. A cell without the key keeps\n the filling child. */\n.cn-dashboard-grid__item--fit > :deep(.grid-stack-item-content > *) {\n\theight: auto;\n}\n\n:deep(.grid-stack-item-content) {\n\tbackground: var(--color-main-background);\n\tborder: none;\n\tbox-shadow: none;\n\toverflow: hidden;\n}\n\n:deep(.grid-stack-item-content:has(.cn-widget-wrapper--borderless)) {\n\tbackground: transparent;\n}\n\n:deep(.grid-stack-placeholder > .placeholder-content) {\n\tbackground: var(--color-primary-element-light);\n\tborder: 2px dashed var(--color-primary-element);\n\tborder-radius: var(--border-radius-large);\n}\n</style>\n"],"names":["_createElementVNode","_Fragment","_renderList","_renderSlot"],"mappings":";;;;;;;AAyCO,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,YAAY,GAAA;;AAmBtB,MAAA,UAAA,GAAA,EAAA,KAAA,EAAA,yBAAA,GAAA;;;;;;;;;;KAzBM,QAAc,CAAA,cAAA;wCAIjB,CAAA,GAAA,EAAA;;AAHF,UAAA,EAAE,EAAE,QAAiB,CAAA,iBAAA;eACjB,EAAC,4BAAkB;2BACrB,QAAa,CAAA,gBAAA,CAAA,EAAA,CAAA,oBAAA,UAAA,CAAA;;AAGf,IAAAA,kBAAA,CAyBC,OAzBD,UAyBC,EAAA;2CADIC,QAtBc,EAAA,IAAA,EAAAC,UAAA,CAAA,MAAA,CAAA,MAAA,EAAX,CAAA,IAAI,EAAE,KAAK,KAAA;+CAsBd,CAAA,KAAA,EAAA;UArBJ,GAAG,EAAE,QAAc,CAAA,cAAA,CAAA,IAAA,CAAA;AACpB,UAAA,KAAK,kBAAC,iBAAiB,EAAA,EAAA,8BAAA,EACe,0BAAA,CAAA,IAAA,MAAA,SAAA,EAAA,CAAA,CAAA;AACtC,UAAA,IAAK,EAAA,OAAO;AACX,UAAA,YAAU,EAAE,QAAA,CAAA,gBAAgB,CAAC,IAAG,EAAA,KAAA,CAAA;4BAChB,EAAE,QAAe,CAAA,eAAA;AACjC,UAAA,QAAM,EAAA,QAAA,CAAA,YAAA;UACN,OAAI,EAAA,IAAA,CAAA,EAAA;UACJ,MAAG,EAAA,IAAA,CAAA,KAAA;UACH,MAAE,EAAA,IAAA,CAAA,KAAA;;UAEF,MAAA,EAAA,IAAA,CAAA,UAAA;AACA,UAAA,UAAQ,EAAE,MAAQ,CAAA,QAAA;AAClB,UAAA,UAAQ,EAAE,MAAS,CAAA,SAAA;UACnB,oBAAkB,EAAE,QAAkB,CAAA,iBAAA,CAAA,IAAG,CAAA;AAC1C,UAAA,SAAA,EAAA,MAAA,KAAA,QAAA,CAAA,aAAA,CAAA,MAAA,EAAA,IAAA,CAAA,CAAA;;AACD,UAAAF,kBAAA,CAIO,OAJP,UAIO,EAAA;YAHLG,UAEO,CAAA,IAAA,CAAA,MAAA,EAAA,QAAA,EAAA,EAFc,IAAI,EAAE,IAAI,EAAA,CAAA;;;;;;;;;;;"}
|