@conduction/nextcloud-vue 2.62.0 → 2.63.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/CnAdminActionCard/CnAdminActionCard.vue.js +10 -0
- package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue.js.map +1 -0
- package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue2.js +189 -0
- package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue2.js.map +1 -0
- package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue3.js +72 -0
- package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue3.js.map +1 -0
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +271 -32
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +99 -9
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +39 -18
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js +53 -37
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +2 -2
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue.js +10 -0
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue.js.map +1 -0
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js +293 -0
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js.map +1 -0
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js +189 -0
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js.map +1 -0
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +216 -8
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +118 -50
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +2 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue2.js +3 -1
- package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue2.js.map +1 -1
- package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue3.js +1 -1
- package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue3.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +234 -9
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +96 -57
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +51 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +5 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/selfModeActions.js +3 -3
- package/dist/esm/components/CnIndexPage/selfModeActions.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +36 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +4 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue2.js +121 -12
- package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue2.js.map +1 -1
- package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue3.js +3 -1
- package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue3.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +326 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +112 -26
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js +10 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js.map +1 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js +337 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js +102 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js +34 -0
- package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js.map +1 -0
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js +131 -8
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js.map +1 -1
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js +40 -38
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +8 -6
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js +19 -0
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js.map +1 -1
- package/dist/esm/composables/useDependencyCheck.js +65 -0
- package/dist/esm/composables/useDependencyCheck.js.map +1 -0
- package/dist/esm/composables/useWalkthrough.js +218 -6
- package/dist/esm/composables/useWalkthrough.js.map +1 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +43 -0
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +43 -0
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +449 -0
- package/dist/esm/store/useObjectStore.js +8 -0
- package/dist/esm/store/useObjectStore.js.map +1 -1
- package/dist/esm/utils/columnFilters.js +311 -0
- package/dist/esm/utils/columnFilters.js.map +1 -0
- package/dist/esm/utils/groupAutocomplete.js +123 -0
- package/dist/esm/utils/groupAutocomplete.js.map +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js +2 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/pickerOptions.js +198 -0
- package/dist/esm/utils/pickerOptions.js.map +1 -0
- package/dist/esm/utils/schema.js +83 -7
- package/dist/esm/utils/schema.js.map +1 -1
- package/dist/esm/utils/timelineEvents.js +174 -0
- package/dist/esm/utils/timelineEvents.js.map +1 -0
- package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/esm/utils/walkthroughSignals.js +88 -0
- package/dist/esm/utils/walkthroughSignals.js.map +1 -0
- package/dist/nextcloud-vue.cjs.js +9369 -5193
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +449 -0
- package/l10n/en.json +43 -0
- package/l10n/nl.json +43 -0
- package/package.json +1 -1
- package/src/components/CnAdminActionCard/CnAdminActionCard.vue +234 -0
- package/src/components/CnAdminActionCard/index.js +4 -0
- package/src/components/CnAppNav/CnAppNav.vue +271 -32
- package/src/components/CnAppRoot/CnAppRoot.vue +129 -15
- package/src/components/CnChoiceCards/CnChoiceCards.vue +56 -31
- package/src/components/CnDashboardPage/CnDashboardPage.vue +2 -2
- package/src/components/CnDataTable/CnColumnFilterPopover.vue +524 -0
- package/src/components/CnDataTable/CnDataTable.md +3 -0
- package/src/components/CnDataTable/CnDataTable.vue +260 -4
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +2 -2
- package/src/components/CnFieldHelper/CnFieldHelper.vue +4 -2
- package/src/components/CnFormDialog/CnFormDialog.vue +290 -31
- package/src/components/CnIndexPage/CnIndexPage.vue +55 -2
- package/src/components/CnIndexPage/selfModeActions.js +3 -3
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +39 -1
- package/src/components/CnResourceSelect/CnResourceSelect.vue +123 -12
- package/src/components/CnSetupWizard/CnSetupWizard.vue +392 -3
- package/src/components/CnTimelineWidget/CnTimelineWidget.vue +477 -0
- package/src/components/CnTimelineWidget/dashboardRegistration.js +32 -0
- package/src/components/CnTimelineWidget/index.js +4 -0
- package/src/components/CnWalkthrough/CnWalkthrough.vue +143 -10
- package/src/components/CnWidgetGrid/builtInWidgets.js +2 -0
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/src/components/CnWizardDialog/CnWizardDialog.vue +20 -1
- package/src/components/index.js +2 -0
- package/src/composables/useDependencyCheck.js +61 -0
- package/src/composables/useWalkthrough.js +217 -5
- package/src/css/dashboard.css +49 -0
- package/src/css/detail-page.css +19 -0
- package/src/css/table.css +81 -0
- package/src/index.js +2 -0
- package/src/schemas/app-manifest-v2.schema.json +23 -1
- package/src/store/useObjectStore.js +8 -0
- package/src/utils/columnFilters.js +324 -0
- package/src/utils/groupAutocomplete.js +119 -0
- package/src/utils/libraryWidgetKeys.js +2 -0
- package/src/utils/pickerOptions.js +194 -0
- package/src/utils/schema.js +83 -7
- package/src/utils/timelineEvents.js +171 -0
- package/src/utils/validateManifestV2.compiled.js +2 -2
- package/src/utils/walkthroughSignals.js +94 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnDetailWidgetHost.vue2.js","sources":["../../../../src/components/CnDetailWidgetHost/CnDetailWidgetHost.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-detail-widget-host\" :class=\"`cn-detail-widget-host--${chrome}`\">\n\t\t<!-- `type: 'data'` — the schema-driven data widget. In bare mode it is\n\t\t `chromeless`: no border, no background, no padding, no title row and\n\t\t no header divider, so the tab panel holds the content directly\n\t\t instead of a card inside a card.\n\n\t\t One prop, not three. `chromeless` is the word this component already\n\t\t used for the same thing on the integration path (`bareWidget`), and\n\t\t the three it replaces (`show-title`, `borderless`, `flush`) were a\n\t\t decomposition every caller had to remember in full — miss one and\n\t\t the panel keeps half a card.\n\n\t\t Chromeless does NOT mean no wrapper element. The wrapper's content\n\t\t node is what the library's table and detail-page CSS key on.\n\n\t\t It used to keep the whole card here, because its Save button lives\n\t\t in CnWidgetWrapper's header and hiding the header hid Save with it.\n\t\t The wrapper renders its header whenever there are actions, with or\n\t\t without a title, so the panel gets no title while an inline edit\n\t\t stays committable. Controls are not chrome.\n\n\t\t Passing `undefined` was never enough on its own: `title` carries a\n\t\t DEFAULT of \"Data\", so an unset title became a \"Data\" heading that\n\t\t nobody chose. The title is still passed, and now names the content\n\t\t region for a screen reader instead of printing a second heading.\n\n\t\t The overflow menu goes too, which `chromeless` does not cover. It\n\t\t left a 59px band whose only content was an Actions menu about eight\n\t\t pixels from the tab strip's own, and two menus on one row read as the\n\t\t same card inside a card in miniature.\n\n\t\t Nothing is lost, but that took a second piece of work rather than\n\t\t being true on its own. Suppressing the menu takes its two items with\n\t\t it, and Metadata had no other home anywhere: the page header carries\n\t\t no equivalent, so it became unreachable inside a panel. Edit is the\n\t\t softer case. CnDetailPage does carry a record Edit button, but it\n\t\t opens the form the PAGE configures, not the subset THIS widget\n\t\t declares through `overrides`, `include` and `exclude`, so a tabbed\n\t\t widget showing eight of forty fields lost the form scoped to its\n\t\t eight. Both items are now published to the host surface and render in\n\t\t its menu; see utils/panelActions.js. Inline editing was never\n\t\t affected: a cell opens its editor when clicked, and Save and Discard\n\t\t arrive in the header the moment there is an edit to commit. -->\n\t\t<!-- `requiredApp` names another Nextcloud app this widget leans on. When\n\t\t that app is absent the widget renders its NORMAL chrome plus a\n\t\t set-up state, and asks its backend NOTHING.\n\n\t\t It renders rather than hides on purpose. A hidden widget leaves a\n\t\t hole a reader cannot interpret, and the alternative — letting the\n\t\t query run — is worse: an aggregation over an absent app's register\n\t\t 404s and the tile shows `0`, which is exactly what a real zero\n\t\t shows. dossiq's hours tile did that on every install without\n\t\t humaniq, and looked correct doing it.\n\n\t\t \"NORMAL chrome\" means the chrome of the surface it is on. In a tab\n\t\t panel that is no chrome, same as every other widget there: a set-up\n\t\t state is still a widget, and a bordered card around it inside the\n\t\t panel is the same doubled card this file removes below. The title\n\t\t is now passed in bare mode too, where it names the content region\n\t\t rather than printing a heading the tab already carries. -->\n\t\t<CnWidgetWrapper\n\t\t\tv-if=\"missingApp\"\n\t\t\t:title=\"widgetTitle\"\n\t\t\t:chromeless=\"isBare\"\n\t\t\ttitleIconPosition=\"left\"\n\t\t\t:showRefresh=\"false\"\n\t\t\t:showRequestFeature=\"false\">\n\t\t\t<template v-if=\"widget && widget.icon\" #title-icon>\n\t\t\t\t<CnIcon :name=\"widget.icon\" :size=\"20\" />\n\t\t\t</template>\n\t\t\t<NcEmptyContent :name=\"missingAppName\" :description=\"missingAppDescription\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<CnIcon :name=\"(widget && widget.icon) || 'PuzzleOutline'\" :size=\"44\" />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\t\t</CnWidgetWrapper>\n\n\t\t<CnObjectDataWidget\n\t\t\tv-else-if=\"isData && schemaObject\"\n\t\t\t:title=\"resolvedTitle\"\n\t\t\t:chromeless=\"isBare\"\n\t\t\t:showActions=\"!isBare\"\n\t\t\t:icon=\"widget.icon || null\"\n\t\t\t:schema=\"schemaObject\"\n\t\t\t:objectData=\"object\"\n\t\t\t:objectType=\"objectType\"\n\t\t\t:store=\"store\"\n\t\t\t:overrides=\"content.overrides || {}\"\n\t\t\t:include=\"content.include || null\"\n\t\t\t:exclude=\"content.exclude || []\"\n\t\t\t:hideEmpty=\"content.hideEmpty === true || hideEmpty\"\n\t\t\t:columns=\"content.columns || 3\"\n\t\t\t:collapsedFields=\"content.collapsedFields ?? null\"\n\t\t\t:editable=\"content.editable !== false\" />\n\n\t\t<!-- `type: 'related'` — resolves this object's relations and links into\n\t\t integrations. Honours bare mode via its own `bare` prop. -->\n\t\t<CnRelatedObjectsWidget\n\t\t\tv-else-if=\"isRelated\"\n\t\t\t:title=\"resolvedTitle\"\n\t\t\t:bare=\"isBare\"\n\t\t\t:objectType=\"objectType\"\n\t\t\t:objectId=\"objectId\"\n\t\t\t:objectData=\"object\"\n\t\t\t:register=\"register\"\n\t\t\t:schema=\"schema\"\n\t\t\t:store=\"store\"\n\t\t\t:includeGroups=\"content.groups || []\"\n\t\t\t:hideSingleTabTitle=\"content.hideSingleTabTitle !== false\"\n\t\t\t:showTotalCount=\"content.showTotalCount !== false\"\n\t\t\t@openIntegration=\"onOpenIntegration\" />\n\n\t\t<!-- `type: 'object-geo'` — view/edit the object's `@self.geo` on a map. -->\n\t\t<CnObjectGeoWidget\n\t\t\tv-else-if=\"isGeo\"\n\t\t\t:title=\"resolvedTitle\"\n\t\t\t:objectId=\"objectId\"\n\t\t\t:objectData=\"object\"\n\t\t\t:register=\"register\"\n\t\t\t:schema=\"schema\"\n\t\t\t:editable=\"content.editable !== false\"\n\t\t\t:addressSearch=\"content.addressSearch === true\"\n\t\t\t:basemap=\"content.basemap || 'standard'\"\n\t\t\t:allowBasemapSwitch=\"content.allowBasemapSwitch === true\"\n\t\t\t:fitControl=\"content.fitControl !== false\"\n\t\t\t:locateControl=\"content.locateControl !== false\"\n\t\t\t:fullscreenControl=\"content.fullscreenControl !== false\"\n\t\t\t:height=\"content.height || '360px'\"\n\t\t\t:defaultZoom=\"content.defaultZoom || 7\"\n\t\t\t@saved=\"onGeoSaved\" />\n\n\t\t<!-- Mount-mode integration leaf (openregister#2127): a bare host-owned\n\t\t element the leaf mounts its own framework into. -->\n\t\t<CnLeafMountHost\n\t\t\tv-else-if=\"isMountIntegration\"\n\t\t\t:provider=\"integrationProvider\"\n\t\t\t:mountProps=\"integrationMountProps\" />\n\n\t\t<!-- Integration leaf, component mode. In BARE mode this renders the\n\t\t provider's `tab` (its bare content) rather than its `widget` (which\n\t\t draws its own card). That pairing is not an invention here:\n\t\t CnIntegrationWidget already renders `provider.tab` inside its own\n\t\t tab panels for exactly this reason. -->\n\t\t<component\n\t\t\t:is=\"integrationComponent\"\n\t\t\tv-else-if=\"isIntegration && integrationComponent\"\n\t\t\tv-bind=\"integrationProps\" />\n\n\t\t<!-- Content-only catalog widgets (object-list / table / files) render\n\t\t bare tables. On a card surface they get the titled wrapper (ADR-062:\n\t\t every body widget has chrome and its manifest title); inside a tab\n\t\t the strip supplies both, so they render as-is.\n\n\t\t The `cn-detail-page__*` classes here and below keep their old names\n\t\t although the markup moved out of CnDetailPage: src/css/detail-page.css\n\t\t targets them, and so may consumer app CSS. Renaming them to match this\n\t\t component would be a silent visual regression across the fleet. -->\n\t\t<CnWidgetWrapper\n\t\t\tv-else-if=\"renderer && isContentOnly && !isBare\"\n\t\t\t:title=\"widget.title || ''\"\n\t\t\ttitleIconPosition=\"left\"\n\t\t\t:showRefresh=\"false\"\n\t\t\t:showRequestFeature=\"false\"\n\t\t\tclass=\"cn-detail-page__catalog-card\">\n\t\t\t<template v-if=\"widget.icon\" #title-icon>\n\t\t\t\t<CnIcon :name=\"widget.icon\" :size=\"20\" />\n\t\t\t</template>\n\t\t\t<template v-if=\"catalogAddEnabled\" #action-items>\n\t\t\t\t<NcActionButton @click=\"invokeCatalogAdd\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ addLabel }}\n\t\t\t\t</NcActionButton>\n\t\t\t</template>\n\t\t\t<component\n\t\t\t\t:is=\"renderer\"\n\t\t\t\tref=\"renderer\"\n\t\t\t\tv-bind=\"rendererProps\" />\n\t\t</CnWidgetWrapper>\n\n\t\t<!-- Registry \"card\" widgets (stat / gauge / delta) render bare tile\n\t\t content, so on a card surface they need the same titled wrapper the\n\t\t dashboard gives them (ADR-062: a lone stat must not read as uncarded\n\t\t floating text). `card-fit` centres the tile and drops the inner\n\t\t scrollbar. No Actions menu: it would hold nothing here and squeezes\n\t\t the title of a narrow tile. -->\n\t\t<CnWidgetWrapper\n\t\t\tv-else-if=\"renderer && isCard && !isBare\"\n\t\t\t:title=\"widget.title || content.title || ''\"\n\t\t\t:showTitle=\"effectiveShowCardTitle\"\n\t\t\ttitleIconPosition=\"left\"\n\t\t\tflush\n\t\t\t:showActions=\"false\"\n\t\t\t:showRefresh=\"false\"\n\t\t\t:showRequestFeature=\"false\"\n\t\t\tclass=\"cn-detail-page__card-fit\">\n\t\t\t<template v-if=\"widget.icon\" #title-icon>\n\t\t\t\t<CnIcon :name=\"widget.icon\" :size=\"20\" />\n\t\t\t</template>\n\t\t\t<component :is=\"renderer\" :content=\"content\" v-bind=\"content\" />\n\t\t</CnWidgetWrapper>\n\n\t\t<!-- Every other content-driven catalog widget, and every bare-mode\n\t\t catalog widget. These self-fetch from OpenRegister. The object\n\t\t context is forwarded too (the same shape CnWidgetGrid merges on the\n\t\t v2 widgets[] path) so object-aware widgets receive it; widgets that\n\t\t declare none of those props ignore them. `content` spreads LAST so\n\t\t explicit widget config still wins. -->\n\t\t<component\n\t\t\t:is=\"renderer\"\n\t\t\tv-else-if=\"renderer\"\n\t\t\tref=\"renderer\"\n\t\t\tv-bind=\"rendererProps\" />\n\n\t\t<!-- No renderer resolves: render nothing, which is what CnDetailPage has\n\t\t always done here. Surfacing an \"unknown widget\" box instead would be\n\t\t more honest, but this is the SLOT FALLBACK for every detail page in\n\t\t the fleet, so an app that declares a custom widget and omits its\n\t\t `#widget-<id>` slot would start showing a placeholder where it shows\n\t\t nothing today. That is its own change, not a side effect of this one. -->\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcEmptyContent } from '@nextcloud/vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnIcon from '../CnIcon/CnIcon.vue'\nimport CnLeafMountHost from '../CnLeafMountHost/CnLeafMountHost.vue'\nimport CnObjectDataWidget from '../CnObjectDataWidget/CnObjectDataWidget.vue'\nimport CnObjectGeoWidget from '../CnObjectGeoWidget/CnObjectGeoWidget.vue'\nimport CnRelatedObjectsWidget from '../CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue'\nimport { useIntegrationRegistry } from '../../composables/useIntegrationRegistry.js'\nimport { isAppInstalled } from '../../utils/appInstalled.js'\nimport { PANEL_ACTION_SINK } from '../../utils/panelActions.js'\nimport {\n\tisCardWidgetDef,\n\tisContentOnlyWidgetDef,\n\tisDataWidgetDef,\n\tisGeoWidgetDef,\n\tisIntegrationWidgetDef,\n\tisRelatedWidgetDef,\n\tresolveRegistryRenderer,\n\twidgetContentOf,\n\twidgetTitleOf,\n} from '../../utils/widgetDispatch.js'\nimport { getWidgetTypeEntry } from '../CnWidgetGrid/dashboardWidgetRegistry.js'\nimport { CnWidgetWrapper } from '../CnWidgetWrapper/index.js'\n\n/**\n * CnDetailWidgetHost — renders ONE detail-page widget definition.\n *\n * This is the dispatch that used to be a ~230-line `v-if` chain inside\n * `CnDetailPage`'s grid slot. It moved out when a second surface needed it:\n * `CnTabsWidget` renders the same widget definitions inside tab panels, and\n * two copies of \"which renderer does this type get\" is the kind of thing that\n * drifts silently. A type added to one and not the other renders correctly on\n * the page and blank in a tab, with nothing in the console either way.\n *\n * ## The two chrome modes\n *\n * `chrome=\"card\"` is the detail-page grid: every widget gets a titled card,\n * because ADR-062 rule 5 says every body widget carries chrome and its manifest\n * title.\n *\n * `chrome=\"bare\"` is a tab panel. The tab strip already shows the title and\n * draws the card, so a second title inside the panel reads as a heading nested\n * in its own heading. Bare mode therefore drops the wrapper and, for\n * integration leaves, renders the provider's `tab` (bare content) instead of\n * its `widget` (which draws its own card). That pairing is not new: it is\n * exactly what `CnIntegrationWidget` already does inside its own panels.\n *\n * ## The `data` widget in bare mode\n *\n * It drops its title, border and padding, so a tab panel holds the content\n * directly rather than a card inside a card.\n *\n * That used not to be possible. `CnWidgetWrapper` renders its actions INSIDE\n * the header and `CnObjectDataWidget` puts its **Save** button there, so\n * hiding the header to remove the duplicate title also removed the only way to\n * commit an inline edit; a silently unsaveable form being worse than a doubled\n * title, the title stayed. The wrapper now renders its header whenever there\n * are actions, with or without a title, so the two are separate requests and a\n * panel can have no title while staying saveable.\n *\n * ```vue\n * <CnDetailWidgetHost\n * :widget=\"def\"\n * :object-id=\"id\"\n * :object=\"record\"\n * register=\"dossiq\"\n * schema=\"case\"\n * chrome=\"bare\" />\n * ```\n */\nexport default {\n\tname: 'CnDetailWidgetHost',\n\n\tcomponents: {\n\t\tCnIcon,\n\t\tCnLeafMountHost,\n\t\tCnObjectDataWidget,\n\t\tCnObjectGeoWidget,\n\t\tCnRelatedObjectsWidget,\n\t\tCnWidgetWrapper,\n\t\tNcActionButton,\n\t\tNcEmptyContent,\n\t\tPlus,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * The surface's panel-action channel, when this host sits inside one.\n\t\t *\n\t\t * Absent on every other surface, which is why the default is null\n\t\t * rather than a required injection.\n\t\t */\n\t\tpanelActionSink: {\n\t\t\tfrom: PANEL_ACTION_SINK,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\t/**\n\t * Re-provide the channel with this panel's widget id baked in.\n\t *\n\t * The widget below does not know its own id on the surface and should not\n\t * have to, so the id is supplied here, where it is already known. Read at\n\t * CALL time rather than captured, so a host whose `widget` changes keeps\n\t * publishing under the right key.\n\t *\n\t * Provides null when there is no surface to publish to, so the widget's own\n\t * injection resolves to null instead of finding a channel that goes nowhere.\n\t *\n\t * @return {object} The narrowed sink, or null.\n\t */\n\tprovide() {\n\t\tconst sink = this.panelActionSink\n\t\treturn {\n\t\t\t[PANEL_ACTION_SINK]: sink\n\t\t\t\t? {\n\t\t\t\t\t\tset: (...args) => sink.set(...this.narrowPanelArgs(args, 'set')),\n\t\t\t\t\t\tclear: (...args) => sink.clear(...this.narrowPanelArgs(args, 'clear')),\n\t\t\t\t\t}\n\t\t\t\t: null,\n\t\t}\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The resolved widget definition: `{ id, type, title, icon, content }`,\n\t\t * plus `integrationId` for `type: 'integration'`.\n\t\t *\n\t\t * Resolved, not a layout item: turning an item into a definition needs\n\t\t * the surface's own widget list, which is the one part the two surfaces\n\t\t * genuinely do differently.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\twidget: {\n\t\t\ttype: Object,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/**\n\t\t * How much chrome to draw around the widget.\n\t\t * - `'card'` — a titled `CnWidgetWrapper`, the detail-page grid default.\n\t\t * - `'bare'` — no wrapper; the caller owns the title and the card.\n\t\t */\n\t\tchrome: {\n\t\t\ttype: String,\n\t\t\tdefault: 'card',\n\t\t\tvalidator: (v) => ['card', 'bare'].includes(v),\n\t\t},\n\n\t\t/** The bound record's id. Present on the first render; `object` is not. */\n\t\tobjectId: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The loaded record, or null while it is still being fetched. */\n\t\tobject: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The resolved object-type slug. */\n\t\tobjectType: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The resolved JSON Schema object, needed by the `data` widget. */\n\t\tschemaObject: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** OpenRegister register slug of the surface. */\n\t\tregister: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** OpenRegister schema slug of the surface. */\n\t\tschema: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The effective object store. */\n\t\tstore: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Rendering surface forwarded to integration widgets (AD-19). */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t},\n\n\t\t/** Object context forwarded to integration widgets. */\n\t\tintegrationContext: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Hide empty properties in the `data` widget. */\n\t\thideEmpty: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Every widget definition on the surface, for a CONTAINER widget to\n\t\t * resolve the children it references by id.\n\t\t *\n\t\t * Only container types receive it (see `rendererProps`). A leaf widget\n\t\t * has no business knowing what else is on the page.\n\t\t *\n\t\t * @type {object[]}\n\t\t */\n\t\tavailableWidgets: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Whether a card widget (stat / gauge / delta) draws the wrapper header.\n\t\t *\n\t\t * A prop, not a computed, because the rule reads the LAYOUT item's\n\t\t * `showTitle` and only the surface holds the layout. Deriving it from the\n\t\t * widget definition alone silently drops `showTitle: false`, which is the\n\t\t * one way a consumer can stop a card printing its title twice: the tile\n\t\t * already draws `content.label` itself.\n\t\t *\n\t\t * Null means \"decide from the definition\", which is what a surface with\n\t\t * no layout (a tab panel) wants.\n\t\t *\n\t\t * @type {boolean|null}\n\t\t */\n\t\tshowCardTitle: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * The consumer's component registry, consulted before the built-in\n\t\t * catalog so a custom widget type overrides a built-in (REQ-MVR-005).\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tcnRegistry: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\t},\n\n\temits: ['geo-saved', 'open-integration'],\n\n\tsetup() {\n\t\t// Every integration component this host renders goes through the\n\t\t// composable's resolvers, never through the registry entry's own\n\t\t// `tab` / `widget`. The resolvers swap a lib-owned entry for THIS\n\t\t// bundle's copy of the component (openregister#1958). The entry's\n\t\t// object belongs to whichever bundle registered it; when that is\n\t\t// OpenRegister's integration-global bundle, rendering it under the\n\t\t// app's Vue leaves `resolveComponent()` without a current instance,\n\t\t// and `<NcButton>` / `<CnDetailCard>` reach the DOM as literal\n\t\t// unknown elements. Seen on dossiq's Notes, Contacts and Files tabs.\n\t\tconst { resolveWidget, resolveBaseWidget, resolveTab, getById } = useIntegrationRegistry()\n\t\treturn {\n\t\t\tresolveRegistryWidget: resolveWidget,\n\t\t\tresolveRegistryBaseWidget: resolveBaseWidget,\n\t\t\tresolveRegistryTab: resolveTab,\n\t\t\tgetRegistryProvider: getById,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the caller owns the title and card.\n\t\t *\n\t\t * @return {boolean} true in bare mode.\n\t\t */\n\t\tisBare() {\n\t\t\treturn this.chrome === 'bare'\n\t\t},\n\n\t\t/**\n\t\t * The widget's stored config blob.\n\t\t *\n\t\t * @return {object} The `content`, or an empty object.\n\t\t */\n\t\tcontent() {\n\t\t\treturn widgetContentOf(this.widget)\n\t\t},\n\n\t\t/**\n\t\t * The title to render. Undefined in bare mode so a self-chromed widget\n\t\t * does not repeat what the tab strip already says.\n\t\t *\n\t\t * @return {string|undefined} The title, or undefined.\n\t\t */\n\t\tresolvedTitle() {\n\t\t\treturn this.isBare ? undefined : widgetTitleOf(this.widget)\n\t\t},\n\n\t\t/**\n\t\t * The app this widget leans on, if it declares one.\n\t\t *\n\t\t * Read from the widget definition first and its `content` second, so a\n\t\t * manifest may put it wherever the rest of that widget's config lives.\n\t\t *\n\t\t * @return {string} A Nextcloud app id, or ''.\n\t\t */\n\t\trequiredApp() {\n\t\t\t// `widget` is declared required, but CnDetailPage renders a host for\n\t\t\t// a layout item whose widget id resolves to nothing — and there is a\n\t\t\t// test that it must not throw for one. This computed runs in the\n\t\t\t// render path BEFORE the branches that tolerate a missing widget, so\n\t\t\t// it is the one that has to survive it.\n\t\t\treturn (this.widget || {}).requiredApp || this.content.requiredApp || ''\n\t\t},\n\n\t\t/**\n\t\t * Whether this widget's backing app is declared and absent.\n\t\t *\n\t\t * @return {boolean} true when the widget must render its set-up state.\n\t\t */\n\t\tmissingApp() {\n\t\t\treturn this.requiredApp !== '' && !isAppInstalled(this.requiredApp)\n\t\t},\n\n\t\t/**\n\t\t * The widget's title, independent of chrome mode.\n\t\t *\n\t\t * `resolvedTitle` is deliberately undefined in bare mode so a tabbed\n\t\t * widget does not repeat what the strip already says. The set-up state\n\t\t * still needs the name to say WHICH widget is inert.\n\t\t *\n\t\t * @return {string} The manifest title.\n\t\t */\n\t\twidgetTitle() {\n\t\t\treturn widgetTitleOf(this.widget) || ''\n\t\t},\n\n\t\t/**\n\t\t * Headline for the set-up state.\n\t\t *\n\t\t * @return {string} e.g. \"Humaniq is not installed\".\n\t\t */\n\t\tmissingAppName() {\n\t\t\treturn t('nextcloud-vue', '{app} is not installed', { app: this.requiredAppLabel })\n\t\t},\n\n\t\t/**\n\t\t * Body for the set-up state, naming what the reader is NOT seeing.\n\t\t *\n\t\t * @return {string} The description.\n\t\t */\n\t\tmissingAppDescription() {\n\t\t\treturn this.widgetTitle\n\t\t\t\t? t('nextcloud-vue', '{title} needs the {app} app. Install and enable it to see this.', {\n\t\t\t\t\t\ttitle: this.widgetTitle,\n\t\t\t\t\t\tapp: this.requiredAppLabel,\n\t\t\t\t\t})\n\t\t\t\t: t('nextcloud-vue', 'Install and enable the {app} app to see this.', {\n\t\t\t\t\t\tapp: this.requiredAppLabel,\n\t\t\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * The app id as a display name — `humaniq` reads as `Humaniq`.\n\t\t *\n\t\t * @return {string} The capitalised app id.\n\t\t */\n\t\trequiredAppLabel() {\n\t\t\treturn this.requiredApp.charAt(0).toUpperCase() + this.requiredApp.slice(1)\n\t\t},\n\n\t\t/** @return {boolean} true for `type: 'data'`. */\n\t\tisData() {\n\t\t\treturn isDataWidgetDef(this.widget)\n\t\t},\n\n\t\t/** @return {boolean} true for `type: 'related'`. */\n\t\tisRelated() {\n\t\t\treturn isRelatedWidgetDef(this.widget)\n\t\t},\n\n\t\t/** @return {boolean} true for `type: 'object-geo'`. */\n\t\tisGeo() {\n\t\t\treturn isGeoWidgetDef(this.widget)\n\t\t},\n\n\t\t/** @return {boolean} true for a registered integration leaf. */\n\t\tisIntegration() {\n\t\t\treturn isIntegrationWidgetDef(this.widget)\n\t\t},\n\n\t\t/** @return {boolean} true for object-list / table / files. */\n\t\tisContentOnly() {\n\t\t\treturn isContentOnlyWidgetDef(this.widget)\n\t\t},\n\n\t\t/** @return {boolean} true when the registry entry declares `card`. */\n\t\tisCard() {\n\t\t\treturn isCardWidgetDef(this.widget)\n\t\t},\n\n\t\t/**\n\t\t * The registered renderer for a content-driven catalog type.\n\t\t *\n\t\t * @return {object|null} The component, or null.\n\t\t */\n\t\trenderer() {\n\t\t\treturn resolveRegistryRenderer(this.widget, this.cnRegistry)\n\t\t},\n\n\t\t/**\n\t\t * The integration descriptor behind an `integration` widget.\n\t\t *\n\t\t * @return {object|null} The provider, or null.\n\t\t */\n\t\tintegrationProvider() {\n\t\t\tif (!this.isIntegration || typeof this.getRegistryProvider !== 'function') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn this.getRegistryProvider(this.widget.integrationId)\n\t\t},\n\n\t\t/**\n\t\t * Whether the integration is a mount-mode leaf (ADR-066), rendered\n\t\t * through CnLeafMountHost rather than as a component.\n\t\t *\n\t\t * @return {boolean} true for a mount-mode leaf.\n\t\t */\n\t\tisMountIntegration() {\n\t\t\tconst provider = this.integrationProvider\n\t\t\treturn Boolean(provider)\n\t\t\t\t&& provider.renderMode === 'mount'\n\t\t\t\t&& typeof provider.mount === 'function'\n\t\t\t\t&& typeof provider.unmount === 'function'\n\t\t},\n\n\t\t/**\n\t\t * Whether this render is using a provider's widget as its bare surface.\n\t\t *\n\t\t * @return {boolean} True when bare AND the provider opted in.\n\t\t */\n\t\tusesBareWidget() {\n\t\t\treturn !!(this.isBare && this.integrationProvider?.bareWidget && this.integrationProvider?.widget)\n\t\t},\n\n\t\t/**\n\t\t * The component for a component-mode integration leaf. Bare mode takes\n\t\t * the provider's `tab`, which is its content without a card, unless the\n\t\t * provider opted into `bareWidget`.\n\t\t *\n\t\t * Each branch resolves through the registry composable rather than\n\t\t * reading `integrationProvider.widget` / `.tab`. The provider is the\n\t\t * shared registry's entry, and its component objects belong to the\n\t\t * bundle that registered it. For a lib-owned id registered by\n\t\t * OpenRegister's bundle, that object renders under the wrong Vue: its\n\t\t * `resolveComponent()` finds no current instance, so `NcButton` and\n\t\t * `CnDetailCard` reach the DOM as literal `<ncbutton>` /\n\t\t * `<cndetailcard>` elements, and a Files tab whose whole body sits\n\t\t * inside that card looks empty. The resolvers hand back this bundle's\n\t\t * own copy for lib-owned ids and the stored object for consumer ids.\n\t\t *\n\t\t * @return {object|null} The component, or null.\n\t\t */\n\t\tintegrationComponent() {\n\t\t\tif (!this.isIntegration) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst id = this.widget.integrationId\n\t\t\t// `bareWidget` lets a provider say its WIDGET is already bare, so a\n\t\t\t// tab panel gets the widget surface instead of the sidebar one.\n\t\t\t// The default below (prefer `tab`) assumes every `widget` draws its\n\t\t\t// own card, which is not true: `notes`' widget is an adapter around\n\t\t\t// CnNotesCard, whose root carries no border, background or padding.\n\t\t\t// Preferring `tab` there swapped an inline compose textarea for the\n\t\t\t// sidebar's collapsed \"Add note\" button and dressed the panel in\n\t\t\t// sidebar CSS, which is what made the tabbed leaves look like they\n\t\t\t// had lost their styling. Opt-in rather than a blanket switch: the\n\t\t\t// other providers keep today's behaviour until each is checked.\n\t\t\tif (this.usesBareWidget && typeof this.resolveRegistryBaseWidget === 'function') {\n\t\t\t\treturn this.resolveRegistryBaseWidget(id)\n\t\t\t}\n\t\t\tif (this.isBare && this.integrationProvider?.tab && typeof this.resolveRegistryTab === 'function') {\n\t\t\t\treturn this.resolveRegistryTab(id)\n\t\t\t}\n\t\t\tif (typeof this.resolveRegistryWidget !== 'function') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn this.resolveRegistryWidget(id, this.surface)\n\t\t},\n\n\t\t/**\n\t\t * The object context every integration surface is handed.\n\t\t *\n\t\t * @return {object} `{ register, schema, objectId }`.\n\t\t */\n\t\tderivedIntegrationContext() {\n\t\t\treturn this.integrationContext || {\n\t\t\t\tregister: this.register || '',\n\t\t\t\tschema: this.schema || this.objectType || '',\n\t\t\t\tobjectId: this.objectId ? String(this.objectId) : '',\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Props for an integration widget or tab.\n\t\t *\n\t\t * The title is forwarded because `CnIntegrationCard`'s `cardTitle` falls\n\t\t * back to the raw `integrationId` without it, so a widget the manifest\n\t\t * calls \"Class space\" rendered as \"talk\". It is placed BEFORE `def.props`\n\t\t * so an explicit per-widget prop still wins.\n\t\t *\n\t\t * @return {object} The prop bag.\n\t\t */\n\t\tintegrationProps() {\n\t\t\treturn {\n\t\t\t\tsurface: this.surface,\n\t\t\t\ttitle: this.widget?.title || '',\n\t\t\t\t// A provider opting into `bareWidget` is being rendered where the\n\t\t\t\t// tab strip already supplies the card and the title, so tell it to\n\t\t\t\t// drop its own. Without this the panel gets a titled card inside\n\t\t\t\t// the tabs card, repeating the label the open tab already shows.\n\t\t\t\t...(this.usesBareWidget ? { chromeless: true } : {}),\n\t\t\t\t...this.derivedIntegrationContext,\n\t\t\t\t...(this.widget?.props || {}),\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Props handed to a mount-mode leaf.\n\t\t *\n\t\t * @return {object} The mount prop bag.\n\t\t */\n\t\tintegrationMountProps() {\n\t\t\treturn {\n\t\t\t\tsurface: this.surface,\n\t\t\t\t...this.derivedIntegrationContext,\n\t\t\t\tintegrationContext: this.derivedIntegrationContext,\n\t\t\t\t...(this.widget?.props || {}),\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Props for a catalog renderer: its content plus the object context, in\n\t\t * the shape CnWidgetGrid merges on the v2 widgets[] path. `content`\n\t\t * spreads last so explicit widget config still wins.\n\t\t *\n\t\t * @return {object} The prop bag.\n\t\t */\n\t\trendererProps() {\n\t\t\tconst base = {\n\t\t\t\tcontent: this.content,\n\t\t\t\tobjectId: this.objectId,\n\t\t\t\tregister: this.register,\n\t\t\t\tschema: this.schema,\n\t\t\t\tobjectData: this.object,\n\t\t\t\tobjectType: this.objectType,\n\t\t\t\tstore: this.store,\n\t\t\t}\n\t\t\t// A container widget renders other widgets, so it needs what a\n\t\t\t// surface knows and a leaf does not: the sibling definitions to\n\t\t\t// resolve its children against, and the context those children will\n\t\t\t// need. Handing this to every widget instead would put objects on\n\t\t\t// the DOM as stringified attributes for the ones that ignore them.\n\t\t\tif (this.isContainer) {\n\t\t\t\tObject.assign(base, {\n\t\t\t\t\tavailableWidgets: this.availableWidgets,\n\t\t\t\t\tschemaObject: this.schemaObject,\n\t\t\t\t\tintegrationContext: this.integrationContext,\n\t\t\t\t\tcnRegistry: this.cnRegistry,\n\t\t\t\t\tsurface: this.surface,\n\t\t\t\t})\n\t\t\t}\n\t\t\treturn { ...base, ...this.content }\n\t\t},\n\n\t\t/**\n\t\t * Whether the type declares itself a container (registry `container`).\n\t\t *\n\t\t * @return {boolean} true for a widget that renders other widgets.\n\t\t */\n\t\tisContainer() {\n\t\t\tif (!this.widget?.type) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst entry = getWidgetTypeEntry(this.widget.type)\n\t\t\treturn Boolean(entry && entry.container === true)\n\t\t},\n\n\t\t/**\n\t\t * Whether a catalog list widget offers the Add action (ADR-062:\n\t\t * collections carry their create affordance in the card's Actions menu).\n\t\t * Files widgets carry their own upload toolbar, so they are excluded.\n\t\t *\n\t\t * @return {boolean} true when Add should render.\n\t\t */\n\t\tcatalogAddEnabled() {\n\t\t\tif (!['object-list', 'table'].includes(this.widget?.type)) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn this.content.allowCreate !== false\n\t\t},\n\n\t\t/**\n\t\t * The card header rule, with the surface's layout-driven answer winning\n\t\t * when it supplied one.\n\t\t *\n\t\t * @return {boolean} true when the wrapper header renders.\n\t\t */\n\t\teffectiveShowCardTitle() {\n\t\t\tif (this.showCardTitle !== null) {\n\t\t\t\treturn this.showCardTitle\n\t\t\t}\n\t\t\treturn this.widget?.title !== undefined || this.content.title !== undefined\n\t\t},\n\n\t\t/** @return {string} Label for the catalog Add action. */\n\t\taddLabel() {\n\t\t\treturn t('nextcloud-vue', 'Add')\n\t\t},\n\n\t\t/**\n\t\t * The items THIS HOST offers to the surface, as opposed to the ones the\n\t\t * widget inside it publishes.\n\t\t *\n\t\t * There is one: the catalog Add. It is drawn in the host's own card\n\t\t * header, and that header only exists off a panel, so a Documents or\n\t\t * Files tab simply had no way to add anything. The data widget's\n\t\t * Metadata went the same way until it was published, and this is the\n\t\t * same defect one widget type along.\n\t\t *\n\t\t * Empty off a panel, where the header draws the item itself and\n\t\t * publishing would put it in two menus at once.\n\t\t *\n\t\t * @return {object[]} PanelAction descriptors.\n\t\t */\n\t\townPanelActions() {\n\t\t\tif (!this.isBare || !this.catalogAddEnabled) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn [{\n\t\t\t\tkey: 'catalog-add',\n\t\t\t\tlabel: this.addLabel,\n\t\t\t\ticon: 'Plus',\n\t\t\t\trun: () => this.invokeCatalogAdd(),\n\t\t\t}]\n\t\t},\n\t},\n\n\twatch: {\n\t\townPanelActions: {\n\t\t\thandler() {\n\t\t\t\tthis.publishOwnPanelActions()\n\t\t\t},\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.publishOwnPanelActions()\n\t},\n\n\tbeforeUnmount() {\n\t\t// A closed tab's panel can be torn down while the strip lives on, and an\n\t\t// item whose host is gone would call into nothing.\n\t\tif (this.panelActionSink) {\n\t\t\tthis.panelActionSink.clear(this.widget?.id, 'host')\n\t\t}\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Re-key a call on the channel this host provides onto the one it holds.\n\t\t * A widget publishes `set(items)`; a host nested in this one speaks the\n\t\t * surface's `set(id, items, source)`. Items are always an array and an\n\t\t * id never is, which tells the two apart.\n\t\t *\n\t\t * @param {Array} args The arguments as called.\n\t\t * @param {'set'|'clear'} kind Which channel method was called.\n\t\t * @return {Array} Arguments for the held channel.\n\t\t */\n\t\tnarrowPanelArgs(args, kind) {\n\t\t\tconst id = this.widget?.id\n\t\t\tif (kind === 'set') {\n\t\t\t\treturn Array.isArray(args[0])\n\t\t\t\t\t? [id, args[0], 'widget']\n\t\t\t\t\t: [id, args[1], args[2] || 'widget']\n\t\t\t}\n\t\t\treturn [id, args.length > 1 ? (args[1] || 'widget') : 'widget']\n\t\t},\n\n\t\t/**\n\t\t * Publish or withdraw this host's OWN items on the surface.\n\t\t *\n\t\t * Published under the `host` source so the widget inside the panel can\n\t\t * publish its own without either replacing the other.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tpublishOwnPanelActions() {\n\t\t\tif (!this.panelActionSink) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst id = this.widget?.id\n\t\t\tif (!id) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.ownPanelActions.length) {\n\t\t\t\tthis.panelActionSink.set(id, this.ownPanelActions, 'host')\n\t\t\t} else {\n\t\t\t\tthis.panelActionSink.clear(id, 'host')\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Re-emit the geo widget's save so the surface can reload the record.\n\t\t *\n\t\t * @param {object} geo The saved geometry.\n\t\t * @return {void}\n\t\t */\n\t\tonGeoSaved(geo) {\n\t\t\t/**\n\t\t\t * @event geo-saved Emitted when the geo widget saved a geometry.\n\t\t\t * @type {object}\n\t\t\t */\n\t\t\tthis.$emit('geo-saved', geo)\n\t\t},\n\n\t\t/**\n\t\t * Re-emit the related widget's request to open an integration.\n\t\t *\n\t\t * @param {string} integrationId The integration to open.\n\t\t * @return {void}\n\t\t */\n\t\tonOpenIntegration(integrationId) {\n\t\t\t/**\n\t\t\t * @event open-integration Emitted when the related widget asks to open an integration.\n\t\t\t * @type {string}\n\t\t\t */\n\t\t\tthis.$emit('open-integration', integrationId)\n\t\t},\n\n\t\t/**\n\t\t * Delegate to the rendered list widget's own create affordance, so the\n\t\t * card's Add action and the widget's footer button open the same form.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tinvokeCatalogAdd() {\n\t\t\tconst child = this.$refs.renderer\n\t\t\tconst target = Array.isArray(child) ? child[0] : child\n\t\t\tif (target && typeof target.openCreate === 'function') {\n\t\t\t\ttarget.openCreate()\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-detail-widget-host,\n.cn-detail-widget-host > * {\n\theight: 100%;\n}\n\n/* A bare host is a tab panel's body: it fills the panel and lets the panel\n scroll, rather than growing a second scroll region of its own. */\n.cn-detail-widget-host--bare {\n\tmin-height: 0;\n}\n</style>\n"],"names":["CnIcon","CnLeafMountHost","CnObjectDataWidget","CnObjectGeoWidget","CnRelatedObjectsWidget","CnWidgetWrapper","t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnDetailWidgetHost.vue2.js","sources":["../../../../src/components/CnDetailWidgetHost/CnDetailWidgetHost.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-detail-widget-host\" :class=\"`cn-detail-widget-host--${chrome}`\">\n\t\t<!-- `type: 'data'` — the schema-driven data widget. In bare mode it is\n\t\t `chromeless`: no border, no background, no padding, no title row and\n\t\t no header divider, so the tab panel holds the content directly\n\t\t instead of a card inside a card.\n\n\t\t One prop, not three. `chromeless` is the word this component already\n\t\t used for the same thing on the integration path (`bareWidget`), and\n\t\t the three it replaces (`show-title`, `borderless`, `flush`) were a\n\t\t decomposition every caller had to remember in full — miss one and\n\t\t the panel keeps half a card.\n\n\t\t Chromeless does NOT mean no wrapper element. The wrapper's content\n\t\t node is what the library's table and detail-page CSS key on.\n\n\t\t It used to keep the whole card here, because its Save button lives\n\t\t in CnWidgetWrapper's header and hiding the header hid Save with it.\n\t\t The wrapper renders its header whenever there are actions, with or\n\t\t without a title, so the panel gets no title while an inline edit\n\t\t stays committable. Controls are not chrome.\n\n\t\t Passing `undefined` was never enough on its own: `title` carries a\n\t\t DEFAULT of \"Data\", so an unset title became a \"Data\" heading that\n\t\t nobody chose. The title is still passed, and now names the content\n\t\t region for a screen reader instead of printing a second heading.\n\n\t\t The overflow menu goes too, which `chromeless` does not cover. It\n\t\t left a 59px band whose only content was an Actions menu about eight\n\t\t pixels from the tab strip's own, and two menus on one row read as the\n\t\t same card inside a card in miniature.\n\n\t\t Nothing is lost, but that took a second piece of work rather than\n\t\t being true on its own. Suppressing the menu takes its two items with\n\t\t it, and Metadata had no other home anywhere: the page header carries\n\t\t no equivalent, so it became unreachable inside a panel. Edit is the\n\t\t softer case. CnDetailPage does carry a record Edit button, but it\n\t\t opens the form the PAGE configures, not the subset THIS widget\n\t\t declares through `overrides`, `include` and `exclude`, so a tabbed\n\t\t widget showing eight of forty fields lost the form scoped to its\n\t\t eight. Both items are now published to the host surface and render in\n\t\t its menu; see utils/panelActions.js. Inline editing was never\n\t\t affected: a cell opens its editor when clicked, and Save and Discard\n\t\t arrive in the header the moment there is an edit to commit. -->\n\t\t<!-- `requiredApp` names another Nextcloud app this widget leans on. When\n\t\t that app is absent the widget renders its NORMAL chrome plus a\n\t\t set-up state, and asks its backend NOTHING.\n\n\t\t It renders rather than hides on purpose. A hidden widget leaves a\n\t\t hole a reader cannot interpret, and the alternative — letting the\n\t\t query run — is worse: an aggregation over an absent app's register\n\t\t 404s and the tile shows `0`, which is exactly what a real zero\n\t\t shows. dossiq's hours tile did that on every install without\n\t\t humaniq, and looked correct doing it.\n\n\t\t \"NORMAL chrome\" means the chrome of the surface it is on. In a tab\n\t\t panel that is no chrome, same as every other widget there: a set-up\n\t\t state is still a widget, and a bordered card around it inside the\n\t\t panel is the same doubled card this file removes below. The title\n\t\t is now passed in bare mode too, where it names the content region\n\t\t rather than printing a heading the tab already carries. -->\n\t\t<CnWidgetWrapper\n\t\t\tv-if=\"missingApp\"\n\t\t\t:title=\"widgetTitle\"\n\t\t\t:chromeless=\"isBare\"\n\t\t\ttitleIconPosition=\"left\"\n\t\t\t:showRefresh=\"false\"\n\t\t\t:showRequestFeature=\"false\">\n\t\t\t<template v-if=\"widget && widget.icon\" #title-icon>\n\t\t\t\t<CnIcon :name=\"widget.icon\" :size=\"20\" />\n\t\t\t</template>\n\t\t\t<NcEmptyContent class=\"cn-requires-app\" :name=\"missingAppName\" :description=\"missingAppDescription\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<CnIcon :name=\"(widget && widget.icon) || 'PuzzleOutline'\" :size=\"32\" />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\t\t</CnWidgetWrapper>\n\n\t\t<CnObjectDataWidget\n\t\t\tv-else-if=\"isData && schemaObject\"\n\t\t\t:title=\"resolvedTitle\"\n\t\t\t:chromeless=\"isBare\"\n\t\t\t:showActions=\"!isBare\"\n\t\t\t:icon=\"widget.icon || null\"\n\t\t\t:schema=\"schemaObject\"\n\t\t\t:objectData=\"object\"\n\t\t\t:objectType=\"objectType\"\n\t\t\t:store=\"store\"\n\t\t\t:overrides=\"content.overrides || {}\"\n\t\t\t:include=\"content.include || null\"\n\t\t\t:exclude=\"content.exclude || []\"\n\t\t\t:hideEmpty=\"content.hideEmpty === true || hideEmpty\"\n\t\t\t:columns=\"content.columns || 3\"\n\t\t\t:collapsedFields=\"content.collapsedFields ?? null\"\n\t\t\t:editable=\"content.editable !== false\" />\n\n\t\t<!-- `type: 'related'` — resolves this object's relations and links into\n\t\t integrations. Honours bare mode via its own `bare` prop. -->\n\t\t<CnRelatedObjectsWidget\n\t\t\tv-else-if=\"isRelated\"\n\t\t\t:title=\"resolvedTitle\"\n\t\t\t:bare=\"isBare\"\n\t\t\t:objectType=\"objectType\"\n\t\t\t:objectId=\"objectId\"\n\t\t\t:objectData=\"object\"\n\t\t\t:register=\"register\"\n\t\t\t:schema=\"schema\"\n\t\t\t:store=\"store\"\n\t\t\t:includeGroups=\"content.groups || []\"\n\t\t\t:hideSingleTabTitle=\"content.hideSingleTabTitle !== false\"\n\t\t\t:showTotalCount=\"content.showTotalCount !== false\"\n\t\t\t@openIntegration=\"onOpenIntegration\" />\n\n\t\t<!-- `type: 'object-geo'` — view/edit the object's `@self.geo` on a map. -->\n\t\t<CnObjectGeoWidget\n\t\t\tv-else-if=\"isGeo\"\n\t\t\t:title=\"resolvedTitle\"\n\t\t\t:objectId=\"objectId\"\n\t\t\t:objectData=\"object\"\n\t\t\t:register=\"register\"\n\t\t\t:schema=\"schema\"\n\t\t\t:editable=\"content.editable !== false\"\n\t\t\t:addressSearch=\"content.addressSearch === true\"\n\t\t\t:basemap=\"content.basemap || 'standard'\"\n\t\t\t:allowBasemapSwitch=\"content.allowBasemapSwitch === true\"\n\t\t\t:fitControl=\"content.fitControl !== false\"\n\t\t\t:locateControl=\"content.locateControl !== false\"\n\t\t\t:fullscreenControl=\"content.fullscreenControl !== false\"\n\t\t\t:height=\"content.height || '360px'\"\n\t\t\t:defaultZoom=\"content.defaultZoom || 7\"\n\t\t\t@saved=\"onGeoSaved\" />\n\n\t\t<!-- Mount-mode integration leaf (openregister#2127): a bare host-owned\n\t\t element the leaf mounts its own framework into. -->\n\t\t<CnLeafMountHost\n\t\t\tv-else-if=\"isMountIntegration\"\n\t\t\t:provider=\"integrationProvider\"\n\t\t\t:mountProps=\"integrationMountProps\" />\n\n\t\t<!-- Integration leaf, component mode. In BARE mode this renders the\n\t\t provider's `tab` (its bare content) rather than its `widget` (which\n\t\t draws its own card). That pairing is not an invention here:\n\t\t CnIntegrationWidget already renders `provider.tab` inside its own\n\t\t tab panels for exactly this reason. -->\n\t\t<component\n\t\t\t:is=\"integrationComponent\"\n\t\t\tv-else-if=\"isIntegration && integrationComponent\"\n\t\t\tv-bind=\"integrationProps\" />\n\n\t\t<!-- Content-only catalog widgets (object-list / table / files) render\n\t\t bare tables. On a card surface they get the titled wrapper (ADR-062:\n\t\t every body widget has chrome and its manifest title); inside a tab\n\t\t the strip supplies both, so they render as-is.\n\n\t\t The `cn-detail-page__*` classes here and below keep their old names\n\t\t although the markup moved out of CnDetailPage: src/css/detail-page.css\n\t\t targets them, and so may consumer app CSS. Renaming them to match this\n\t\t component would be a silent visual regression across the fleet. -->\n\t\t<CnWidgetWrapper\n\t\t\tv-else-if=\"renderer && isContentOnly && !isBare\"\n\t\t\t:title=\"widget.title || ''\"\n\t\t\ttitleIconPosition=\"left\"\n\t\t\t:showRefresh=\"false\"\n\t\t\t:showRequestFeature=\"false\"\n\t\t\tclass=\"cn-detail-page__catalog-card\">\n\t\t\t<template v-if=\"widget.icon\" #title-icon>\n\t\t\t\t<CnIcon :name=\"widget.icon\" :size=\"20\" />\n\t\t\t</template>\n\t\t\t<template v-if=\"catalogAddEnabled\" #action-items>\n\t\t\t\t<NcActionButton @click=\"invokeCatalogAdd\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ addLabel }}\n\t\t\t\t</NcActionButton>\n\t\t\t</template>\n\t\t\t<component\n\t\t\t\t:is=\"renderer\"\n\t\t\t\tref=\"renderer\"\n\t\t\t\tv-bind=\"rendererProps\" />\n\t\t</CnWidgetWrapper>\n\n\t\t<!-- Registry \"card\" widgets (stat / gauge / delta) render bare tile\n\t\t content, so on a card surface they need the same titled wrapper the\n\t\t dashboard gives them (ADR-062: a lone stat must not read as uncarded\n\t\t floating text). `card-fit` centres the tile and drops the inner\n\t\t scrollbar. No Actions menu: it would hold nothing here and squeezes\n\t\t the title of a narrow tile. -->\n\t\t<CnWidgetWrapper\n\t\t\tv-else-if=\"renderer && isCard && !isBare\"\n\t\t\t:title=\"widget.title || content.title || ''\"\n\t\t\t:showTitle=\"effectiveShowCardTitle\"\n\t\t\ttitleIconPosition=\"left\"\n\t\t\tflush\n\t\t\t:showActions=\"false\"\n\t\t\t:showRefresh=\"false\"\n\t\t\t:showRequestFeature=\"false\"\n\t\t\tclass=\"cn-detail-page__card-fit\">\n\t\t\t<template v-if=\"widget.icon\" #title-icon>\n\t\t\t\t<CnIcon :name=\"widget.icon\" :size=\"20\" />\n\t\t\t</template>\n\t\t\t<component :is=\"renderer\" :content=\"content\" v-bind=\"content\" />\n\t\t</CnWidgetWrapper>\n\n\t\t<!-- Every other content-driven catalog widget, and every bare-mode\n\t\t catalog widget. These self-fetch from OpenRegister. The object\n\t\t context is forwarded too (the same shape CnWidgetGrid merges on the\n\t\t v2 widgets[] path) so object-aware widgets receive it; widgets that\n\t\t declare none of those props ignore them. `content` spreads LAST so\n\t\t explicit widget config still wins. -->\n\t\t<component\n\t\t\t:is=\"renderer\"\n\t\t\tv-else-if=\"renderer\"\n\t\t\tref=\"renderer\"\n\t\t\tv-bind=\"rendererProps\" />\n\n\t\t<!-- No renderer resolves: render nothing, which is what CnDetailPage has\n\t\t always done here. Surfacing an \"unknown widget\" box instead would be\n\t\t more honest, but this is the SLOT FALLBACK for every detail page in\n\t\t the fleet, so an app that declares a custom widget and omits its\n\t\t `#widget-<id>` slot would start showing a placeholder where it shows\n\t\t nothing today. That is its own change, not a side effect of this one. -->\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcEmptyContent } from '@nextcloud/vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnIcon from '../CnIcon/CnIcon.vue'\nimport CnLeafMountHost from '../CnLeafMountHost/CnLeafMountHost.vue'\nimport CnObjectDataWidget from '../CnObjectDataWidget/CnObjectDataWidget.vue'\nimport CnObjectGeoWidget from '../CnObjectGeoWidget/CnObjectGeoWidget.vue'\nimport CnRelatedObjectsWidget from '../CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue'\nimport { useIntegrationRegistry } from '../../composables/useIntegrationRegistry.js'\nimport { isAppInstalled } from '../../utils/appInstalled.js'\nimport { PANEL_ACTION_SINK } from '../../utils/panelActions.js'\nimport {\n\tisCardWidgetDef,\n\tisContentOnlyWidgetDef,\n\tisDataWidgetDef,\n\tisGeoWidgetDef,\n\tisIntegrationWidgetDef,\n\tisRelatedWidgetDef,\n\tresolveRegistryRenderer,\n\twidgetContentOf,\n\twidgetTitleOf,\n} from '../../utils/widgetDispatch.js'\nimport { getWidgetTypeEntry } from '../CnWidgetGrid/dashboardWidgetRegistry.js'\nimport { CnWidgetWrapper } from '../CnWidgetWrapper/index.js'\n\n/**\n * CnDetailWidgetHost — renders ONE detail-page widget definition.\n *\n * This is the dispatch that used to be a ~230-line `v-if` chain inside\n * `CnDetailPage`'s grid slot. It moved out when a second surface needed it:\n * `CnTabsWidget` renders the same widget definitions inside tab panels, and\n * two copies of \"which renderer does this type get\" is the kind of thing that\n * drifts silently. A type added to one and not the other renders correctly on\n * the page and blank in a tab, with nothing in the console either way.\n *\n * ## The two chrome modes\n *\n * `chrome=\"card\"` is the detail-page grid: every widget gets a titled card,\n * because ADR-062 rule 5 says every body widget carries chrome and its manifest\n * title.\n *\n * `chrome=\"bare\"` is a tab panel. The tab strip already shows the title and\n * draws the card, so a second title inside the panel reads as a heading nested\n * in its own heading. Bare mode therefore drops the wrapper and, for\n * integration leaves, renders the provider's `tab` (bare content) instead of\n * its `widget` (which draws its own card). That pairing is not new: it is\n * exactly what `CnIntegrationWidget` already does inside its own panels.\n *\n * ## The `data` widget in bare mode\n *\n * It drops its title, border and padding, so a tab panel holds the content\n * directly rather than a card inside a card.\n *\n * That used not to be possible. `CnWidgetWrapper` renders its actions INSIDE\n * the header and `CnObjectDataWidget` puts its **Save** button there, so\n * hiding the header to remove the duplicate title also removed the only way to\n * commit an inline edit; a silently unsaveable form being worse than a doubled\n * title, the title stayed. The wrapper now renders its header whenever there\n * are actions, with or without a title, so the two are separate requests and a\n * panel can have no title while staying saveable.\n *\n * ```vue\n * <CnDetailWidgetHost\n * :widget=\"def\"\n * :object-id=\"id\"\n * :object=\"record\"\n * register=\"dossiq\"\n * schema=\"case\"\n * chrome=\"bare\" />\n * ```\n */\nexport default {\n\tname: 'CnDetailWidgetHost',\n\n\tcomponents: {\n\t\tCnIcon,\n\t\tCnLeafMountHost,\n\t\tCnObjectDataWidget,\n\t\tCnObjectGeoWidget,\n\t\tCnRelatedObjectsWidget,\n\t\tCnWidgetWrapper,\n\t\tNcActionButton,\n\t\tNcEmptyContent,\n\t\tPlus,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * The surface's panel-action channel, when this host sits inside one.\n\t\t *\n\t\t * Absent on every other surface, which is why the default is null\n\t\t * rather than a required injection.\n\t\t */\n\t\tpanelActionSink: {\n\t\t\tfrom: PANEL_ACTION_SINK,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\t/**\n\t * Re-provide the channel with this panel's widget id baked in.\n\t *\n\t * The widget below does not know its own id on the surface and should not\n\t * have to, so the id is supplied here, where it is already known. Read at\n\t * CALL time rather than captured, so a host whose `widget` changes keeps\n\t * publishing under the right key.\n\t *\n\t * Provides null when there is no surface to publish to, so the widget's own\n\t * injection resolves to null instead of finding a channel that goes nowhere.\n\t *\n\t * @return {object} The narrowed sink, or null.\n\t */\n\tprovide() {\n\t\tconst sink = this.panelActionSink\n\t\treturn {\n\t\t\t[PANEL_ACTION_SINK]: sink\n\t\t\t\t? {\n\t\t\t\t\t\tset: (...args) => sink.set(...this.narrowPanelArgs(args, 'set')),\n\t\t\t\t\t\tclear: (...args) => sink.clear(...this.narrowPanelArgs(args, 'clear')),\n\t\t\t\t\t}\n\t\t\t\t: null,\n\t\t}\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The resolved widget definition: `{ id, type, title, icon, content }`,\n\t\t * plus `integrationId` for `type: 'integration'`.\n\t\t *\n\t\t * Resolved, not a layout item: turning an item into a definition needs\n\t\t * the surface's own widget list, which is the one part the two surfaces\n\t\t * genuinely do differently.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\twidget: {\n\t\t\ttype: Object,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/**\n\t\t * How much chrome to draw around the widget.\n\t\t * - `'card'` — a titled `CnWidgetWrapper`, the detail-page grid default.\n\t\t * - `'bare'` — no wrapper; the caller owns the title and the card.\n\t\t */\n\t\tchrome: {\n\t\t\ttype: String,\n\t\t\tdefault: 'card',\n\t\t\tvalidator: (v) => ['card', 'bare'].includes(v),\n\t\t},\n\n\t\t/** The bound record's id. Present on the first render; `object` is not. */\n\t\tobjectId: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The loaded record, or null while it is still being fetched. */\n\t\tobject: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The resolved object-type slug. */\n\t\tobjectType: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The resolved JSON Schema object, needed by the `data` widget. */\n\t\tschemaObject: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** OpenRegister register slug of the surface. */\n\t\tregister: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** OpenRegister schema slug of the surface. */\n\t\tschema: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The effective object store. */\n\t\tstore: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Rendering surface forwarded to integration widgets (AD-19). */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t},\n\n\t\t/** Object context forwarded to integration widgets. */\n\t\tintegrationContext: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Hide empty properties in the `data` widget. */\n\t\thideEmpty: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Every widget definition on the surface, for a CONTAINER widget to\n\t\t * resolve the children it references by id.\n\t\t *\n\t\t * Only container types receive it (see `rendererProps`). A leaf widget\n\t\t * has no business knowing what else is on the page.\n\t\t *\n\t\t * @type {object[]}\n\t\t */\n\t\tavailableWidgets: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Whether a card widget (stat / gauge / delta) draws the wrapper header.\n\t\t *\n\t\t * A prop, not a computed, because the rule reads the LAYOUT item's\n\t\t * `showTitle` and only the surface holds the layout. Deriving it from the\n\t\t * widget definition alone silently drops `showTitle: false`, which is the\n\t\t * one way a consumer can stop a card printing its title twice: the tile\n\t\t * already draws `content.label` itself.\n\t\t *\n\t\t * Null means \"decide from the definition\", which is what a surface with\n\t\t * no layout (a tab panel) wants.\n\t\t *\n\t\t * @type {boolean|null}\n\t\t */\n\t\tshowCardTitle: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * The consumer's component registry, consulted before the built-in\n\t\t * catalog so a custom widget type overrides a built-in (REQ-MVR-005).\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tcnRegistry: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\t},\n\n\temits: ['geo-saved', 'open-integration'],\n\n\tsetup() {\n\t\t// Every integration component this host renders goes through the\n\t\t// composable's resolvers, never through the registry entry's own\n\t\t// `tab` / `widget`. The resolvers swap a lib-owned entry for THIS\n\t\t// bundle's copy of the component (openregister#1958). The entry's\n\t\t// object belongs to whichever bundle registered it; when that is\n\t\t// OpenRegister's integration-global bundle, rendering it under the\n\t\t// app's Vue leaves `resolveComponent()` without a current instance,\n\t\t// and `<NcButton>` / `<CnDetailCard>` reach the DOM as literal\n\t\t// unknown elements. Seen on dossiq's Notes, Contacts and Files tabs.\n\t\tconst { resolveWidget, resolveBaseWidget, resolveTab, getById } = useIntegrationRegistry()\n\t\treturn {\n\t\t\tresolveRegistryWidget: resolveWidget,\n\t\t\tresolveRegistryBaseWidget: resolveBaseWidget,\n\t\t\tresolveRegistryTab: resolveTab,\n\t\t\tgetRegistryProvider: getById,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the caller owns the title and card.\n\t\t *\n\t\t * @return {boolean} true in bare mode.\n\t\t */\n\t\tisBare() {\n\t\t\treturn this.chrome === 'bare'\n\t\t},\n\n\t\t/**\n\t\t * The widget's stored config blob.\n\t\t *\n\t\t * @return {object} The `content`, or an empty object.\n\t\t */\n\t\tcontent() {\n\t\t\treturn widgetContentOf(this.widget)\n\t\t},\n\n\t\t/**\n\t\t * The title to render. Undefined in bare mode so a self-chromed widget\n\t\t * does not repeat what the tab strip already says.\n\t\t *\n\t\t * @return {string|undefined} The title, or undefined.\n\t\t */\n\t\tresolvedTitle() {\n\t\t\treturn this.isBare ? undefined : widgetTitleOf(this.widget)\n\t\t},\n\n\t\t/**\n\t\t * The app this widget leans on, if it declares one.\n\t\t *\n\t\t * Read from the widget definition first and its `content` second, so a\n\t\t * manifest may put it wherever the rest of that widget's config lives.\n\t\t *\n\t\t * @return {string} A Nextcloud app id, or ''.\n\t\t */\n\t\trequiredApp() {\n\t\t\t// `widget` is declared required, but CnDetailPage renders a host for\n\t\t\t// a layout item whose widget id resolves to nothing — and there is a\n\t\t\t// test that it must not throw for one. This computed runs in the\n\t\t\t// render path BEFORE the branches that tolerate a missing widget, so\n\t\t\t// it is the one that has to survive it.\n\t\t\treturn (this.widget || {}).requiredApp || this.content.requiredApp || ''\n\t\t},\n\n\t\t/**\n\t\t * Whether this widget's backing app is declared and absent.\n\t\t *\n\t\t * @return {boolean} true when the widget must render its set-up state.\n\t\t */\n\t\tmissingApp() {\n\t\t\treturn this.requiredApp !== '' && !isAppInstalled(this.requiredApp)\n\t\t},\n\n\t\t/**\n\t\t * The widget's title, independent of chrome mode.\n\t\t *\n\t\t * `resolvedTitle` is deliberately undefined in bare mode so a tabbed\n\t\t * widget does not repeat what the strip already says. The set-up state\n\t\t * still needs the name to say WHICH widget is inert.\n\t\t *\n\t\t * @return {string} The manifest title.\n\t\t */\n\t\twidgetTitle() {\n\t\t\treturn widgetTitleOf(this.widget) || ''\n\t\t},\n\n\t\t/**\n\t\t * Headline for the set-up state.\n\t\t *\n\t\t * @return {string} e.g. \"Humaniq is not installed\".\n\t\t */\n\t\tmissingAppName() {\n\t\t\treturn t('nextcloud-vue', '{app} is not installed', { app: this.requiredAppLabel })\n\t\t},\n\n\t\t/**\n\t\t * Body for the set-up state, naming what the reader is NOT seeing.\n\t\t *\n\t\t * @return {string} The description.\n\t\t */\n\t\tmissingAppDescription() {\n\t\t\treturn this.widgetTitle\n\t\t\t\t? t('nextcloud-vue', '{title} needs the {app} app. Install and enable it to see this.', {\n\t\t\t\t\t\ttitle: this.widgetTitle,\n\t\t\t\t\t\tapp: this.requiredAppLabel,\n\t\t\t\t\t})\n\t\t\t\t: t('nextcloud-vue', 'Install and enable the {app} app to see this.', {\n\t\t\t\t\t\tapp: this.requiredAppLabel,\n\t\t\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * The app id as a display name — `humaniq` reads as `Humaniq`.\n\t\t *\n\t\t * @return {string} The capitalised app id.\n\t\t */\n\t\trequiredAppLabel() {\n\t\t\treturn this.requiredApp.charAt(0).toUpperCase() + this.requiredApp.slice(1)\n\t\t},\n\n\t\t/** @return {boolean} true for `type: 'data'`. */\n\t\tisData() {\n\t\t\treturn isDataWidgetDef(this.widget)\n\t\t},\n\n\t\t/** @return {boolean} true for `type: 'related'`. */\n\t\tisRelated() {\n\t\t\treturn isRelatedWidgetDef(this.widget)\n\t\t},\n\n\t\t/** @return {boolean} true for `type: 'object-geo'`. */\n\t\tisGeo() {\n\t\t\treturn isGeoWidgetDef(this.widget)\n\t\t},\n\n\t\t/** @return {boolean} true for a registered integration leaf. */\n\t\tisIntegration() {\n\t\t\treturn isIntegrationWidgetDef(this.widget)\n\t\t},\n\n\t\t/** @return {boolean} true for object-list / table / files. */\n\t\tisContentOnly() {\n\t\t\treturn isContentOnlyWidgetDef(this.widget)\n\t\t},\n\n\t\t/** @return {boolean} true when the registry entry declares `card`. */\n\t\tisCard() {\n\t\t\treturn isCardWidgetDef(this.widget)\n\t\t},\n\n\t\t/**\n\t\t * The registered renderer for a content-driven catalog type.\n\t\t *\n\t\t * @return {object|null} The component, or null.\n\t\t */\n\t\trenderer() {\n\t\t\treturn resolveRegistryRenderer(this.widget, this.cnRegistry)\n\t\t},\n\n\t\t/**\n\t\t * The integration descriptor behind an `integration` widget.\n\t\t *\n\t\t * @return {object|null} The provider, or null.\n\t\t */\n\t\tintegrationProvider() {\n\t\t\tif (!this.isIntegration || typeof this.getRegistryProvider !== 'function') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn this.getRegistryProvider(this.widget.integrationId)\n\t\t},\n\n\t\t/**\n\t\t * Whether the integration is a mount-mode leaf (ADR-066), rendered\n\t\t * through CnLeafMountHost rather than as a component.\n\t\t *\n\t\t * @return {boolean} true for a mount-mode leaf.\n\t\t */\n\t\tisMountIntegration() {\n\t\t\tconst provider = this.integrationProvider\n\t\t\treturn Boolean(provider)\n\t\t\t\t&& provider.renderMode === 'mount'\n\t\t\t\t&& typeof provider.mount === 'function'\n\t\t\t\t&& typeof provider.unmount === 'function'\n\t\t},\n\n\t\t/**\n\t\t * Whether this render is using a provider's widget as its bare surface.\n\t\t *\n\t\t * @return {boolean} True when bare AND the provider opted in.\n\t\t */\n\t\tusesBareWidget() {\n\t\t\treturn !!(this.isBare && this.integrationProvider?.bareWidget && this.integrationProvider?.widget)\n\t\t},\n\n\t\t/**\n\t\t * The component for a component-mode integration leaf. Bare mode takes\n\t\t * the provider's `tab`, which is its content without a card, unless the\n\t\t * provider opted into `bareWidget`.\n\t\t *\n\t\t * Each branch resolves through the registry composable rather than\n\t\t * reading `integrationProvider.widget` / `.tab`. The provider is the\n\t\t * shared registry's entry, and its component objects belong to the\n\t\t * bundle that registered it. For a lib-owned id registered by\n\t\t * OpenRegister's bundle, that object renders under the wrong Vue: its\n\t\t * `resolveComponent()` finds no current instance, so `NcButton` and\n\t\t * `CnDetailCard` reach the DOM as literal `<ncbutton>` /\n\t\t * `<cndetailcard>` elements, and a Files tab whose whole body sits\n\t\t * inside that card looks empty. The resolvers hand back this bundle's\n\t\t * own copy for lib-owned ids and the stored object for consumer ids.\n\t\t *\n\t\t * @return {object|null} The component, or null.\n\t\t */\n\t\tintegrationComponent() {\n\t\t\tif (!this.isIntegration) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst id = this.widget.integrationId\n\t\t\t// `bareWidget` lets a provider say its WIDGET is already bare, so a\n\t\t\t// tab panel gets the widget surface instead of the sidebar one.\n\t\t\t// The default below (prefer `tab`) assumes every `widget` draws its\n\t\t\t// own card, which is not true: `notes`' widget is an adapter around\n\t\t\t// CnNotesCard, whose root carries no border, background or padding.\n\t\t\t// Preferring `tab` there swapped an inline compose textarea for the\n\t\t\t// sidebar's collapsed \"Add note\" button and dressed the panel in\n\t\t\t// sidebar CSS, which is what made the tabbed leaves look like they\n\t\t\t// had lost their styling. Opt-in rather than a blanket switch: the\n\t\t\t// other providers keep today's behaviour until each is checked.\n\t\t\tif (this.usesBareWidget && typeof this.resolveRegistryBaseWidget === 'function') {\n\t\t\t\treturn this.resolveRegistryBaseWidget(id)\n\t\t\t}\n\t\t\tif (this.isBare && this.integrationProvider?.tab && typeof this.resolveRegistryTab === 'function') {\n\t\t\t\treturn this.resolveRegistryTab(id)\n\t\t\t}\n\t\t\tif (typeof this.resolveRegistryWidget !== 'function') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn this.resolveRegistryWidget(id, this.surface)\n\t\t},\n\n\t\t/**\n\t\t * The object context every integration surface is handed.\n\t\t *\n\t\t * @return {object} `{ register, schema, objectId }`.\n\t\t */\n\t\tderivedIntegrationContext() {\n\t\t\treturn this.integrationContext || {\n\t\t\t\tregister: this.register || '',\n\t\t\t\tschema: this.schema || this.objectType || '',\n\t\t\t\tobjectId: this.objectId ? String(this.objectId) : '',\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Props for an integration widget or tab.\n\t\t *\n\t\t * The title is forwarded because `CnIntegrationCard`'s `cardTitle` falls\n\t\t * back to the raw `integrationId` without it, so a widget the manifest\n\t\t * calls \"Class space\" rendered as \"talk\". It is placed BEFORE `def.props`\n\t\t * so an explicit per-widget prop still wins.\n\t\t *\n\t\t * @return {object} The prop bag.\n\t\t */\n\t\tintegrationProps() {\n\t\t\treturn {\n\t\t\t\tsurface: this.surface,\n\t\t\t\ttitle: this.widget?.title || '',\n\t\t\t\t// A provider opting into `bareWidget` is being rendered where the\n\t\t\t\t// tab strip already supplies the card and the title, so tell it to\n\t\t\t\t// drop its own. Without this the panel gets a titled card inside\n\t\t\t\t// the tabs card, repeating the label the open tab already shows.\n\t\t\t\t...(this.usesBareWidget ? { chromeless: true } : {}),\n\t\t\t\t...this.derivedIntegrationContext,\n\t\t\t\t...(this.widget?.props || {}),\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Props handed to a mount-mode leaf.\n\t\t *\n\t\t * @return {object} The mount prop bag.\n\t\t */\n\t\tintegrationMountProps() {\n\t\t\treturn {\n\t\t\t\tsurface: this.surface,\n\t\t\t\t...this.derivedIntegrationContext,\n\t\t\t\tintegrationContext: this.derivedIntegrationContext,\n\t\t\t\t...(this.widget?.props || {}),\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Props for a catalog renderer: its content plus the object context, in\n\t\t * the shape CnWidgetGrid merges on the v2 widgets[] path. `content`\n\t\t * spreads last so explicit widget config still wins.\n\t\t *\n\t\t * @return {object} The prop bag.\n\t\t */\n\t\trendererProps() {\n\t\t\tconst base = {\n\t\t\t\tcontent: this.content,\n\t\t\t\tobjectId: this.objectId,\n\t\t\t\tregister: this.register,\n\t\t\t\tschema: this.schema,\n\t\t\t\tobjectData: this.object,\n\t\t\t\tobjectType: this.objectType,\n\t\t\t\tstore: this.store,\n\t\t\t}\n\t\t\t// A container widget renders other widgets, so it needs what a\n\t\t\t// surface knows and a leaf does not: the sibling definitions to\n\t\t\t// resolve its children against, and the context those children will\n\t\t\t// need. Handing this to every widget instead would put objects on\n\t\t\t// the DOM as stringified attributes for the ones that ignore them.\n\t\t\tif (this.isContainer) {\n\t\t\t\tObject.assign(base, {\n\t\t\t\t\tavailableWidgets: this.availableWidgets,\n\t\t\t\t\tschemaObject: this.schemaObject,\n\t\t\t\t\tintegrationContext: this.integrationContext,\n\t\t\t\t\tcnRegistry: this.cnRegistry,\n\t\t\t\t\tsurface: this.surface,\n\t\t\t\t})\n\t\t\t}\n\t\t\treturn { ...base, ...this.content }\n\t\t},\n\n\t\t/**\n\t\t * Whether the type declares itself a container (registry `container`).\n\t\t *\n\t\t * @return {boolean} true for a widget that renders other widgets.\n\t\t */\n\t\tisContainer() {\n\t\t\tif (!this.widget?.type) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst entry = getWidgetTypeEntry(this.widget.type)\n\t\t\treturn Boolean(entry && entry.container === true)\n\t\t},\n\n\t\t/**\n\t\t * Whether a catalog list widget offers the Add action (ADR-062:\n\t\t * collections carry their create affordance in the card's Actions menu).\n\t\t * Files widgets carry their own upload toolbar, so they are excluded.\n\t\t *\n\t\t * @return {boolean} true when Add should render.\n\t\t */\n\t\tcatalogAddEnabled() {\n\t\t\tif (!['object-list', 'table'].includes(this.widget?.type)) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn this.content.allowCreate !== false\n\t\t},\n\n\t\t/**\n\t\t * The card header rule, with the surface's layout-driven answer winning\n\t\t * when it supplied one.\n\t\t *\n\t\t * @return {boolean} true when the wrapper header renders.\n\t\t */\n\t\teffectiveShowCardTitle() {\n\t\t\tif (this.showCardTitle !== null) {\n\t\t\t\treturn this.showCardTitle\n\t\t\t}\n\t\t\treturn this.widget?.title !== undefined || this.content.title !== undefined\n\t\t},\n\n\t\t/** @return {string} Label for the catalog Add action. */\n\t\taddLabel() {\n\t\t\treturn t('nextcloud-vue', 'Add')\n\t\t},\n\n\t\t/**\n\t\t * The items THIS HOST offers to the surface, as opposed to the ones the\n\t\t * widget inside it publishes.\n\t\t *\n\t\t * There is one: the catalog Add. It is drawn in the host's own card\n\t\t * header, and that header only exists off a panel, so a Documents or\n\t\t * Files tab simply had no way to add anything. The data widget's\n\t\t * Metadata went the same way until it was published, and this is the\n\t\t * same defect one widget type along.\n\t\t *\n\t\t * Empty off a panel, where the header draws the item itself and\n\t\t * publishing would put it in two menus at once.\n\t\t *\n\t\t * @return {object[]} PanelAction descriptors.\n\t\t */\n\t\townPanelActions() {\n\t\t\tif (!this.isBare || !this.catalogAddEnabled) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn [{\n\t\t\t\tkey: 'catalog-add',\n\t\t\t\tlabel: this.addLabel,\n\t\t\t\ticon: 'Plus',\n\t\t\t\trun: () => this.invokeCatalogAdd(),\n\t\t\t}]\n\t\t},\n\t},\n\n\twatch: {\n\t\townPanelActions: {\n\t\t\thandler() {\n\t\t\t\tthis.publishOwnPanelActions()\n\t\t\t},\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.publishOwnPanelActions()\n\t},\n\n\tbeforeUnmount() {\n\t\t// A closed tab's panel can be torn down while the strip lives on, and an\n\t\t// item whose host is gone would call into nothing.\n\t\tif (this.panelActionSink) {\n\t\t\tthis.panelActionSink.clear(this.widget?.id, 'host')\n\t\t}\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Re-key a call on the channel this host provides onto the one it holds.\n\t\t * A widget publishes `set(items)`; a host nested in this one speaks the\n\t\t * surface's `set(id, items, source)`. Items are always an array and an\n\t\t * id never is, which tells the two apart.\n\t\t *\n\t\t * @param {Array} args The arguments as called.\n\t\t * @param {'set'|'clear'} kind Which channel method was called.\n\t\t * @return {Array} Arguments for the held channel.\n\t\t */\n\t\tnarrowPanelArgs(args, kind) {\n\t\t\tconst id = this.widget?.id\n\t\t\tif (kind === 'set') {\n\t\t\t\treturn Array.isArray(args[0])\n\t\t\t\t\t? [id, args[0], 'widget']\n\t\t\t\t\t: [id, args[1], args[2] || 'widget']\n\t\t\t}\n\t\t\treturn [id, args.length > 1 ? (args[1] || 'widget') : 'widget']\n\t\t},\n\n\t\t/**\n\t\t * Publish or withdraw this host's OWN items on the surface.\n\t\t *\n\t\t * Published under the `host` source so the widget inside the panel can\n\t\t * publish its own without either replacing the other.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tpublishOwnPanelActions() {\n\t\t\tif (!this.panelActionSink) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst id = this.widget?.id\n\t\t\tif (!id) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.ownPanelActions.length) {\n\t\t\t\tthis.panelActionSink.set(id, this.ownPanelActions, 'host')\n\t\t\t} else {\n\t\t\t\tthis.panelActionSink.clear(id, 'host')\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Re-emit the geo widget's save so the surface can reload the record.\n\t\t *\n\t\t * @param {object} geo The saved geometry.\n\t\t * @return {void}\n\t\t */\n\t\tonGeoSaved(geo) {\n\t\t\t/**\n\t\t\t * @event geo-saved Emitted when the geo widget saved a geometry.\n\t\t\t * @type {object}\n\t\t\t */\n\t\t\tthis.$emit('geo-saved', geo)\n\t\t},\n\n\t\t/**\n\t\t * Re-emit the related widget's request to open an integration.\n\t\t *\n\t\t * @param {string} integrationId The integration to open.\n\t\t * @return {void}\n\t\t */\n\t\tonOpenIntegration(integrationId) {\n\t\t\t/**\n\t\t\t * @event open-integration Emitted when the related widget asks to open an integration.\n\t\t\t * @type {string}\n\t\t\t */\n\t\t\tthis.$emit('open-integration', integrationId)\n\t\t},\n\n\t\t/**\n\t\t * Delegate to the rendered list widget's own create affordance, so the\n\t\t * card's Add action and the widget's footer button open the same form.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tinvokeCatalogAdd() {\n\t\t\tconst child = this.$refs.renderer\n\t\t\tconst target = Array.isArray(child) ? child[0] : child\n\t\t\tif (target && typeof target.openCreate === 'function') {\n\t\t\t\ttarget.openCreate()\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-detail-widget-host,\n.cn-detail-widget-host > * {\n\theight: 100%;\n}\n\n/* A bare host is a tab panel's body: it fills the panel and lets the panel\n scroll, rather than growing a second scroll region of its own. */\n.cn-detail-widget-host--bare {\n\tmin-height: 0;\n}\n</style>\n"],"names":["CnIcon","CnLeafMountHost","CnObjectDataWidget","CnObjectGeoWidget","CnRelatedObjectsWidget","CnWidgetWrapper","t"],"mappings":"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"}
|
|
@@ -25,13 +25,14 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
25
25
|
}, createSlots({
|
|
26
26
|
default: withCtx(() => [
|
|
27
27
|
createVNode(_component_NcEmptyContent, {
|
|
28
|
+
class: "cn-requires-app",
|
|
28
29
|
name: $options.missingAppName,
|
|
29
30
|
description: $options.missingAppDescription
|
|
30
31
|
}, {
|
|
31
32
|
icon: withCtx(() => [
|
|
32
33
|
createVNode(_component_CnIcon, {
|
|
33
34
|
name: ($props.widget && $props.widget.icon) || 'PuzzleOutline',
|
|
34
|
-
size:
|
|
35
|
+
size: 32
|
|
35
36
|
}, null, 8 /* PROPS */, ["name"])
|
|
36
37
|
]),
|
|
37
38
|
_: 1 /* STABLE */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnDetailWidgetHost.vue3.js","sources":["../../../../src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue?vue&type=template&id=76c1b60f&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-detail-widget-host\" :class=\"`cn-detail-widget-host--${chrome}`\">\n\t\t<!-- `type: 'data'` — the schema-driven data widget. In bare mode it is\n\t\t `chromeless`: no border, no background, no padding, no title row and\n\t\t no header divider, so the tab panel holds the content directly\n\t\t instead of a card inside a card.\n\n\t\t One prop, not three. `chromeless` is the word this component already\n\t\t used for the same thing on the integration path (`bareWidget`), and\n\t\t the three it replaces (`show-title`, `borderless`, `flush`) were a\n\t\t decomposition every caller had to remember in full — miss one and\n\t\t the panel keeps half a card.\n\n\t\t Chromeless does NOT mean no wrapper element. The wrapper's content\n\t\t node is what the library's table and detail-page CSS key on.\n\n\t\t It used to keep the whole card here, because its Save button lives\n\t\t in CnWidgetWrapper's header and hiding the header hid Save with it.\n\t\t The wrapper renders its header whenever there are actions, with or\n\t\t without a title, so the panel gets no title while an inline edit\n\t\t stays committable. Controls are not chrome.\n\n\t\t Passing `undefined` was never enough on its own: `title` carries a\n\t\t DEFAULT of \"Data\", so an unset title became a \"Data\" heading that\n\t\t nobody chose. The title is still passed, and now names the content\n\t\t region for a screen reader instead of printing a second heading.\n\n\t\t The overflow menu goes too, which `chromeless` does not cover. It\n\t\t left a 59px band whose only content was an Actions menu about eight\n\t\t pixels from the tab strip's own, and two menus on one row read as the\n\t\t same card inside a card in miniature.\n\n\t\t Nothing is lost, but that took a second piece of work rather than\n\t\t being true on its own. Suppressing the menu takes its two items with\n\t\t it, and Metadata had no other home anywhere: the page header carries\n\t\t no equivalent, so it became unreachable inside a panel. Edit is the\n\t\t softer case. CnDetailPage does carry a record Edit button, but it\n\t\t opens the form the PAGE configures, not the subset THIS widget\n\t\t declares through `overrides`, `include` and `exclude`, so a tabbed\n\t\t widget showing eight of forty fields lost the form scoped to its\n\t\t eight. Both items are now published to the host surface and render in\n\t\t its menu; see utils/panelActions.js. Inline editing was never\n\t\t affected: a cell opens its editor when clicked, and Save and Discard\n\t\t arrive in the header the moment there is an edit to commit. -->\n\t\t<!-- `requiredApp` names another Nextcloud app this widget leans on. When\n\t\t that app is absent the widget renders its NORMAL chrome plus a\n\t\t set-up state, and asks its backend NOTHING.\n\n\t\t It renders rather than hides on purpose. A hidden widget leaves a\n\t\t hole a reader cannot interpret, and the alternative — letting the\n\t\t query run — is worse: an aggregation over an absent app's register\n\t\t 404s and the tile shows `0`, which is exactly what a real zero\n\t\t shows. dossiq's hours tile did that on every install without\n\t\t humaniq, and looked correct doing it.\n\n\t\t \"NORMAL chrome\" means the chrome of the surface it is on. In a tab\n\t\t panel that is no chrome, same as every other widget there: a set-up\n\t\t state is still a widget, and a bordered card around it inside the\n\t\t panel is the same doubled card this file removes below. The title\n\t\t is now passed in bare mode too, where it names the content region\n\t\t rather than printing a heading the tab already carries. -->\n\t\t<CnWidgetWrapper\n\t\t\tv-if=\"missingApp\"\n\t\t\t:title=\"widgetTitle\"\n\t\t\t:chromeless=\"isBare\"\n\t\t\ttitleIconPosition=\"left\"\n\t\t\t:showRefresh=\"false\"\n\t\t\t:showRequestFeature=\"false\">\n\t\t\t<template v-if=\"widget && widget.icon\" #title-icon>\n\t\t\t\t<CnIcon :name=\"widget.icon\" :size=\"20\" />\n\t\t\t</template>\n\t\t\t<NcEmptyContent :name=\"missingAppName\" :description=\"missingAppDescription\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<CnIcon :name=\"(widget && widget.icon) || 'PuzzleOutline'\" :size=\"44\" />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\t\t</CnWidgetWrapper>\n\n\t\t<CnObjectDataWidget\n\t\t\tv-else-if=\"isData && schemaObject\"\n\t\t\t:title=\"resolvedTitle\"\n\t\t\t:chromeless=\"isBare\"\n\t\t\t:showActions=\"!isBare\"\n\t\t\t:icon=\"widget.icon || null\"\n\t\t\t:schema=\"schemaObject\"\n\t\t\t:objectData=\"object\"\n\t\t\t:objectType=\"objectType\"\n\t\t\t:store=\"store\"\n\t\t\t:overrides=\"content.overrides || {}\"\n\t\t\t:include=\"content.include || null\"\n\t\t\t:exclude=\"content.exclude || []\"\n\t\t\t:hideEmpty=\"content.hideEmpty === true || hideEmpty\"\n\t\t\t:columns=\"content.columns || 3\"\n\t\t\t:collapsedFields=\"content.collapsedFields ?? null\"\n\t\t\t:editable=\"content.editable !== false\" />\n\n\t\t<!-- `type: 'related'` — resolves this object's relations and links into\n\t\t integrations. Honours bare mode via its own `bare` prop. -->\n\t\t<CnRelatedObjectsWidget\n\t\t\tv-else-if=\"isRelated\"\n\t\t\t:title=\"resolvedTitle\"\n\t\t\t:bare=\"isBare\"\n\t\t\t:objectType=\"objectType\"\n\t\t\t:objectId=\"objectId\"\n\t\t\t:objectData=\"object\"\n\t\t\t:register=\"register\"\n\t\t\t:schema=\"schema\"\n\t\t\t:store=\"store\"\n\t\t\t:includeGroups=\"content.groups || []\"\n\t\t\t:hideSingleTabTitle=\"content.hideSingleTabTitle !== false\"\n\t\t\t:showTotalCount=\"content.showTotalCount !== false\"\n\t\t\t@openIntegration=\"onOpenIntegration\" />\n\n\t\t<!-- `type: 'object-geo'` — view/edit the object's `@self.geo` on a map. -->\n\t\t<CnObjectGeoWidget\n\t\t\tv-else-if=\"isGeo\"\n\t\t\t:title=\"resolvedTitle\"\n\t\t\t:objectId=\"objectId\"\n\t\t\t:objectData=\"object\"\n\t\t\t:register=\"register\"\n\t\t\t:schema=\"schema\"\n\t\t\t:editable=\"content.editable !== false\"\n\t\t\t:addressSearch=\"content.addressSearch === true\"\n\t\t\t:basemap=\"content.basemap || 'standard'\"\n\t\t\t:allowBasemapSwitch=\"content.allowBasemapSwitch === true\"\n\t\t\t:fitControl=\"content.fitControl !== false\"\n\t\t\t:locateControl=\"content.locateControl !== false\"\n\t\t\t:fullscreenControl=\"content.fullscreenControl !== false\"\n\t\t\t:height=\"content.height || '360px'\"\n\t\t\t:defaultZoom=\"content.defaultZoom || 7\"\n\t\t\t@saved=\"onGeoSaved\" />\n\n\t\t<!-- Mount-mode integration leaf (openregister#2127): a bare host-owned\n\t\t element the leaf mounts its own framework into. -->\n\t\t<CnLeafMountHost\n\t\t\tv-else-if=\"isMountIntegration\"\n\t\t\t:provider=\"integrationProvider\"\n\t\t\t:mountProps=\"integrationMountProps\" />\n\n\t\t<!-- Integration leaf, component mode. In BARE mode this renders the\n\t\t provider's `tab` (its bare content) rather than its `widget` (which\n\t\t draws its own card). That pairing is not an invention here:\n\t\t CnIntegrationWidget already renders `provider.tab` inside its own\n\t\t tab panels for exactly this reason. -->\n\t\t<component\n\t\t\t:is=\"integrationComponent\"\n\t\t\tv-else-if=\"isIntegration && integrationComponent\"\n\t\t\tv-bind=\"integrationProps\" />\n\n\t\t<!-- Content-only catalog widgets (object-list / table / files) render\n\t\t bare tables. On a card surface they get the titled wrapper (ADR-062:\n\t\t every body widget has chrome and its manifest title); inside a tab\n\t\t the strip supplies both, so they render as-is.\n\n\t\t The `cn-detail-page__*` classes here and below keep their old names\n\t\t although the markup moved out of CnDetailPage: src/css/detail-page.css\n\t\t targets them, and so may consumer app CSS. Renaming them to match this\n\t\t component would be a silent visual regression across the fleet. -->\n\t\t<CnWidgetWrapper\n\t\t\tv-else-if=\"renderer && isContentOnly && !isBare\"\n\t\t\t:title=\"widget.title || ''\"\n\t\t\ttitleIconPosition=\"left\"\n\t\t\t:showRefresh=\"false\"\n\t\t\t:showRequestFeature=\"false\"\n\t\t\tclass=\"cn-detail-page__catalog-card\">\n\t\t\t<template v-if=\"widget.icon\" #title-icon>\n\t\t\t\t<CnIcon :name=\"widget.icon\" :size=\"20\" />\n\t\t\t</template>\n\t\t\t<template v-if=\"catalogAddEnabled\" #action-items>\n\t\t\t\t<NcActionButton @click=\"invokeCatalogAdd\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ addLabel }}\n\t\t\t\t</NcActionButton>\n\t\t\t</template>\n\t\t\t<component\n\t\t\t\t:is=\"renderer\"\n\t\t\t\tref=\"renderer\"\n\t\t\t\tv-bind=\"rendererProps\" />\n\t\t</CnWidgetWrapper>\n\n\t\t<!-- Registry \"card\" widgets (stat / gauge / delta) render bare tile\n\t\t content, so on a card surface they need the same titled wrapper the\n\t\t dashboard gives them (ADR-062: a lone stat must not read as uncarded\n\t\t floating text). `card-fit` centres the tile and drops the inner\n\t\t scrollbar. No Actions menu: it would hold nothing here and squeezes\n\t\t the title of a narrow tile. -->\n\t\t<CnWidgetWrapper\n\t\t\tv-else-if=\"renderer && isCard && !isBare\"\n\t\t\t:title=\"widget.title || content.title || ''\"\n\t\t\t:showTitle=\"effectiveShowCardTitle\"\n\t\t\ttitleIconPosition=\"left\"\n\t\t\tflush\n\t\t\t:showActions=\"false\"\n\t\t\t:showRefresh=\"false\"\n\t\t\t:showRequestFeature=\"false\"\n\t\t\tclass=\"cn-detail-page__card-fit\">\n\t\t\t<template v-if=\"widget.icon\" #title-icon>\n\t\t\t\t<CnIcon :name=\"widget.icon\" :size=\"20\" />\n\t\t\t</template>\n\t\t\t<component :is=\"renderer\" :content=\"content\" v-bind=\"content\" />\n\t\t</CnWidgetWrapper>\n\n\t\t<!-- Every other content-driven catalog widget, and every bare-mode\n\t\t catalog widget. These self-fetch from OpenRegister. The object\n\t\t context is forwarded too (the same shape CnWidgetGrid merges on the\n\t\t v2 widgets[] path) so object-aware widgets receive it; widgets that\n\t\t declare none of those props ignore them. `content` spreads LAST so\n\t\t explicit widget config still wins. -->\n\t\t<component\n\t\t\t:is=\"renderer\"\n\t\t\tv-else-if=\"renderer\"\n\t\t\tref=\"renderer\"\n\t\t\tv-bind=\"rendererProps\" />\n\n\t\t<!-- No renderer resolves: render nothing, which is what CnDetailPage has\n\t\t always done here. Surfacing an \"unknown widget\" box instead would be\n\t\t more honest, but this is the SLOT FALLBACK for every detail page in\n\t\t the fleet, so an app that declares a custom widget and omits its\n\t\t `#widget-<id>` slot would start showing a placeholder where it shows\n\t\t nothing today. That is its own change, not a side effect of this one. -->\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcEmptyContent } from '@nextcloud/vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnIcon from '../CnIcon/CnIcon.vue'\nimport CnLeafMountHost from '../CnLeafMountHost/CnLeafMountHost.vue'\nimport CnObjectDataWidget from '../CnObjectDataWidget/CnObjectDataWidget.vue'\nimport CnObjectGeoWidget from '../CnObjectGeoWidget/CnObjectGeoWidget.vue'\nimport CnRelatedObjectsWidget from '../CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue'\nimport { useIntegrationRegistry } from '../../composables/useIntegrationRegistry.js'\nimport { isAppInstalled } from '../../utils/appInstalled.js'\nimport { PANEL_ACTION_SINK } from '../../utils/panelActions.js'\nimport {\n\tisCardWidgetDef,\n\tisContentOnlyWidgetDef,\n\tisDataWidgetDef,\n\tisGeoWidgetDef,\n\tisIntegrationWidgetDef,\n\tisRelatedWidgetDef,\n\tresolveRegistryRenderer,\n\twidgetContentOf,\n\twidgetTitleOf,\n} from '../../utils/widgetDispatch.js'\nimport { getWidgetTypeEntry } from '../CnWidgetGrid/dashboardWidgetRegistry.js'\nimport { CnWidgetWrapper } from '../CnWidgetWrapper/index.js'\n\n/**\n * CnDetailWidgetHost — renders ONE detail-page widget definition.\n *\n * This is the dispatch that used to be a ~230-line `v-if` chain inside\n * `CnDetailPage`'s grid slot. It moved out when a second surface needed it:\n * `CnTabsWidget` renders the same widget definitions inside tab panels, and\n * two copies of \"which renderer does this type get\" is the kind of thing that\n * drifts silently. A type added to one and not the other renders correctly on\n * the page and blank in a tab, with nothing in the console either way.\n *\n * ## The two chrome modes\n *\n * `chrome=\"card\"` is the detail-page grid: every widget gets a titled card,\n * because ADR-062 rule 5 says every body widget carries chrome and its manifest\n * title.\n *\n * `chrome=\"bare\"` is a tab panel. The tab strip already shows the title and\n * draws the card, so a second title inside the panel reads as a heading nested\n * in its own heading. Bare mode therefore drops the wrapper and, for\n * integration leaves, renders the provider's `tab` (bare content) instead of\n * its `widget` (which draws its own card). That pairing is not new: it is\n * exactly what `CnIntegrationWidget` already does inside its own panels.\n *\n * ## The `data` widget in bare mode\n *\n * It drops its title, border and padding, so a tab panel holds the content\n * directly rather than a card inside a card.\n *\n * That used not to be possible. `CnWidgetWrapper` renders its actions INSIDE\n * the header and `CnObjectDataWidget` puts its **Save** button there, so\n * hiding the header to remove the duplicate title also removed the only way to\n * commit an inline edit; a silently unsaveable form being worse than a doubled\n * title, the title stayed. The wrapper now renders its header whenever there\n * are actions, with or without a title, so the two are separate requests and a\n * panel can have no title while staying saveable.\n *\n * ```vue\n * <CnDetailWidgetHost\n * :widget=\"def\"\n * :object-id=\"id\"\n * :object=\"record\"\n * register=\"dossiq\"\n * schema=\"case\"\n * chrome=\"bare\" />\n * ```\n */\nexport default {\n\tname: 'CnDetailWidgetHost',\n\n\tcomponents: {\n\t\tCnIcon,\n\t\tCnLeafMountHost,\n\t\tCnObjectDataWidget,\n\t\tCnObjectGeoWidget,\n\t\tCnRelatedObjectsWidget,\n\t\tCnWidgetWrapper,\n\t\tNcActionButton,\n\t\tNcEmptyContent,\n\t\tPlus,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * The surface's panel-action channel, when this host sits inside one.\n\t\t *\n\t\t * Absent on every other surface, which is why the default is null\n\t\t * rather than a required injection.\n\t\t */\n\t\tpanelActionSink: {\n\t\t\tfrom: PANEL_ACTION_SINK,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\t/**\n\t * Re-provide the channel with this panel's widget id baked in.\n\t *\n\t * The widget below does not know its own id on the surface and should not\n\t * have to, so the id is supplied here, where it is already known. Read at\n\t * CALL time rather than captured, so a host whose `widget` changes keeps\n\t * publishing under the right key.\n\t *\n\t * Provides null when there is no surface to publish to, so the widget's own\n\t * injection resolves to null instead of finding a channel that goes nowhere.\n\t *\n\t * @return {object} The narrowed sink, or null.\n\t */\n\tprovide() {\n\t\tconst sink = this.panelActionSink\n\t\treturn {\n\t\t\t[PANEL_ACTION_SINK]: sink\n\t\t\t\t? {\n\t\t\t\t\t\tset: (...args) => sink.set(...this.narrowPanelArgs(args, 'set')),\n\t\t\t\t\t\tclear: (...args) => sink.clear(...this.narrowPanelArgs(args, 'clear')),\n\t\t\t\t\t}\n\t\t\t\t: null,\n\t\t}\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The resolved widget definition: `{ id, type, title, icon, content }`,\n\t\t * plus `integrationId` for `type: 'integration'`.\n\t\t *\n\t\t * Resolved, not a layout item: turning an item into a definition needs\n\t\t * the surface's own widget list, which is the one part the two surfaces\n\t\t * genuinely do differently.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\twidget: {\n\t\t\ttype: Object,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/**\n\t\t * How much chrome to draw around the widget.\n\t\t * - `'card'` — a titled `CnWidgetWrapper`, the detail-page grid default.\n\t\t * - `'bare'` — no wrapper; the caller owns the title and the card.\n\t\t */\n\t\tchrome: {\n\t\t\ttype: String,\n\t\t\tdefault: 'card',\n\t\t\tvalidator: (v) => ['card', 'bare'].includes(v),\n\t\t},\n\n\t\t/** The bound record's id. Present on the first render; `object` is not. */\n\t\tobjectId: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The loaded record, or null while it is still being fetched. */\n\t\tobject: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The resolved object-type slug. */\n\t\tobjectType: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The resolved JSON Schema object, needed by the `data` widget. */\n\t\tschemaObject: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** OpenRegister register slug of the surface. */\n\t\tregister: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** OpenRegister schema slug of the surface. */\n\t\tschema: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The effective object store. */\n\t\tstore: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Rendering surface forwarded to integration widgets (AD-19). */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t},\n\n\t\t/** Object context forwarded to integration widgets. */\n\t\tintegrationContext: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Hide empty properties in the `data` widget. */\n\t\thideEmpty: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Every widget definition on the surface, for a CONTAINER widget to\n\t\t * resolve the children it references by id.\n\t\t *\n\t\t * Only container types receive it (see `rendererProps`). A leaf widget\n\t\t * has no business knowing what else is on the page.\n\t\t *\n\t\t * @type {object[]}\n\t\t */\n\t\tavailableWidgets: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Whether a card widget (stat / gauge / delta) draws the wrapper header.\n\t\t *\n\t\t * A prop, not a computed, because the rule reads the LAYOUT item's\n\t\t * `showTitle` and only the surface holds the layout. Deriving it from the\n\t\t * widget definition alone silently drops `showTitle: false`, which is the\n\t\t * one way a consumer can stop a card printing its title twice: the tile\n\t\t * already draws `content.label` itself.\n\t\t *\n\t\t * Null means \"decide from the definition\", which is what a surface with\n\t\t * no layout (a tab panel) wants.\n\t\t *\n\t\t * @type {boolean|null}\n\t\t */\n\t\tshowCardTitle: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * The consumer's component registry, consulted before the built-in\n\t\t * catalog so a custom widget type overrides a built-in (REQ-MVR-005).\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tcnRegistry: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\t},\n\n\temits: ['geo-saved', 'open-integration'],\n\n\tsetup() {\n\t\t// Every integration component this host renders goes through the\n\t\t// composable's resolvers, never through the registry entry's own\n\t\t// `tab` / `widget`. The resolvers swap a lib-owned entry for THIS\n\t\t// bundle's copy of the component (openregister#1958). The entry's\n\t\t// object belongs to whichever bundle registered it; when that is\n\t\t// OpenRegister's integration-global bundle, rendering it under the\n\t\t// app's Vue leaves `resolveComponent()` without a current instance,\n\t\t// and `<NcButton>` / `<CnDetailCard>` reach the DOM as literal\n\t\t// unknown elements. Seen on dossiq's Notes, Contacts and Files tabs.\n\t\tconst { resolveWidget, resolveBaseWidget, resolveTab, getById } = useIntegrationRegistry()\n\t\treturn {\n\t\t\tresolveRegistryWidget: resolveWidget,\n\t\t\tresolveRegistryBaseWidget: resolveBaseWidget,\n\t\t\tresolveRegistryTab: resolveTab,\n\t\t\tgetRegistryProvider: getById,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the caller owns the title and card.\n\t\t *\n\t\t * @return {boolean} true in bare mode.\n\t\t */\n\t\tisBare() {\n\t\t\treturn this.chrome === 'bare'\n\t\t},\n\n\t\t/**\n\t\t * The widget's stored config blob.\n\t\t *\n\t\t * @return {object} The `content`, or an empty object.\n\t\t */\n\t\tcontent() {\n\t\t\treturn widgetContentOf(this.widget)\n\t\t},\n\n\t\t/**\n\t\t * The title to render. Undefined in bare mode so a self-chromed widget\n\t\t * does not repeat what the tab strip already says.\n\t\t *\n\t\t * @return {string|undefined} The title, or undefined.\n\t\t */\n\t\tresolvedTitle() {\n\t\t\treturn this.isBare ? undefined : widgetTitleOf(this.widget)\n\t\t},\n\n\t\t/**\n\t\t * The app this widget leans on, if it declares one.\n\t\t *\n\t\t * Read from the widget definition first and its `content` second, so a\n\t\t * manifest may put it wherever the rest of that widget's config lives.\n\t\t *\n\t\t * @return {string} A Nextcloud app id, or ''.\n\t\t */\n\t\trequiredApp() {\n\t\t\t// `widget` is declared required, but CnDetailPage renders a host for\n\t\t\t// a layout item whose widget id resolves to nothing — and there is a\n\t\t\t// test that it must not throw for one. This computed runs in the\n\t\t\t// render path BEFORE the branches that tolerate a missing widget, so\n\t\t\t// it is the one that has to survive it.\n\t\t\treturn (this.widget || {}).requiredApp || this.content.requiredApp || ''\n\t\t},\n\n\t\t/**\n\t\t * Whether this widget's backing app is declared and absent.\n\t\t *\n\t\t * @return {boolean} true when the widget must render its set-up state.\n\t\t */\n\t\tmissingApp() {\n\t\t\treturn this.requiredApp !== '' && !isAppInstalled(this.requiredApp)\n\t\t},\n\n\t\t/**\n\t\t * The widget's title, independent of chrome mode.\n\t\t *\n\t\t * `resolvedTitle` is deliberately undefined in bare mode so a tabbed\n\t\t * widget does not repeat what the strip already says. The set-up state\n\t\t * still needs the name to say WHICH widget is inert.\n\t\t *\n\t\t * @return {string} The manifest title.\n\t\t */\n\t\twidgetTitle() {\n\t\t\treturn widgetTitleOf(this.widget) || ''\n\t\t},\n\n\t\t/**\n\t\t * Headline for the set-up state.\n\t\t *\n\t\t * @return {string} e.g. \"Humaniq is not installed\".\n\t\t */\n\t\tmissingAppName() {\n\t\t\treturn t('nextcloud-vue', '{app} is not installed', { app: this.requiredAppLabel })\n\t\t},\n\n\t\t/**\n\t\t * Body for the set-up state, naming what the reader is NOT seeing.\n\t\t *\n\t\t * @return {string} The description.\n\t\t */\n\t\tmissingAppDescription() {\n\t\t\treturn this.widgetTitle\n\t\t\t\t? t('nextcloud-vue', '{title} needs the {app} app. Install and enable it to see this.', {\n\t\t\t\t\t\ttitle: this.widgetTitle,\n\t\t\t\t\t\tapp: this.requiredAppLabel,\n\t\t\t\t\t})\n\t\t\t\t: t('nextcloud-vue', 'Install and enable the {app} app to see this.', {\n\t\t\t\t\t\tapp: this.requiredAppLabel,\n\t\t\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * The app id as a display name — `humaniq` reads as `Humaniq`.\n\t\t *\n\t\t * @return {string} The capitalised app id.\n\t\t */\n\t\trequiredAppLabel() {\n\t\t\treturn this.requiredApp.charAt(0).toUpperCase() + this.requiredApp.slice(1)\n\t\t},\n\n\t\t/** @return {boolean} true for `type: 'data'`. */\n\t\tisData() {\n\t\t\treturn isDataWidgetDef(this.widget)\n\t\t},\n\n\t\t/** @return {boolean} true for `type: 'related'`. */\n\t\tisRelated() {\n\t\t\treturn isRelatedWidgetDef(this.widget)\n\t\t},\n\n\t\t/** @return {boolean} true for `type: 'object-geo'`. */\n\t\tisGeo() {\n\t\t\treturn isGeoWidgetDef(this.widget)\n\t\t},\n\n\t\t/** @return {boolean} true for a registered integration leaf. */\n\t\tisIntegration() {\n\t\t\treturn isIntegrationWidgetDef(this.widget)\n\t\t},\n\n\t\t/** @return {boolean} true for object-list / table / files. */\n\t\tisContentOnly() {\n\t\t\treturn isContentOnlyWidgetDef(this.widget)\n\t\t},\n\n\t\t/** @return {boolean} true when the registry entry declares `card`. */\n\t\tisCard() {\n\t\t\treturn isCardWidgetDef(this.widget)\n\t\t},\n\n\t\t/**\n\t\t * The registered renderer for a content-driven catalog type.\n\t\t *\n\t\t * @return {object|null} The component, or null.\n\t\t */\n\t\trenderer() {\n\t\t\treturn resolveRegistryRenderer(this.widget, this.cnRegistry)\n\t\t},\n\n\t\t/**\n\t\t * The integration descriptor behind an `integration` widget.\n\t\t *\n\t\t * @return {object|null} The provider, or null.\n\t\t */\n\t\tintegrationProvider() {\n\t\t\tif (!this.isIntegration || typeof this.getRegistryProvider !== 'function') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn this.getRegistryProvider(this.widget.integrationId)\n\t\t},\n\n\t\t/**\n\t\t * Whether the integration is a mount-mode leaf (ADR-066), rendered\n\t\t * through CnLeafMountHost rather than as a component.\n\t\t *\n\t\t * @return {boolean} true for a mount-mode leaf.\n\t\t */\n\t\tisMountIntegration() {\n\t\t\tconst provider = this.integrationProvider\n\t\t\treturn Boolean(provider)\n\t\t\t\t&& provider.renderMode === 'mount'\n\t\t\t\t&& typeof provider.mount === 'function'\n\t\t\t\t&& typeof provider.unmount === 'function'\n\t\t},\n\n\t\t/**\n\t\t * Whether this render is using a provider's widget as its bare surface.\n\t\t *\n\t\t * @return {boolean} True when bare AND the provider opted in.\n\t\t */\n\t\tusesBareWidget() {\n\t\t\treturn !!(this.isBare && this.integrationProvider?.bareWidget && this.integrationProvider?.widget)\n\t\t},\n\n\t\t/**\n\t\t * The component for a component-mode integration leaf. Bare mode takes\n\t\t * the provider's `tab`, which is its content without a card, unless the\n\t\t * provider opted into `bareWidget`.\n\t\t *\n\t\t * Each branch resolves through the registry composable rather than\n\t\t * reading `integrationProvider.widget` / `.tab`. The provider is the\n\t\t * shared registry's entry, and its component objects belong to the\n\t\t * bundle that registered it. For a lib-owned id registered by\n\t\t * OpenRegister's bundle, that object renders under the wrong Vue: its\n\t\t * `resolveComponent()` finds no current instance, so `NcButton` and\n\t\t * `CnDetailCard` reach the DOM as literal `<ncbutton>` /\n\t\t * `<cndetailcard>` elements, and a Files tab whose whole body sits\n\t\t * inside that card looks empty. The resolvers hand back this bundle's\n\t\t * own copy for lib-owned ids and the stored object for consumer ids.\n\t\t *\n\t\t * @return {object|null} The component, or null.\n\t\t */\n\t\tintegrationComponent() {\n\t\t\tif (!this.isIntegration) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst id = this.widget.integrationId\n\t\t\t// `bareWidget` lets a provider say its WIDGET is already bare, so a\n\t\t\t// tab panel gets the widget surface instead of the sidebar one.\n\t\t\t// The default below (prefer `tab`) assumes every `widget` draws its\n\t\t\t// own card, which is not true: `notes`' widget is an adapter around\n\t\t\t// CnNotesCard, whose root carries no border, background or padding.\n\t\t\t// Preferring `tab` there swapped an inline compose textarea for the\n\t\t\t// sidebar's collapsed \"Add note\" button and dressed the panel in\n\t\t\t// sidebar CSS, which is what made the tabbed leaves look like they\n\t\t\t// had lost their styling. Opt-in rather than a blanket switch: the\n\t\t\t// other providers keep today's behaviour until each is checked.\n\t\t\tif (this.usesBareWidget && typeof this.resolveRegistryBaseWidget === 'function') {\n\t\t\t\treturn this.resolveRegistryBaseWidget(id)\n\t\t\t}\n\t\t\tif (this.isBare && this.integrationProvider?.tab && typeof this.resolveRegistryTab === 'function') {\n\t\t\t\treturn this.resolveRegistryTab(id)\n\t\t\t}\n\t\t\tif (typeof this.resolveRegistryWidget !== 'function') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn this.resolveRegistryWidget(id, this.surface)\n\t\t},\n\n\t\t/**\n\t\t * The object context every integration surface is handed.\n\t\t *\n\t\t * @return {object} `{ register, schema, objectId }`.\n\t\t */\n\t\tderivedIntegrationContext() {\n\t\t\treturn this.integrationContext || {\n\t\t\t\tregister: this.register || '',\n\t\t\t\tschema: this.schema || this.objectType || '',\n\t\t\t\tobjectId: this.objectId ? String(this.objectId) : '',\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Props for an integration widget or tab.\n\t\t *\n\t\t * The title is forwarded because `CnIntegrationCard`'s `cardTitle` falls\n\t\t * back to the raw `integrationId` without it, so a widget the manifest\n\t\t * calls \"Class space\" rendered as \"talk\". It is placed BEFORE `def.props`\n\t\t * so an explicit per-widget prop still wins.\n\t\t *\n\t\t * @return {object} The prop bag.\n\t\t */\n\t\tintegrationProps() {\n\t\t\treturn {\n\t\t\t\tsurface: this.surface,\n\t\t\t\ttitle: this.widget?.title || '',\n\t\t\t\t// A provider opting into `bareWidget` is being rendered where the\n\t\t\t\t// tab strip already supplies the card and the title, so tell it to\n\t\t\t\t// drop its own. Without this the panel gets a titled card inside\n\t\t\t\t// the tabs card, repeating the label the open tab already shows.\n\t\t\t\t...(this.usesBareWidget ? { chromeless: true } : {}),\n\t\t\t\t...this.derivedIntegrationContext,\n\t\t\t\t...(this.widget?.props || {}),\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Props handed to a mount-mode leaf.\n\t\t *\n\t\t * @return {object} The mount prop bag.\n\t\t */\n\t\tintegrationMountProps() {\n\t\t\treturn {\n\t\t\t\tsurface: this.surface,\n\t\t\t\t...this.derivedIntegrationContext,\n\t\t\t\tintegrationContext: this.derivedIntegrationContext,\n\t\t\t\t...(this.widget?.props || {}),\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Props for a catalog renderer: its content plus the object context, in\n\t\t * the shape CnWidgetGrid merges on the v2 widgets[] path. `content`\n\t\t * spreads last so explicit widget config still wins.\n\t\t *\n\t\t * @return {object} The prop bag.\n\t\t */\n\t\trendererProps() {\n\t\t\tconst base = {\n\t\t\t\tcontent: this.content,\n\t\t\t\tobjectId: this.objectId,\n\t\t\t\tregister: this.register,\n\t\t\t\tschema: this.schema,\n\t\t\t\tobjectData: this.object,\n\t\t\t\tobjectType: this.objectType,\n\t\t\t\tstore: this.store,\n\t\t\t}\n\t\t\t// A container widget renders other widgets, so it needs what a\n\t\t\t// surface knows and a leaf does not: the sibling definitions to\n\t\t\t// resolve its children against, and the context those children will\n\t\t\t// need. Handing this to every widget instead would put objects on\n\t\t\t// the DOM as stringified attributes for the ones that ignore them.\n\t\t\tif (this.isContainer) {\n\t\t\t\tObject.assign(base, {\n\t\t\t\t\tavailableWidgets: this.availableWidgets,\n\t\t\t\t\tschemaObject: this.schemaObject,\n\t\t\t\t\tintegrationContext: this.integrationContext,\n\t\t\t\t\tcnRegistry: this.cnRegistry,\n\t\t\t\t\tsurface: this.surface,\n\t\t\t\t})\n\t\t\t}\n\t\t\treturn { ...base, ...this.content }\n\t\t},\n\n\t\t/**\n\t\t * Whether the type declares itself a container (registry `container`).\n\t\t *\n\t\t * @return {boolean} true for a widget that renders other widgets.\n\t\t */\n\t\tisContainer() {\n\t\t\tif (!this.widget?.type) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst entry = getWidgetTypeEntry(this.widget.type)\n\t\t\treturn Boolean(entry && entry.container === true)\n\t\t},\n\n\t\t/**\n\t\t * Whether a catalog list widget offers the Add action (ADR-062:\n\t\t * collections carry their create affordance in the card's Actions menu).\n\t\t * Files widgets carry their own upload toolbar, so they are excluded.\n\t\t *\n\t\t * @return {boolean} true when Add should render.\n\t\t */\n\t\tcatalogAddEnabled() {\n\t\t\tif (!['object-list', 'table'].includes(this.widget?.type)) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn this.content.allowCreate !== false\n\t\t},\n\n\t\t/**\n\t\t * The card header rule, with the surface's layout-driven answer winning\n\t\t * when it supplied one.\n\t\t *\n\t\t * @return {boolean} true when the wrapper header renders.\n\t\t */\n\t\teffectiveShowCardTitle() {\n\t\t\tif (this.showCardTitle !== null) {\n\t\t\t\treturn this.showCardTitle\n\t\t\t}\n\t\t\treturn this.widget?.title !== undefined || this.content.title !== undefined\n\t\t},\n\n\t\t/** @return {string} Label for the catalog Add action. */\n\t\taddLabel() {\n\t\t\treturn t('nextcloud-vue', 'Add')\n\t\t},\n\n\t\t/**\n\t\t * The items THIS HOST offers to the surface, as opposed to the ones the\n\t\t * widget inside it publishes.\n\t\t *\n\t\t * There is one: the catalog Add. It is drawn in the host's own card\n\t\t * header, and that header only exists off a panel, so a Documents or\n\t\t * Files tab simply had no way to add anything. The data widget's\n\t\t * Metadata went the same way until it was published, and this is the\n\t\t * same defect one widget type along.\n\t\t *\n\t\t * Empty off a panel, where the header draws the item itself and\n\t\t * publishing would put it in two menus at once.\n\t\t *\n\t\t * @return {object[]} PanelAction descriptors.\n\t\t */\n\t\townPanelActions() {\n\t\t\tif (!this.isBare || !this.catalogAddEnabled) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn [{\n\t\t\t\tkey: 'catalog-add',\n\t\t\t\tlabel: this.addLabel,\n\t\t\t\ticon: 'Plus',\n\t\t\t\trun: () => this.invokeCatalogAdd(),\n\t\t\t}]\n\t\t},\n\t},\n\n\twatch: {\n\t\townPanelActions: {\n\t\t\thandler() {\n\t\t\t\tthis.publishOwnPanelActions()\n\t\t\t},\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.publishOwnPanelActions()\n\t},\n\n\tbeforeUnmount() {\n\t\t// A closed tab's panel can be torn down while the strip lives on, and an\n\t\t// item whose host is gone would call into nothing.\n\t\tif (this.panelActionSink) {\n\t\t\tthis.panelActionSink.clear(this.widget?.id, 'host')\n\t\t}\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Re-key a call on the channel this host provides onto the one it holds.\n\t\t * A widget publishes `set(items)`; a host nested in this one speaks the\n\t\t * surface's `set(id, items, source)`. Items are always an array and an\n\t\t * id never is, which tells the two apart.\n\t\t *\n\t\t * @param {Array} args The arguments as called.\n\t\t * @param {'set'|'clear'} kind Which channel method was called.\n\t\t * @return {Array} Arguments for the held channel.\n\t\t */\n\t\tnarrowPanelArgs(args, kind) {\n\t\t\tconst id = this.widget?.id\n\t\t\tif (kind === 'set') {\n\t\t\t\treturn Array.isArray(args[0])\n\t\t\t\t\t? [id, args[0], 'widget']\n\t\t\t\t\t: [id, args[1], args[2] || 'widget']\n\t\t\t}\n\t\t\treturn [id, args.length > 1 ? (args[1] || 'widget') : 'widget']\n\t\t},\n\n\t\t/**\n\t\t * Publish or withdraw this host's OWN items on the surface.\n\t\t *\n\t\t * Published under the `host` source so the widget inside the panel can\n\t\t * publish its own without either replacing the other.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tpublishOwnPanelActions() {\n\t\t\tif (!this.panelActionSink) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst id = this.widget?.id\n\t\t\tif (!id) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.ownPanelActions.length) {\n\t\t\t\tthis.panelActionSink.set(id, this.ownPanelActions, 'host')\n\t\t\t} else {\n\t\t\t\tthis.panelActionSink.clear(id, 'host')\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Re-emit the geo widget's save so the surface can reload the record.\n\t\t *\n\t\t * @param {object} geo The saved geometry.\n\t\t * @return {void}\n\t\t */\n\t\tonGeoSaved(geo) {\n\t\t\t/**\n\t\t\t * @event geo-saved Emitted when the geo widget saved a geometry.\n\t\t\t * @type {object}\n\t\t\t */\n\t\t\tthis.$emit('geo-saved', geo)\n\t\t},\n\n\t\t/**\n\t\t * Re-emit the related widget's request to open an integration.\n\t\t *\n\t\t * @param {string} integrationId The integration to open.\n\t\t * @return {void}\n\t\t */\n\t\tonOpenIntegration(integrationId) {\n\t\t\t/**\n\t\t\t * @event open-integration Emitted when the related widget asks to open an integration.\n\t\t\t * @type {string}\n\t\t\t */\n\t\t\tthis.$emit('open-integration', integrationId)\n\t\t},\n\n\t\t/**\n\t\t * Delegate to the rendered list widget's own create affordance, so the\n\t\t * card's Add action and the widget's footer button open the same form.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tinvokeCatalogAdd() {\n\t\t\tconst child = this.$refs.renderer\n\t\t\tconst target = Array.isArray(child) ? child[0] : child\n\t\t\tif (target && typeof target.openCreate === 'function') {\n\t\t\t\ttarget.openCreate()\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-detail-widget-host,\n.cn-detail-widget-host > * {\n\theight: 100%;\n}\n\n/* A bare host is a tab panel's body: it fills the panel and lets the panel\n scroll, rather than growing a second scroll region of its own. */\n.cn-detail-widget-host--bare {\n\tmin-height: 0;\n}\n</style>\n"],"names":["_createBlock","_createVNode","_resolveDynamicComponent","_normalizeProps","_mergeProps","_createTextVNode"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnDetailWidgetHost.vue3.js","sources":["../../../../src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue?vue&type=template&id=76c1b60f&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-detail-widget-host\" :class=\"`cn-detail-widget-host--${chrome}`\">\n\t\t<!-- `type: 'data'` — the schema-driven data widget. In bare mode it is\n\t\t `chromeless`: no border, no background, no padding, no title row and\n\t\t no header divider, so the tab panel holds the content directly\n\t\t instead of a card inside a card.\n\n\t\t One prop, not three. `chromeless` is the word this component already\n\t\t used for the same thing on the integration path (`bareWidget`), and\n\t\t the three it replaces (`show-title`, `borderless`, `flush`) were a\n\t\t decomposition every caller had to remember in full — miss one and\n\t\t the panel keeps half a card.\n\n\t\t Chromeless does NOT mean no wrapper element. The wrapper's content\n\t\t node is what the library's table and detail-page CSS key on.\n\n\t\t It used to keep the whole card here, because its Save button lives\n\t\t in CnWidgetWrapper's header and hiding the header hid Save with it.\n\t\t The wrapper renders its header whenever there are actions, with or\n\t\t without a title, so the panel gets no title while an inline edit\n\t\t stays committable. Controls are not chrome.\n\n\t\t Passing `undefined` was never enough on its own: `title` carries a\n\t\t DEFAULT of \"Data\", so an unset title became a \"Data\" heading that\n\t\t nobody chose. The title is still passed, and now names the content\n\t\t region for a screen reader instead of printing a second heading.\n\n\t\t The overflow menu goes too, which `chromeless` does not cover. It\n\t\t left a 59px band whose only content was an Actions menu about eight\n\t\t pixels from the tab strip's own, and two menus on one row read as the\n\t\t same card inside a card in miniature.\n\n\t\t Nothing is lost, but that took a second piece of work rather than\n\t\t being true on its own. Suppressing the menu takes its two items with\n\t\t it, and Metadata had no other home anywhere: the page header carries\n\t\t no equivalent, so it became unreachable inside a panel. Edit is the\n\t\t softer case. CnDetailPage does carry a record Edit button, but it\n\t\t opens the form the PAGE configures, not the subset THIS widget\n\t\t declares through `overrides`, `include` and `exclude`, so a tabbed\n\t\t widget showing eight of forty fields lost the form scoped to its\n\t\t eight. Both items are now published to the host surface and render in\n\t\t its menu; see utils/panelActions.js. Inline editing was never\n\t\t affected: a cell opens its editor when clicked, and Save and Discard\n\t\t arrive in the header the moment there is an edit to commit. -->\n\t\t<!-- `requiredApp` names another Nextcloud app this widget leans on. When\n\t\t that app is absent the widget renders its NORMAL chrome plus a\n\t\t set-up state, and asks its backend NOTHING.\n\n\t\t It renders rather than hides on purpose. A hidden widget leaves a\n\t\t hole a reader cannot interpret, and the alternative — letting the\n\t\t query run — is worse: an aggregation over an absent app's register\n\t\t 404s and the tile shows `0`, which is exactly what a real zero\n\t\t shows. dossiq's hours tile did that on every install without\n\t\t humaniq, and looked correct doing it.\n\n\t\t \"NORMAL chrome\" means the chrome of the surface it is on. In a tab\n\t\t panel that is no chrome, same as every other widget there: a set-up\n\t\t state is still a widget, and a bordered card around it inside the\n\t\t panel is the same doubled card this file removes below. The title\n\t\t is now passed in bare mode too, where it names the content region\n\t\t rather than printing a heading the tab already carries. -->\n\t\t<CnWidgetWrapper\n\t\t\tv-if=\"missingApp\"\n\t\t\t:title=\"widgetTitle\"\n\t\t\t:chromeless=\"isBare\"\n\t\t\ttitleIconPosition=\"left\"\n\t\t\t:showRefresh=\"false\"\n\t\t\t:showRequestFeature=\"false\">\n\t\t\t<template v-if=\"widget && widget.icon\" #title-icon>\n\t\t\t\t<CnIcon :name=\"widget.icon\" :size=\"20\" />\n\t\t\t</template>\n\t\t\t<NcEmptyContent class=\"cn-requires-app\" :name=\"missingAppName\" :description=\"missingAppDescription\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<CnIcon :name=\"(widget && widget.icon) || 'PuzzleOutline'\" :size=\"32\" />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\t\t</CnWidgetWrapper>\n\n\t\t<CnObjectDataWidget\n\t\t\tv-else-if=\"isData && schemaObject\"\n\t\t\t:title=\"resolvedTitle\"\n\t\t\t:chromeless=\"isBare\"\n\t\t\t:showActions=\"!isBare\"\n\t\t\t:icon=\"widget.icon || null\"\n\t\t\t:schema=\"schemaObject\"\n\t\t\t:objectData=\"object\"\n\t\t\t:objectType=\"objectType\"\n\t\t\t:store=\"store\"\n\t\t\t:overrides=\"content.overrides || {}\"\n\t\t\t:include=\"content.include || null\"\n\t\t\t:exclude=\"content.exclude || []\"\n\t\t\t:hideEmpty=\"content.hideEmpty === true || hideEmpty\"\n\t\t\t:columns=\"content.columns || 3\"\n\t\t\t:collapsedFields=\"content.collapsedFields ?? null\"\n\t\t\t:editable=\"content.editable !== false\" />\n\n\t\t<!-- `type: 'related'` — resolves this object's relations and links into\n\t\t integrations. Honours bare mode via its own `bare` prop. -->\n\t\t<CnRelatedObjectsWidget\n\t\t\tv-else-if=\"isRelated\"\n\t\t\t:title=\"resolvedTitle\"\n\t\t\t:bare=\"isBare\"\n\t\t\t:objectType=\"objectType\"\n\t\t\t:objectId=\"objectId\"\n\t\t\t:objectData=\"object\"\n\t\t\t:register=\"register\"\n\t\t\t:schema=\"schema\"\n\t\t\t:store=\"store\"\n\t\t\t:includeGroups=\"content.groups || []\"\n\t\t\t:hideSingleTabTitle=\"content.hideSingleTabTitle !== false\"\n\t\t\t:showTotalCount=\"content.showTotalCount !== false\"\n\t\t\t@openIntegration=\"onOpenIntegration\" />\n\n\t\t<!-- `type: 'object-geo'` — view/edit the object's `@self.geo` on a map. -->\n\t\t<CnObjectGeoWidget\n\t\t\tv-else-if=\"isGeo\"\n\t\t\t:title=\"resolvedTitle\"\n\t\t\t:objectId=\"objectId\"\n\t\t\t:objectData=\"object\"\n\t\t\t:register=\"register\"\n\t\t\t:schema=\"schema\"\n\t\t\t:editable=\"content.editable !== false\"\n\t\t\t:addressSearch=\"content.addressSearch === true\"\n\t\t\t:basemap=\"content.basemap || 'standard'\"\n\t\t\t:allowBasemapSwitch=\"content.allowBasemapSwitch === true\"\n\t\t\t:fitControl=\"content.fitControl !== false\"\n\t\t\t:locateControl=\"content.locateControl !== false\"\n\t\t\t:fullscreenControl=\"content.fullscreenControl !== false\"\n\t\t\t:height=\"content.height || '360px'\"\n\t\t\t:defaultZoom=\"content.defaultZoom || 7\"\n\t\t\t@saved=\"onGeoSaved\" />\n\n\t\t<!-- Mount-mode integration leaf (openregister#2127): a bare host-owned\n\t\t element the leaf mounts its own framework into. -->\n\t\t<CnLeafMountHost\n\t\t\tv-else-if=\"isMountIntegration\"\n\t\t\t:provider=\"integrationProvider\"\n\t\t\t:mountProps=\"integrationMountProps\" />\n\n\t\t<!-- Integration leaf, component mode. In BARE mode this renders the\n\t\t provider's `tab` (its bare content) rather than its `widget` (which\n\t\t draws its own card). That pairing is not an invention here:\n\t\t CnIntegrationWidget already renders `provider.tab` inside its own\n\t\t tab panels for exactly this reason. -->\n\t\t<component\n\t\t\t:is=\"integrationComponent\"\n\t\t\tv-else-if=\"isIntegration && integrationComponent\"\n\t\t\tv-bind=\"integrationProps\" />\n\n\t\t<!-- Content-only catalog widgets (object-list / table / files) render\n\t\t bare tables. On a card surface they get the titled wrapper (ADR-062:\n\t\t every body widget has chrome and its manifest title); inside a tab\n\t\t the strip supplies both, so they render as-is.\n\n\t\t The `cn-detail-page__*` classes here and below keep their old names\n\t\t although the markup moved out of CnDetailPage: src/css/detail-page.css\n\t\t targets them, and so may consumer app CSS. Renaming them to match this\n\t\t component would be a silent visual regression across the fleet. -->\n\t\t<CnWidgetWrapper\n\t\t\tv-else-if=\"renderer && isContentOnly && !isBare\"\n\t\t\t:title=\"widget.title || ''\"\n\t\t\ttitleIconPosition=\"left\"\n\t\t\t:showRefresh=\"false\"\n\t\t\t:showRequestFeature=\"false\"\n\t\t\tclass=\"cn-detail-page__catalog-card\">\n\t\t\t<template v-if=\"widget.icon\" #title-icon>\n\t\t\t\t<CnIcon :name=\"widget.icon\" :size=\"20\" />\n\t\t\t</template>\n\t\t\t<template v-if=\"catalogAddEnabled\" #action-items>\n\t\t\t\t<NcActionButton @click=\"invokeCatalogAdd\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ addLabel }}\n\t\t\t\t</NcActionButton>\n\t\t\t</template>\n\t\t\t<component\n\t\t\t\t:is=\"renderer\"\n\t\t\t\tref=\"renderer\"\n\t\t\t\tv-bind=\"rendererProps\" />\n\t\t</CnWidgetWrapper>\n\n\t\t<!-- Registry \"card\" widgets (stat / gauge / delta) render bare tile\n\t\t content, so on a card surface they need the same titled wrapper the\n\t\t dashboard gives them (ADR-062: a lone stat must not read as uncarded\n\t\t floating text). `card-fit` centres the tile and drops the inner\n\t\t scrollbar. No Actions menu: it would hold nothing here and squeezes\n\t\t the title of a narrow tile. -->\n\t\t<CnWidgetWrapper\n\t\t\tv-else-if=\"renderer && isCard && !isBare\"\n\t\t\t:title=\"widget.title || content.title || ''\"\n\t\t\t:showTitle=\"effectiveShowCardTitle\"\n\t\t\ttitleIconPosition=\"left\"\n\t\t\tflush\n\t\t\t:showActions=\"false\"\n\t\t\t:showRefresh=\"false\"\n\t\t\t:showRequestFeature=\"false\"\n\t\t\tclass=\"cn-detail-page__card-fit\">\n\t\t\t<template v-if=\"widget.icon\" #title-icon>\n\t\t\t\t<CnIcon :name=\"widget.icon\" :size=\"20\" />\n\t\t\t</template>\n\t\t\t<component :is=\"renderer\" :content=\"content\" v-bind=\"content\" />\n\t\t</CnWidgetWrapper>\n\n\t\t<!-- Every other content-driven catalog widget, and every bare-mode\n\t\t catalog widget. These self-fetch from OpenRegister. The object\n\t\t context is forwarded too (the same shape CnWidgetGrid merges on the\n\t\t v2 widgets[] path) so object-aware widgets receive it; widgets that\n\t\t declare none of those props ignore them. `content` spreads LAST so\n\t\t explicit widget config still wins. -->\n\t\t<component\n\t\t\t:is=\"renderer\"\n\t\t\tv-else-if=\"renderer\"\n\t\t\tref=\"renderer\"\n\t\t\tv-bind=\"rendererProps\" />\n\n\t\t<!-- No renderer resolves: render nothing, which is what CnDetailPage has\n\t\t always done here. Surfacing an \"unknown widget\" box instead would be\n\t\t more honest, but this is the SLOT FALLBACK for every detail page in\n\t\t the fleet, so an app that declares a custom widget and omits its\n\t\t `#widget-<id>` slot would start showing a placeholder where it shows\n\t\t nothing today. That is its own change, not a side effect of this one. -->\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcEmptyContent } from '@nextcloud/vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnIcon from '../CnIcon/CnIcon.vue'\nimport CnLeafMountHost from '../CnLeafMountHost/CnLeafMountHost.vue'\nimport CnObjectDataWidget from '../CnObjectDataWidget/CnObjectDataWidget.vue'\nimport CnObjectGeoWidget from '../CnObjectGeoWidget/CnObjectGeoWidget.vue'\nimport CnRelatedObjectsWidget from '../CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue'\nimport { useIntegrationRegistry } from '../../composables/useIntegrationRegistry.js'\nimport { isAppInstalled } from '../../utils/appInstalled.js'\nimport { PANEL_ACTION_SINK } from '../../utils/panelActions.js'\nimport {\n\tisCardWidgetDef,\n\tisContentOnlyWidgetDef,\n\tisDataWidgetDef,\n\tisGeoWidgetDef,\n\tisIntegrationWidgetDef,\n\tisRelatedWidgetDef,\n\tresolveRegistryRenderer,\n\twidgetContentOf,\n\twidgetTitleOf,\n} from '../../utils/widgetDispatch.js'\nimport { getWidgetTypeEntry } from '../CnWidgetGrid/dashboardWidgetRegistry.js'\nimport { CnWidgetWrapper } from '../CnWidgetWrapper/index.js'\n\n/**\n * CnDetailWidgetHost — renders ONE detail-page widget definition.\n *\n * This is the dispatch that used to be a ~230-line `v-if` chain inside\n * `CnDetailPage`'s grid slot. It moved out when a second surface needed it:\n * `CnTabsWidget` renders the same widget definitions inside tab panels, and\n * two copies of \"which renderer does this type get\" is the kind of thing that\n * drifts silently. A type added to one and not the other renders correctly on\n * the page and blank in a tab, with nothing in the console either way.\n *\n * ## The two chrome modes\n *\n * `chrome=\"card\"` is the detail-page grid: every widget gets a titled card,\n * because ADR-062 rule 5 says every body widget carries chrome and its manifest\n * title.\n *\n * `chrome=\"bare\"` is a tab panel. The tab strip already shows the title and\n * draws the card, so a second title inside the panel reads as a heading nested\n * in its own heading. Bare mode therefore drops the wrapper and, for\n * integration leaves, renders the provider's `tab` (bare content) instead of\n * its `widget` (which draws its own card). That pairing is not new: it is\n * exactly what `CnIntegrationWidget` already does inside its own panels.\n *\n * ## The `data` widget in bare mode\n *\n * It drops its title, border and padding, so a tab panel holds the content\n * directly rather than a card inside a card.\n *\n * That used not to be possible. `CnWidgetWrapper` renders its actions INSIDE\n * the header and `CnObjectDataWidget` puts its **Save** button there, so\n * hiding the header to remove the duplicate title also removed the only way to\n * commit an inline edit; a silently unsaveable form being worse than a doubled\n * title, the title stayed. The wrapper now renders its header whenever there\n * are actions, with or without a title, so the two are separate requests and a\n * panel can have no title while staying saveable.\n *\n * ```vue\n * <CnDetailWidgetHost\n * :widget=\"def\"\n * :object-id=\"id\"\n * :object=\"record\"\n * register=\"dossiq\"\n * schema=\"case\"\n * chrome=\"bare\" />\n * ```\n */\nexport default {\n\tname: 'CnDetailWidgetHost',\n\n\tcomponents: {\n\t\tCnIcon,\n\t\tCnLeafMountHost,\n\t\tCnObjectDataWidget,\n\t\tCnObjectGeoWidget,\n\t\tCnRelatedObjectsWidget,\n\t\tCnWidgetWrapper,\n\t\tNcActionButton,\n\t\tNcEmptyContent,\n\t\tPlus,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * The surface's panel-action channel, when this host sits inside one.\n\t\t *\n\t\t * Absent on every other surface, which is why the default is null\n\t\t * rather than a required injection.\n\t\t */\n\t\tpanelActionSink: {\n\t\t\tfrom: PANEL_ACTION_SINK,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\t/**\n\t * Re-provide the channel with this panel's widget id baked in.\n\t *\n\t * The widget below does not know its own id on the surface and should not\n\t * have to, so the id is supplied here, where it is already known. Read at\n\t * CALL time rather than captured, so a host whose `widget` changes keeps\n\t * publishing under the right key.\n\t *\n\t * Provides null when there is no surface to publish to, so the widget's own\n\t * injection resolves to null instead of finding a channel that goes nowhere.\n\t *\n\t * @return {object} The narrowed sink, or null.\n\t */\n\tprovide() {\n\t\tconst sink = this.panelActionSink\n\t\treturn {\n\t\t\t[PANEL_ACTION_SINK]: sink\n\t\t\t\t? {\n\t\t\t\t\t\tset: (...args) => sink.set(...this.narrowPanelArgs(args, 'set')),\n\t\t\t\t\t\tclear: (...args) => sink.clear(...this.narrowPanelArgs(args, 'clear')),\n\t\t\t\t\t}\n\t\t\t\t: null,\n\t\t}\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The resolved widget definition: `{ id, type, title, icon, content }`,\n\t\t * plus `integrationId` for `type: 'integration'`.\n\t\t *\n\t\t * Resolved, not a layout item: turning an item into a definition needs\n\t\t * the surface's own widget list, which is the one part the two surfaces\n\t\t * genuinely do differently.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\twidget: {\n\t\t\ttype: Object,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/**\n\t\t * How much chrome to draw around the widget.\n\t\t * - `'card'` — a titled `CnWidgetWrapper`, the detail-page grid default.\n\t\t * - `'bare'` — no wrapper; the caller owns the title and the card.\n\t\t */\n\t\tchrome: {\n\t\t\ttype: String,\n\t\t\tdefault: 'card',\n\t\t\tvalidator: (v) => ['card', 'bare'].includes(v),\n\t\t},\n\n\t\t/** The bound record's id. Present on the first render; `object` is not. */\n\t\tobjectId: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The loaded record, or null while it is still being fetched. */\n\t\tobject: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The resolved object-type slug. */\n\t\tobjectType: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The resolved JSON Schema object, needed by the `data` widget. */\n\t\tschemaObject: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** OpenRegister register slug of the surface. */\n\t\tregister: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** OpenRegister schema slug of the surface. */\n\t\tschema: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The effective object store. */\n\t\tstore: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Rendering surface forwarded to integration widgets (AD-19). */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t},\n\n\t\t/** Object context forwarded to integration widgets. */\n\t\tintegrationContext: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Hide empty properties in the `data` widget. */\n\t\thideEmpty: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Every widget definition on the surface, for a CONTAINER widget to\n\t\t * resolve the children it references by id.\n\t\t *\n\t\t * Only container types receive it (see `rendererProps`). A leaf widget\n\t\t * has no business knowing what else is on the page.\n\t\t *\n\t\t * @type {object[]}\n\t\t */\n\t\tavailableWidgets: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Whether a card widget (stat / gauge / delta) draws the wrapper header.\n\t\t *\n\t\t * A prop, not a computed, because the rule reads the LAYOUT item's\n\t\t * `showTitle` and only the surface holds the layout. Deriving it from the\n\t\t * widget definition alone silently drops `showTitle: false`, which is the\n\t\t * one way a consumer can stop a card printing its title twice: the tile\n\t\t * already draws `content.label` itself.\n\t\t *\n\t\t * Null means \"decide from the definition\", which is what a surface with\n\t\t * no layout (a tab panel) wants.\n\t\t *\n\t\t * @type {boolean|null}\n\t\t */\n\t\tshowCardTitle: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * The consumer's component registry, consulted before the built-in\n\t\t * catalog so a custom widget type overrides a built-in (REQ-MVR-005).\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tcnRegistry: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\t},\n\n\temits: ['geo-saved', 'open-integration'],\n\n\tsetup() {\n\t\t// Every integration component this host renders goes through the\n\t\t// composable's resolvers, never through the registry entry's own\n\t\t// `tab` / `widget`. The resolvers swap a lib-owned entry for THIS\n\t\t// bundle's copy of the component (openregister#1958). The entry's\n\t\t// object belongs to whichever bundle registered it; when that is\n\t\t// OpenRegister's integration-global bundle, rendering it under the\n\t\t// app's Vue leaves `resolveComponent()` without a current instance,\n\t\t// and `<NcButton>` / `<CnDetailCard>` reach the DOM as literal\n\t\t// unknown elements. Seen on dossiq's Notes, Contacts and Files tabs.\n\t\tconst { resolveWidget, resolveBaseWidget, resolveTab, getById } = useIntegrationRegistry()\n\t\treturn {\n\t\t\tresolveRegistryWidget: resolveWidget,\n\t\t\tresolveRegistryBaseWidget: resolveBaseWidget,\n\t\t\tresolveRegistryTab: resolveTab,\n\t\t\tgetRegistryProvider: getById,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the caller owns the title and card.\n\t\t *\n\t\t * @return {boolean} true in bare mode.\n\t\t */\n\t\tisBare() {\n\t\t\treturn this.chrome === 'bare'\n\t\t},\n\n\t\t/**\n\t\t * The widget's stored config blob.\n\t\t *\n\t\t * @return {object} The `content`, or an empty object.\n\t\t */\n\t\tcontent() {\n\t\t\treturn widgetContentOf(this.widget)\n\t\t},\n\n\t\t/**\n\t\t * The title to render. Undefined in bare mode so a self-chromed widget\n\t\t * does not repeat what the tab strip already says.\n\t\t *\n\t\t * @return {string|undefined} The title, or undefined.\n\t\t */\n\t\tresolvedTitle() {\n\t\t\treturn this.isBare ? undefined : widgetTitleOf(this.widget)\n\t\t},\n\n\t\t/**\n\t\t * The app this widget leans on, if it declares one.\n\t\t *\n\t\t * Read from the widget definition first and its `content` second, so a\n\t\t * manifest may put it wherever the rest of that widget's config lives.\n\t\t *\n\t\t * @return {string} A Nextcloud app id, or ''.\n\t\t */\n\t\trequiredApp() {\n\t\t\t// `widget` is declared required, but CnDetailPage renders a host for\n\t\t\t// a layout item whose widget id resolves to nothing — and there is a\n\t\t\t// test that it must not throw for one. This computed runs in the\n\t\t\t// render path BEFORE the branches that tolerate a missing widget, so\n\t\t\t// it is the one that has to survive it.\n\t\t\treturn (this.widget || {}).requiredApp || this.content.requiredApp || ''\n\t\t},\n\n\t\t/**\n\t\t * Whether this widget's backing app is declared and absent.\n\t\t *\n\t\t * @return {boolean} true when the widget must render its set-up state.\n\t\t */\n\t\tmissingApp() {\n\t\t\treturn this.requiredApp !== '' && !isAppInstalled(this.requiredApp)\n\t\t},\n\n\t\t/**\n\t\t * The widget's title, independent of chrome mode.\n\t\t *\n\t\t * `resolvedTitle` is deliberately undefined in bare mode so a tabbed\n\t\t * widget does not repeat what the strip already says. The set-up state\n\t\t * still needs the name to say WHICH widget is inert.\n\t\t *\n\t\t * @return {string} The manifest title.\n\t\t */\n\t\twidgetTitle() {\n\t\t\treturn widgetTitleOf(this.widget) || ''\n\t\t},\n\n\t\t/**\n\t\t * Headline for the set-up state.\n\t\t *\n\t\t * @return {string} e.g. \"Humaniq is not installed\".\n\t\t */\n\t\tmissingAppName() {\n\t\t\treturn t('nextcloud-vue', '{app} is not installed', { app: this.requiredAppLabel })\n\t\t},\n\n\t\t/**\n\t\t * Body for the set-up state, naming what the reader is NOT seeing.\n\t\t *\n\t\t * @return {string} The description.\n\t\t */\n\t\tmissingAppDescription() {\n\t\t\treturn this.widgetTitle\n\t\t\t\t? t('nextcloud-vue', '{title} needs the {app} app. Install and enable it to see this.', {\n\t\t\t\t\t\ttitle: this.widgetTitle,\n\t\t\t\t\t\tapp: this.requiredAppLabel,\n\t\t\t\t\t})\n\t\t\t\t: t('nextcloud-vue', 'Install and enable the {app} app to see this.', {\n\t\t\t\t\t\tapp: this.requiredAppLabel,\n\t\t\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * The app id as a display name — `humaniq` reads as `Humaniq`.\n\t\t *\n\t\t * @return {string} The capitalised app id.\n\t\t */\n\t\trequiredAppLabel() {\n\t\t\treturn this.requiredApp.charAt(0).toUpperCase() + this.requiredApp.slice(1)\n\t\t},\n\n\t\t/** @return {boolean} true for `type: 'data'`. */\n\t\tisData() {\n\t\t\treturn isDataWidgetDef(this.widget)\n\t\t},\n\n\t\t/** @return {boolean} true for `type: 'related'`. */\n\t\tisRelated() {\n\t\t\treturn isRelatedWidgetDef(this.widget)\n\t\t},\n\n\t\t/** @return {boolean} true for `type: 'object-geo'`. */\n\t\tisGeo() {\n\t\t\treturn isGeoWidgetDef(this.widget)\n\t\t},\n\n\t\t/** @return {boolean} true for a registered integration leaf. */\n\t\tisIntegration() {\n\t\t\treturn isIntegrationWidgetDef(this.widget)\n\t\t},\n\n\t\t/** @return {boolean} true for object-list / table / files. */\n\t\tisContentOnly() {\n\t\t\treturn isContentOnlyWidgetDef(this.widget)\n\t\t},\n\n\t\t/** @return {boolean} true when the registry entry declares `card`. */\n\t\tisCard() {\n\t\t\treturn isCardWidgetDef(this.widget)\n\t\t},\n\n\t\t/**\n\t\t * The registered renderer for a content-driven catalog type.\n\t\t *\n\t\t * @return {object|null} The component, or null.\n\t\t */\n\t\trenderer() {\n\t\t\treturn resolveRegistryRenderer(this.widget, this.cnRegistry)\n\t\t},\n\n\t\t/**\n\t\t * The integration descriptor behind an `integration` widget.\n\t\t *\n\t\t * @return {object|null} The provider, or null.\n\t\t */\n\t\tintegrationProvider() {\n\t\t\tif (!this.isIntegration || typeof this.getRegistryProvider !== 'function') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn this.getRegistryProvider(this.widget.integrationId)\n\t\t},\n\n\t\t/**\n\t\t * Whether the integration is a mount-mode leaf (ADR-066), rendered\n\t\t * through CnLeafMountHost rather than as a component.\n\t\t *\n\t\t * @return {boolean} true for a mount-mode leaf.\n\t\t */\n\t\tisMountIntegration() {\n\t\t\tconst provider = this.integrationProvider\n\t\t\treturn Boolean(provider)\n\t\t\t\t&& provider.renderMode === 'mount'\n\t\t\t\t&& typeof provider.mount === 'function'\n\t\t\t\t&& typeof provider.unmount === 'function'\n\t\t},\n\n\t\t/**\n\t\t * Whether this render is using a provider's widget as its bare surface.\n\t\t *\n\t\t * @return {boolean} True when bare AND the provider opted in.\n\t\t */\n\t\tusesBareWidget() {\n\t\t\treturn !!(this.isBare && this.integrationProvider?.bareWidget && this.integrationProvider?.widget)\n\t\t},\n\n\t\t/**\n\t\t * The component for a component-mode integration leaf. Bare mode takes\n\t\t * the provider's `tab`, which is its content without a card, unless the\n\t\t * provider opted into `bareWidget`.\n\t\t *\n\t\t * Each branch resolves through the registry composable rather than\n\t\t * reading `integrationProvider.widget` / `.tab`. The provider is the\n\t\t * shared registry's entry, and its component objects belong to the\n\t\t * bundle that registered it. For a lib-owned id registered by\n\t\t * OpenRegister's bundle, that object renders under the wrong Vue: its\n\t\t * `resolveComponent()` finds no current instance, so `NcButton` and\n\t\t * `CnDetailCard` reach the DOM as literal `<ncbutton>` /\n\t\t * `<cndetailcard>` elements, and a Files tab whose whole body sits\n\t\t * inside that card looks empty. The resolvers hand back this bundle's\n\t\t * own copy for lib-owned ids and the stored object for consumer ids.\n\t\t *\n\t\t * @return {object|null} The component, or null.\n\t\t */\n\t\tintegrationComponent() {\n\t\t\tif (!this.isIntegration) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst id = this.widget.integrationId\n\t\t\t// `bareWidget` lets a provider say its WIDGET is already bare, so a\n\t\t\t// tab panel gets the widget surface instead of the sidebar one.\n\t\t\t// The default below (prefer `tab`) assumes every `widget` draws its\n\t\t\t// own card, which is not true: `notes`' widget is an adapter around\n\t\t\t// CnNotesCard, whose root carries no border, background or padding.\n\t\t\t// Preferring `tab` there swapped an inline compose textarea for the\n\t\t\t// sidebar's collapsed \"Add note\" button and dressed the panel in\n\t\t\t// sidebar CSS, which is what made the tabbed leaves look like they\n\t\t\t// had lost their styling. Opt-in rather than a blanket switch: the\n\t\t\t// other providers keep today's behaviour until each is checked.\n\t\t\tif (this.usesBareWidget && typeof this.resolveRegistryBaseWidget === 'function') {\n\t\t\t\treturn this.resolveRegistryBaseWidget(id)\n\t\t\t}\n\t\t\tif (this.isBare && this.integrationProvider?.tab && typeof this.resolveRegistryTab === 'function') {\n\t\t\t\treturn this.resolveRegistryTab(id)\n\t\t\t}\n\t\t\tif (typeof this.resolveRegistryWidget !== 'function') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn this.resolveRegistryWidget(id, this.surface)\n\t\t},\n\n\t\t/**\n\t\t * The object context every integration surface is handed.\n\t\t *\n\t\t * @return {object} `{ register, schema, objectId }`.\n\t\t */\n\t\tderivedIntegrationContext() {\n\t\t\treturn this.integrationContext || {\n\t\t\t\tregister: this.register || '',\n\t\t\t\tschema: this.schema || this.objectType || '',\n\t\t\t\tobjectId: this.objectId ? String(this.objectId) : '',\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Props for an integration widget or tab.\n\t\t *\n\t\t * The title is forwarded because `CnIntegrationCard`'s `cardTitle` falls\n\t\t * back to the raw `integrationId` without it, so a widget the manifest\n\t\t * calls \"Class space\" rendered as \"talk\". It is placed BEFORE `def.props`\n\t\t * so an explicit per-widget prop still wins.\n\t\t *\n\t\t * @return {object} The prop bag.\n\t\t */\n\t\tintegrationProps() {\n\t\t\treturn {\n\t\t\t\tsurface: this.surface,\n\t\t\t\ttitle: this.widget?.title || '',\n\t\t\t\t// A provider opting into `bareWidget` is being rendered where the\n\t\t\t\t// tab strip already supplies the card and the title, so tell it to\n\t\t\t\t// drop its own. Without this the panel gets a titled card inside\n\t\t\t\t// the tabs card, repeating the label the open tab already shows.\n\t\t\t\t...(this.usesBareWidget ? { chromeless: true } : {}),\n\t\t\t\t...this.derivedIntegrationContext,\n\t\t\t\t...(this.widget?.props || {}),\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Props handed to a mount-mode leaf.\n\t\t *\n\t\t * @return {object} The mount prop bag.\n\t\t */\n\t\tintegrationMountProps() {\n\t\t\treturn {\n\t\t\t\tsurface: this.surface,\n\t\t\t\t...this.derivedIntegrationContext,\n\t\t\t\tintegrationContext: this.derivedIntegrationContext,\n\t\t\t\t...(this.widget?.props || {}),\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Props for a catalog renderer: its content plus the object context, in\n\t\t * the shape CnWidgetGrid merges on the v2 widgets[] path. `content`\n\t\t * spreads last so explicit widget config still wins.\n\t\t *\n\t\t * @return {object} The prop bag.\n\t\t */\n\t\trendererProps() {\n\t\t\tconst base = {\n\t\t\t\tcontent: this.content,\n\t\t\t\tobjectId: this.objectId,\n\t\t\t\tregister: this.register,\n\t\t\t\tschema: this.schema,\n\t\t\t\tobjectData: this.object,\n\t\t\t\tobjectType: this.objectType,\n\t\t\t\tstore: this.store,\n\t\t\t}\n\t\t\t// A container widget renders other widgets, so it needs what a\n\t\t\t// surface knows and a leaf does not: the sibling definitions to\n\t\t\t// resolve its children against, and the context those children will\n\t\t\t// need. Handing this to every widget instead would put objects on\n\t\t\t// the DOM as stringified attributes for the ones that ignore them.\n\t\t\tif (this.isContainer) {\n\t\t\t\tObject.assign(base, {\n\t\t\t\t\tavailableWidgets: this.availableWidgets,\n\t\t\t\t\tschemaObject: this.schemaObject,\n\t\t\t\t\tintegrationContext: this.integrationContext,\n\t\t\t\t\tcnRegistry: this.cnRegistry,\n\t\t\t\t\tsurface: this.surface,\n\t\t\t\t})\n\t\t\t}\n\t\t\treturn { ...base, ...this.content }\n\t\t},\n\n\t\t/**\n\t\t * Whether the type declares itself a container (registry `container`).\n\t\t *\n\t\t * @return {boolean} true for a widget that renders other widgets.\n\t\t */\n\t\tisContainer() {\n\t\t\tif (!this.widget?.type) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst entry = getWidgetTypeEntry(this.widget.type)\n\t\t\treturn Boolean(entry && entry.container === true)\n\t\t},\n\n\t\t/**\n\t\t * Whether a catalog list widget offers the Add action (ADR-062:\n\t\t * collections carry their create affordance in the card's Actions menu).\n\t\t * Files widgets carry their own upload toolbar, so they are excluded.\n\t\t *\n\t\t * @return {boolean} true when Add should render.\n\t\t */\n\t\tcatalogAddEnabled() {\n\t\t\tif (!['object-list', 'table'].includes(this.widget?.type)) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn this.content.allowCreate !== false\n\t\t},\n\n\t\t/**\n\t\t * The card header rule, with the surface's layout-driven answer winning\n\t\t * when it supplied one.\n\t\t *\n\t\t * @return {boolean} true when the wrapper header renders.\n\t\t */\n\t\teffectiveShowCardTitle() {\n\t\t\tif (this.showCardTitle !== null) {\n\t\t\t\treturn this.showCardTitle\n\t\t\t}\n\t\t\treturn this.widget?.title !== undefined || this.content.title !== undefined\n\t\t},\n\n\t\t/** @return {string} Label for the catalog Add action. */\n\t\taddLabel() {\n\t\t\treturn t('nextcloud-vue', 'Add')\n\t\t},\n\n\t\t/**\n\t\t * The items THIS HOST offers to the surface, as opposed to the ones the\n\t\t * widget inside it publishes.\n\t\t *\n\t\t * There is one: the catalog Add. It is drawn in the host's own card\n\t\t * header, and that header only exists off a panel, so a Documents or\n\t\t * Files tab simply had no way to add anything. The data widget's\n\t\t * Metadata went the same way until it was published, and this is the\n\t\t * same defect one widget type along.\n\t\t *\n\t\t * Empty off a panel, where the header draws the item itself and\n\t\t * publishing would put it in two menus at once.\n\t\t *\n\t\t * @return {object[]} PanelAction descriptors.\n\t\t */\n\t\townPanelActions() {\n\t\t\tif (!this.isBare || !this.catalogAddEnabled) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn [{\n\t\t\t\tkey: 'catalog-add',\n\t\t\t\tlabel: this.addLabel,\n\t\t\t\ticon: 'Plus',\n\t\t\t\trun: () => this.invokeCatalogAdd(),\n\t\t\t}]\n\t\t},\n\t},\n\n\twatch: {\n\t\townPanelActions: {\n\t\t\thandler() {\n\t\t\t\tthis.publishOwnPanelActions()\n\t\t\t},\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.publishOwnPanelActions()\n\t},\n\n\tbeforeUnmount() {\n\t\t// A closed tab's panel can be torn down while the strip lives on, and an\n\t\t// item whose host is gone would call into nothing.\n\t\tif (this.panelActionSink) {\n\t\t\tthis.panelActionSink.clear(this.widget?.id, 'host')\n\t\t}\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Re-key a call on the channel this host provides onto the one it holds.\n\t\t * A widget publishes `set(items)`; a host nested in this one speaks the\n\t\t * surface's `set(id, items, source)`. Items are always an array and an\n\t\t * id never is, which tells the two apart.\n\t\t *\n\t\t * @param {Array} args The arguments as called.\n\t\t * @param {'set'|'clear'} kind Which channel method was called.\n\t\t * @return {Array} Arguments for the held channel.\n\t\t */\n\t\tnarrowPanelArgs(args, kind) {\n\t\t\tconst id = this.widget?.id\n\t\t\tif (kind === 'set') {\n\t\t\t\treturn Array.isArray(args[0])\n\t\t\t\t\t? [id, args[0], 'widget']\n\t\t\t\t\t: [id, args[1], args[2] || 'widget']\n\t\t\t}\n\t\t\treturn [id, args.length > 1 ? (args[1] || 'widget') : 'widget']\n\t\t},\n\n\t\t/**\n\t\t * Publish or withdraw this host's OWN items on the surface.\n\t\t *\n\t\t * Published under the `host` source so the widget inside the panel can\n\t\t * publish its own without either replacing the other.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tpublishOwnPanelActions() {\n\t\t\tif (!this.panelActionSink) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst id = this.widget?.id\n\t\t\tif (!id) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.ownPanelActions.length) {\n\t\t\t\tthis.panelActionSink.set(id, this.ownPanelActions, 'host')\n\t\t\t} else {\n\t\t\t\tthis.panelActionSink.clear(id, 'host')\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Re-emit the geo widget's save so the surface can reload the record.\n\t\t *\n\t\t * @param {object} geo The saved geometry.\n\t\t * @return {void}\n\t\t */\n\t\tonGeoSaved(geo) {\n\t\t\t/**\n\t\t\t * @event geo-saved Emitted when the geo widget saved a geometry.\n\t\t\t * @type {object}\n\t\t\t */\n\t\t\tthis.$emit('geo-saved', geo)\n\t\t},\n\n\t\t/**\n\t\t * Re-emit the related widget's request to open an integration.\n\t\t *\n\t\t * @param {string} integrationId The integration to open.\n\t\t * @return {void}\n\t\t */\n\t\tonOpenIntegration(integrationId) {\n\t\t\t/**\n\t\t\t * @event open-integration Emitted when the related widget asks to open an integration.\n\t\t\t * @type {string}\n\t\t\t */\n\t\t\tthis.$emit('open-integration', integrationId)\n\t\t},\n\n\t\t/**\n\t\t * Delegate to the rendered list widget's own create affordance, so the\n\t\t * card's Add action and the widget's footer button open the same form.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tinvokeCatalogAdd() {\n\t\t\tconst child = this.$refs.renderer\n\t\t\tconst target = Array.isArray(child) ? child[0] : child\n\t\t\tif (target && typeof target.openCreate === 'function') {\n\t\t\t\ttarget.openCreate()\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-detail-widget-host,\n.cn-detail-widget-host > * {\n\theight: 100%;\n}\n\n/* A bare host is a tab panel's body: it fills the panel and lets the panel\n scroll, rather than growing a second scroll region of its own. */\n.cn-detail-widget-host--bare {\n\tmin-height: 0;\n}\n</style>\n"],"names":["_createBlock","_createVNode","_resolveDynamicComponent","_normalizeProps","_mergeProps","_createTextVNode"],"mappings":"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"}
|
|
@@ -10,7 +10,9 @@ import InformationOutline from 'vue-material-design-icons/InformationOutline.vue
|
|
|
10
10
|
* `fieldsFromSchema` splits it and puts the full text on the field's
|
|
11
11
|
* `descriptionLong`; pass that as `more` and this renders an ⓘ button that
|
|
12
12
|
* reveals it in a popover, so a paragraph-length schema description cannot
|
|
13
|
-
* push the rest of the form off screen.
|
|
13
|
+
* push the rest of the form off screen. A schema property's `x-help` text
|
|
14
|
+
* also lands on `descriptionLong`, so the ⓘ shows even when the description
|
|
15
|
+
* is short or absent.
|
|
14
16
|
*
|
|
15
17
|
* `CnFormDialog` uses this for every auto-generated field. Use it directly when
|
|
16
18
|
* rendering your own fields through the `#form-fields` or `#field-<key>` slots,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnFieldHelper.vue2.js","sources":["../../../../src/components/CnFieldHelper/CnFieldHelper.vue"],"sourcesContent":["<template>\n\t<!-- Keeps the legacy `cn-form-dialog__helper` class alongside its own so\n\t consumer stylesheets that target the old helper span still apply. -->\n\t<span\n\t\tv-if=\"error || text\"\n\t\tclass=\"cn-field-helper cn-form-dialog__helper\"\n\t\t:class=\"{ 'cn-field-helper--error': !!error, 'cn-form-dialog__helper--error': !!error }\">\n\t\t{{ error || text }}\n\t\t<NcPopover\n\t\t\tv-if=\"!error && more\"\n\t\t\tv-model:shown=\"open\"\n\t\t\t:triggers=\"[]\"\n\t\t\tpopupRole=\"dialog\"\n\t\t\tpopoverBaseClass=\"cn-field-helper__popper\">\n\t\t\t<template #trigger>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"cn-field-helper__trigger\"\n\t\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Show the full description')\"\n\t\t\t\t\t:aria-expanded=\"open\"\n\t\t\t\t\t@click=\"open = !open\">\n\t\t\t\t\t<InformationOutline :size=\"16\" />\n\t\t\t\t</button>\n\t\t\t</template>\n\t\t\t<div class=\"cn-field-helper__full\">\n\t\t\t\t{{ more }}\n\t\t\t</div>\n\t\t</NcPopover>\n\t</span>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcPopover } from '@nextcloud/vue'\nimport InformationOutline from 'vue-material-design-icons/InformationOutline.vue'\n\n/**\n * CnFieldHelper — the helper line rendered under a form field.\n *\n * Shows the validation error when there is one, otherwise the field's\n * description. When the description was too long to sit inline,\n * `fieldsFromSchema` splits it and puts the full text on the field's\n * `descriptionLong`; pass that as `more` and this renders an ⓘ button that\n * reveals it in a popover, so a paragraph-length schema description cannot\n * push the rest of the form off screen.\n *\n * `CnFormDialog` uses this for every auto-generated field. Use it directly when\n * rendering your own fields through the `#form-fields` or `#field-<key>` slots,\n * so a custom form surface keeps the same behaviour:\n *\n * ```vue\n * <CnFieldHelper\n * :text=\"field.description\"\n * :more=\"field.descriptionLong\"\n * :error=\"errors[field.key]\" />\n * ```\n */\nexport default {\n\tname: 'CnFieldHelper',\n\n\tcomponents: {\n\t\tNcPopover,\n\t\tInformationOutline,\n\t},\n\n\tprops: {\n\t\t/** Inline helper text (a field's short description). */\n\t\ttext: { type: String, default: '' },\n\t\t/** Full description, shown in the popover. Empty → no info button. */\n\t\tmore: { type: String, default: '' },\n\t\t/** Validation error; replaces the helper text and hides the popover. */\n\t\terror: { type: String, default: '' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\topen: false,\n\t\t}\n\t},\n\n\tmethods: {\n\t\tt,\n\t},\n}\n</script>\n\n<style scoped>\n/* Mirrors `.cn-form-dialog__helper` so the line looks identical now that it\n renders from this component rather than inline in CnFormDialog.\n Flex row because NcPopover's trigger wrapper is a plain <div> (floating-vue\n renders `<div class=\"v-popper\">`), which as a block pushed the ⓘ onto its own\n line under the text. Making the helper a flex container puts the wrapper back\n beside the text. */\n.cn-field-helper {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 4px;\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-field-helper--error {\n\tcolor: var(--color-error);\n}\n\n/* The button sits inside NcPopover's wrapper, so it is that wrapper — not the\n button — that must hold its width against the text beside it. */\n.cn-field-helper > :deep(.v-popper) {\n\tflex: 0 0 auto;\n\tdisplay: inline-flex;\n}\n\n/* Server's core/css/inputs.css gives every bare button\n `min-height: var(--default-clickable-area)` with `width: auto`, so the target\n came out 34px tall but only as wide as the 16px glyph. Size both axes to the\n clickable area and centre the icon in it. */\n.cn-field-helper__trigger {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\tinline-size: var(--default-clickable-area);\n\tblock-size: var(--default-clickable-area);\n\tpadding: 0;\n\tborder: none;\n\tbackground: transparent;\n\tcolor: var(--color-text-maxcontrast);\n\tcursor: pointer;\n}\n\n/* vue-material-design-icons ships a stylesheet nothing here imports, so the\n icon span is an unstyled inline box and its svg sits on the text baseline\n with descender slack under it — enough to read as off-centre. Make it a flex\n box so the glyph is the whole item and centres exactly. */\n.cn-field-helper__trigger :deep(.material-design-icon) {\n\tdisplay: flex;\n\tline-height: 1;\n}\n\n.cn-field-helper__trigger:hover,\n.cn-field-helper__trigger:focus-visible {\n\tcolor: var(--color-main-text);\n}\n\n.cn-field-helper__full {\n\tmax-width: 380px;\n\tmax-height: 320px;\n\toverflow-y: auto;\n\tpadding: 12px;\n\tfont-size: 0.9em;\n\tline-height: 1.4;\n\twhite-space: pre-wrap;\n}\n</style>\n"],"names":["t"],"mappings":";;;;AAoCA;;AAEC;;;;;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AAIA;;AAED,aAAe;AACd,CAAA,IAAI,EAAE,eAAe;;CAErB,UAAU,EAAE;AACX,EAAA,SAAS;AACT,EAAA,kBAAkB;EAClB;;AAED,CAAA,KAAK,EAAE;;EAEN,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAC,EAAG;;EAEnC,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAC,EAAG;;AAEnC,EAAA,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAC,EAAG;EACpC;;AAED,CAAA,IAAI,GAAG;AACN,EAAA,OAAO;GACN,IAAI,EAAE,KAAK;AACZ,GAAA;EACA;;AAED,CAAA,OAAO,EAAE;KACRA,SAAC;EACD;AACF;;;;"}
|
|
1
|
+
{"version":3,"file":"CnFieldHelper.vue2.js","sources":["../../../../src/components/CnFieldHelper/CnFieldHelper.vue"],"sourcesContent":["<template>\n\t<!-- Keeps the legacy `cn-form-dialog__helper` class alongside its own so\n\t consumer stylesheets that target the old helper span still apply. -->\n\t<span\n\t\tv-if=\"error || text || more\"\n\t\tclass=\"cn-field-helper cn-form-dialog__helper\"\n\t\t:class=\"{ 'cn-field-helper--error': !!error, 'cn-form-dialog__helper--error': !!error }\">\n\t\t{{ error || text }}\n\t\t<NcPopover\n\t\t\tv-if=\"!error && more\"\n\t\t\tv-model:shown=\"open\"\n\t\t\t:triggers=\"[]\"\n\t\t\tpopupRole=\"dialog\"\n\t\t\tpopoverBaseClass=\"cn-field-helper__popper\">\n\t\t\t<template #trigger>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"cn-field-helper__trigger\"\n\t\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Show the full description')\"\n\t\t\t\t\t:aria-expanded=\"open\"\n\t\t\t\t\t@click=\"open = !open\">\n\t\t\t\t\t<InformationOutline :size=\"16\" />\n\t\t\t\t</button>\n\t\t\t</template>\n\t\t\t<div class=\"cn-field-helper__full\">\n\t\t\t\t{{ more }}\n\t\t\t</div>\n\t\t</NcPopover>\n\t</span>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcPopover } from '@nextcloud/vue'\nimport InformationOutline from 'vue-material-design-icons/InformationOutline.vue'\n\n/**\n * CnFieldHelper — the helper line rendered under a form field.\n *\n * Shows the validation error when there is one, otherwise the field's\n * description. When the description was too long to sit inline,\n * `fieldsFromSchema` splits it and puts the full text on the field's\n * `descriptionLong`; pass that as `more` and this renders an ⓘ button that\n * reveals it in a popover, so a paragraph-length schema description cannot\n * push the rest of the form off screen. A schema property's `x-help` text\n * also lands on `descriptionLong`, so the ⓘ shows even when the description\n * is short or absent.\n *\n * `CnFormDialog` uses this for every auto-generated field. Use it directly when\n * rendering your own fields through the `#form-fields` or `#field-<key>` slots,\n * so a custom form surface keeps the same behaviour:\n *\n * ```vue\n * <CnFieldHelper\n * :text=\"field.description\"\n * :more=\"field.descriptionLong\"\n * :error=\"errors[field.key]\" />\n * ```\n */\nexport default {\n\tname: 'CnFieldHelper',\n\n\tcomponents: {\n\t\tNcPopover,\n\t\tInformationOutline,\n\t},\n\n\tprops: {\n\t\t/** Inline helper text (a field's short description). */\n\t\ttext: { type: String, default: '' },\n\t\t/** Full description, shown in the popover. Empty → no info button. */\n\t\tmore: { type: String, default: '' },\n\t\t/** Validation error; replaces the helper text and hides the popover. */\n\t\terror: { type: String, default: '' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\topen: false,\n\t\t}\n\t},\n\n\tmethods: {\n\t\tt,\n\t},\n}\n</script>\n\n<style scoped>\n/* Mirrors `.cn-form-dialog__helper` so the line looks identical now that it\n renders from this component rather than inline in CnFormDialog.\n Flex row because NcPopover's trigger wrapper is a plain <div> (floating-vue\n renders `<div class=\"v-popper\">`), which as a block pushed the ⓘ onto its own\n line under the text. Making the helper a flex container puts the wrapper back\n beside the text. */\n.cn-field-helper {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 4px;\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-field-helper--error {\n\tcolor: var(--color-error);\n}\n\n/* The button sits inside NcPopover's wrapper, so it is that wrapper — not the\n button — that must hold its width against the text beside it. */\n.cn-field-helper > :deep(.v-popper) {\n\tflex: 0 0 auto;\n\tdisplay: inline-flex;\n}\n\n/* Server's core/css/inputs.css gives every bare button\n `min-height: var(--default-clickable-area)` with `width: auto`, so the target\n came out 34px tall but only as wide as the 16px glyph. Size both axes to the\n clickable area and centre the icon in it. */\n.cn-field-helper__trigger {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\tinline-size: var(--default-clickable-area);\n\tblock-size: var(--default-clickable-area);\n\tpadding: 0;\n\tborder: none;\n\tbackground: transparent;\n\tcolor: var(--color-text-maxcontrast);\n\tcursor: pointer;\n}\n\n/* vue-material-design-icons ships a stylesheet nothing here imports, so the\n icon span is an unstyled inline box and its svg sits on the text baseline\n with descender slack under it — enough to read as off-centre. Make it a flex\n box so the glyph is the whole item and centres exactly. */\n.cn-field-helper__trigger :deep(.material-design-icon) {\n\tdisplay: flex;\n\tline-height: 1;\n}\n\n.cn-field-helper__trigger:hover,\n.cn-field-helper__trigger:focus-visible {\n\tcolor: var(--color-main-text);\n}\n\n.cn-field-helper__full {\n\tmax-width: 380px;\n\tmax-height: 320px;\n\toverflow-y: auto;\n\tpadding: 12px;\n\tfont-size: 0.9em;\n\tline-height: 1.4;\n\twhite-space: pre-wrap;\n}\n</style>\n"],"names":["t"],"mappings":";;;;AAoCA;;AAEC;;;;;AAKA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AAIA;;AAED,aAAe;AACd,CAAA,IAAI,EAAE,eAAe;;CAErB,UAAU,EAAE;AACX,EAAA,SAAS;AACT,EAAA,kBAAkB;EAClB;;AAED,CAAA,KAAK,EAAE;;EAEN,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAC,EAAG;;EAEnC,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAC,EAAG;;AAEnC,EAAA,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAC,EAAG;EACpC;;AAED,CAAA,IAAI,GAAG;AACN,EAAA,OAAO;GACN,IAAI,EAAE,KAAK;AACZ,GAAA;EACA;;AAED,CAAA,OAAO,EAAE;KACRA,SAAC;EACD;AACF;;;;"}
|
|
@@ -7,7 +7,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
7
7
|
const _component_InformationOutline = resolveComponent("InformationOutline");
|
|
8
8
|
const _component_NcPopover = resolveComponent("NcPopover");
|
|
9
9
|
|
|
10
|
-
return ($props.error || $props.text)
|
|
10
|
+
return ($props.error || $props.text || $props.more)
|
|
11
11
|
? (openBlock(), createElementBlock("span", {
|
|
12
12
|
key: 0,
|
|
13
13
|
class: normalizeClass(["cn-field-helper cn-form-dialog__helper", { 'cn-field-helper--error': !!$props.error, 'cn-form-dialog__helper--error': !!$props.error }])
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnFieldHelper.vue3.js","sources":["../../../../src/components/CnFieldHelper/CnFieldHelper.vue?vue&type=template&id=3797f4ea&lang.js"],"sourcesContent":["<template>\n\t<!-- Keeps the legacy `cn-form-dialog__helper` class alongside its own so\n\t consumer stylesheets that target the old helper span still apply. -->\n\t<span\n\t\tv-if=\"error || text\"\n\t\tclass=\"cn-field-helper cn-form-dialog__helper\"\n\t\t:class=\"{ 'cn-field-helper--error': !!error, 'cn-form-dialog__helper--error': !!error }\">\n\t\t{{ error || text }}\n\t\t<NcPopover\n\t\t\tv-if=\"!error && more\"\n\t\t\tv-model:shown=\"open\"\n\t\t\t:triggers=\"[]\"\n\t\t\tpopupRole=\"dialog\"\n\t\t\tpopoverBaseClass=\"cn-field-helper__popper\">\n\t\t\t<template #trigger>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"cn-field-helper__trigger\"\n\t\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Show the full description')\"\n\t\t\t\t\t:aria-expanded=\"open\"\n\t\t\t\t\t@click=\"open = !open\">\n\t\t\t\t\t<InformationOutline :size=\"16\" />\n\t\t\t\t</button>\n\t\t\t</template>\n\t\t\t<div class=\"cn-field-helper__full\">\n\t\t\t\t{{ more }}\n\t\t\t</div>\n\t\t</NcPopover>\n\t</span>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcPopover } from '@nextcloud/vue'\nimport InformationOutline from 'vue-material-design-icons/InformationOutline.vue'\n\n/**\n * CnFieldHelper — the helper line rendered under a form field.\n *\n * Shows the validation error when there is one, otherwise the field's\n * description. When the description was too long to sit inline,\n * `fieldsFromSchema` splits it and puts the full text on the field's\n * `descriptionLong`; pass that as `more` and this renders an ⓘ button that\n * reveals it in a popover, so a paragraph-length schema description cannot\n * push the rest of the form off screen.\n *\n * `CnFormDialog` uses this for every auto-generated field. Use it directly when\n * rendering your own fields through the `#form-fields` or `#field-<key>` slots,\n * so a custom form surface keeps the same behaviour:\n *\n * ```vue\n * <CnFieldHelper\n * :text=\"field.description\"\n * :more=\"field.descriptionLong\"\n * :error=\"errors[field.key]\" />\n * ```\n */\nexport default {\n\tname: 'CnFieldHelper',\n\n\tcomponents: {\n\t\tNcPopover,\n\t\tInformationOutline,\n\t},\n\n\tprops: {\n\t\t/** Inline helper text (a field's short description). */\n\t\ttext: { type: String, default: '' },\n\t\t/** Full description, shown in the popover. Empty → no info button. */\n\t\tmore: { type: String, default: '' },\n\t\t/** Validation error; replaces the helper text and hides the popover. */\n\t\terror: { type: String, default: '' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\topen: false,\n\t\t}\n\t},\n\n\tmethods: {\n\t\tt,\n\t},\n}\n</script>\n\n<style scoped>\n/* Mirrors `.cn-form-dialog__helper` so the line looks identical now that it\n renders from this component rather than inline in CnFormDialog.\n Flex row because NcPopover's trigger wrapper is a plain <div> (floating-vue\n renders `<div class=\"v-popper\">`), which as a block pushed the ⓘ onto its own\n line under the text. Making the helper a flex container puts the wrapper back\n beside the text. */\n.cn-field-helper {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 4px;\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-field-helper--error {\n\tcolor: var(--color-error);\n}\n\n/* The button sits inside NcPopover's wrapper, so it is that wrapper — not the\n button — that must hold its width against the text beside it. */\n.cn-field-helper > :deep(.v-popper) {\n\tflex: 0 0 auto;\n\tdisplay: inline-flex;\n}\n\n/* Server's core/css/inputs.css gives every bare button\n `min-height: var(--default-clickable-area)` with `width: auto`, so the target\n came out 34px tall but only as wide as the 16px glyph. Size both axes to the\n clickable area and centre the icon in it. */\n.cn-field-helper__trigger {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\tinline-size: var(--default-clickable-area);\n\tblock-size: var(--default-clickable-area);\n\tpadding: 0;\n\tborder: none;\n\tbackground: transparent;\n\tcolor: var(--color-text-maxcontrast);\n\tcursor: pointer;\n}\n\n/* vue-material-design-icons ships a stylesheet nothing here imports, so the\n icon span is an unstyled inline box and its svg sits on the text baseline\n with descender slack under it — enough to read as off-centre. Make it a flex\n box so the glyph is the whole item and centres exactly. */\n.cn-field-helper__trigger :deep(.material-design-icon) {\n\tdisplay: flex;\n\tline-height: 1;\n}\n\n.cn-field-helper__trigger:hover,\n.cn-field-helper__trigger:focus-visible {\n\tcolor: var(--color-main-text);\n}\n\n.cn-field-helper__full {\n\tmax-width: 380px;\n\tmax-height: 320px;\n\toverflow-y: auto;\n\tpadding: 12px;\n\tfont-size: 0.9em;\n\tline-height: 1.4;\n\twhite-space: pre-wrap;\n}\n</style>\n"],"names":["_createElementBlock","_normalizeClass","_createBlock","_createElementVNode","_createVNode","_toDisplayString"],"mappings":";;;AAwBQ,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,uBAAuB,GAAA;;;;;;
|
|
1
|
+
{"version":3,"file":"CnFieldHelper.vue3.js","sources":["../../../../src/components/CnFieldHelper/CnFieldHelper.vue?vue&type=template&id=3797f4ea&lang.js"],"sourcesContent":["<template>\n\t<!-- Keeps the legacy `cn-form-dialog__helper` class alongside its own so\n\t consumer stylesheets that target the old helper span still apply. -->\n\t<span\n\t\tv-if=\"error || text || more\"\n\t\tclass=\"cn-field-helper cn-form-dialog__helper\"\n\t\t:class=\"{ 'cn-field-helper--error': !!error, 'cn-form-dialog__helper--error': !!error }\">\n\t\t{{ error || text }}\n\t\t<NcPopover\n\t\t\tv-if=\"!error && more\"\n\t\t\tv-model:shown=\"open\"\n\t\t\t:triggers=\"[]\"\n\t\t\tpopupRole=\"dialog\"\n\t\t\tpopoverBaseClass=\"cn-field-helper__popper\">\n\t\t\t<template #trigger>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"cn-field-helper__trigger\"\n\t\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Show the full description')\"\n\t\t\t\t\t:aria-expanded=\"open\"\n\t\t\t\t\t@click=\"open = !open\">\n\t\t\t\t\t<InformationOutline :size=\"16\" />\n\t\t\t\t</button>\n\t\t\t</template>\n\t\t\t<div class=\"cn-field-helper__full\">\n\t\t\t\t{{ more }}\n\t\t\t</div>\n\t\t</NcPopover>\n\t</span>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcPopover } from '@nextcloud/vue'\nimport InformationOutline from 'vue-material-design-icons/InformationOutline.vue'\n\n/**\n * CnFieldHelper — the helper line rendered under a form field.\n *\n * Shows the validation error when there is one, otherwise the field's\n * description. When the description was too long to sit inline,\n * `fieldsFromSchema` splits it and puts the full text on the field's\n * `descriptionLong`; pass that as `more` and this renders an ⓘ button that\n * reveals it in a popover, so a paragraph-length schema description cannot\n * push the rest of the form off screen. A schema property's `x-help` text\n * also lands on `descriptionLong`, so the ⓘ shows even when the description\n * is short or absent.\n *\n * `CnFormDialog` uses this for every auto-generated field. Use it directly when\n * rendering your own fields through the `#form-fields` or `#field-<key>` slots,\n * so a custom form surface keeps the same behaviour:\n *\n * ```vue\n * <CnFieldHelper\n * :text=\"field.description\"\n * :more=\"field.descriptionLong\"\n * :error=\"errors[field.key]\" />\n * ```\n */\nexport default {\n\tname: 'CnFieldHelper',\n\n\tcomponents: {\n\t\tNcPopover,\n\t\tInformationOutline,\n\t},\n\n\tprops: {\n\t\t/** Inline helper text (a field's short description). */\n\t\ttext: { type: String, default: '' },\n\t\t/** Full description, shown in the popover. Empty → no info button. */\n\t\tmore: { type: String, default: '' },\n\t\t/** Validation error; replaces the helper text and hides the popover. */\n\t\terror: { type: String, default: '' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\topen: false,\n\t\t}\n\t},\n\n\tmethods: {\n\t\tt,\n\t},\n}\n</script>\n\n<style scoped>\n/* Mirrors `.cn-form-dialog__helper` so the line looks identical now that it\n renders from this component rather than inline in CnFormDialog.\n Flex row because NcPopover's trigger wrapper is a plain <div> (floating-vue\n renders `<div class=\"v-popper\">`), which as a block pushed the ⓘ onto its own\n line under the text. Making the helper a flex container puts the wrapper back\n beside the text. */\n.cn-field-helper {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 4px;\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-field-helper--error {\n\tcolor: var(--color-error);\n}\n\n/* The button sits inside NcPopover's wrapper, so it is that wrapper — not the\n button — that must hold its width against the text beside it. */\n.cn-field-helper > :deep(.v-popper) {\n\tflex: 0 0 auto;\n\tdisplay: inline-flex;\n}\n\n/* Server's core/css/inputs.css gives every bare button\n `min-height: var(--default-clickable-area)` with `width: auto`, so the target\n came out 34px tall but only as wide as the 16px glyph. Size both axes to the\n clickable area and centre the icon in it. */\n.cn-field-helper__trigger {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\tinline-size: var(--default-clickable-area);\n\tblock-size: var(--default-clickable-area);\n\tpadding: 0;\n\tborder: none;\n\tbackground: transparent;\n\tcolor: var(--color-text-maxcontrast);\n\tcursor: pointer;\n}\n\n/* vue-material-design-icons ships a stylesheet nothing here imports, so the\n icon span is an unstyled inline box and its svg sits on the text baseline\n with descender slack under it — enough to read as off-centre. Make it a flex\n box so the glyph is the whole item and centres exactly. */\n.cn-field-helper__trigger :deep(.material-design-icon) {\n\tdisplay: flex;\n\tline-height: 1;\n}\n\n.cn-field-helper__trigger:hover,\n.cn-field-helper__trigger:focus-visible {\n\tcolor: var(--color-main-text);\n}\n\n.cn-field-helper__full {\n\tmax-width: 380px;\n\tmax-height: 320px;\n\toverflow-y: auto;\n\tpadding: 12px;\n\tfont-size: 0.9em;\n\tline-height: 1.4;\n\twhite-space: pre-wrap;\n}\n</style>\n"],"names":["_createElementBlock","_normalizeClass","_createBlock","_createElementVNode","_createVNode","_toDisplayString"],"mappings":";;;AAwBQ,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,uBAAuB,GAAA;;;;;;UApB7B,MAAI,CAAA,KAAA,IAAK,MAAG,CAAA,IAAA,IAAK,MAAI,CAAA,IAAA;oBAD5BA,kBAyBM,CAAA,MAAA,EAAA;;AAvBL,QAAA,KAAK,EAACC,cAAA,CAAA,CAAA,wCAAuC,EACP,EAAA,wBAAA,EAAA,CAAA,CAAA,MAAA,CAAA,KAAK,qCAAqC,MAAI,CAAA,KAAA,EAAA,CAAA,CAAA;;wCACjF,MAAI,CAAA,KAAA,IAAK,MAAG,CAAA,IAAA,CAAA,GAAG,GAClB,EAAA,CAAA,YAAA;AACQ,QAAA,CAAA,CAAA,MAAA,CAAA,SAAS,MAAI,CAAA,IAAA;0BADrBC,WAmBW,CAAA,oBAAA,EAAA;;AAjBF,cAAA,KAAK,EAAE,KAAI,CAAA,IAAA;sEAAJ,KAAI,CAAA,IAAA,IAAA,MAAA,CAAA,CAAA;AAClB,cAAA,QAAQ,EAAE,EAAE;AACb,cAAA,SAAS,EAAC,QAAO;AACjB,cAAA,gBAAgB,EAAC,yBAAyB;;AAC/B,cAAA,OAAO,UACjB,MAOQ;gBAPRC,kBAOQ,CAAA,QAAA,EAAA;AANP,kBAAA,IAAI,EAAC,QAAO;AACZ,kBAAA,KAAK,EAAC,0BAAyB;AAC9B,kBAAA,YAAU,EAAE,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,2BAAA,CAAA;AACb,kBAAA,eAAa,EAAE,KAAI,CAAA,IAAA;kBACnB,OAAK,EAAA,MAAA,CAAA,CAAA,CAAA,KAAA,MAAA,CAAA,CAAA,CAAA,GAAA,MAAA,KAAE,cAAQ,KAAI,CAAA,IAAA,CAAA,CAAA;;kBACpBC,WAAgC,CAAA,6BAAA,EAAA,EAAX,IAAI,EAAE,EAAE,EAAA,CAAA;;;+BAG/B,MAEK;gBAFLD,kBAEK,CAAA,KAAA,EAFL,UAEK,EAAAE,eAAA,CADD;;;;;;;;;;;"}
|