@ds-mo/ui 16.1.3 → 16.3.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/.package-ready.json +1 -1
- package/dist/agent-patterns.json +1 -1
- package/dist/agent.json +574 -3
- package/dist/angular/ds-disclosure.d.ts +18 -0
- package/dist/angular/ds-disclosure.js +42 -0
- package/dist/angular/ds-drawer.d.ts +40 -0
- package/dist/angular/ds-drawer.js +54 -0
- package/dist/angular/ds-popover.d.ts +24 -0
- package/dist/angular/ds-popover.js +45 -0
- package/dist/angular/index.d.ts +1 -1
- package/dist/angular/index.js +3 -0
- package/dist/angular/proxies.d.ts +3 -0
- package/dist/angular/proxies.js +3 -0
- package/dist/components/ds-agent-activity.js +1 -1
- package/dist/components/ds-agent-questionnaire.js +1 -1
- package/dist/components/ds-agent-response.js +1 -1
- package/dist/components/ds-agent-source-list.js +1 -1
- package/dist/components/ds-agent-tool-call.js +1 -1
- package/dist/components/ds-attachment-list.js +1 -1
- package/dist/components/ds-badge.js +1 -1
- package/dist/components/ds-banner.js +1 -1
- package/dist/components/ds-bar-action.js +1 -1
- package/dist/components/ds-bar-nav.js +1 -1
- package/dist/components/ds-bar-page-title.js +1 -1
- package/dist/components/ds-bar-title.js +1 -1
- package/dist/components/ds-bar-workflow.js +1 -1
- package/dist/components/ds-breadcrumb.js +1 -1
- package/dist/components/ds-button-filled.js +1 -1
- package/dist/components/ds-button-inverted.js +1 -1
- package/dist/components/ds-button-unfilled.js +1 -1
- package/dist/components/ds-calendar.js +1 -1
- package/dist/components/ds-card-action-center.js +1 -1
- package/dist/components/ds-card-chart.js +1 -1
- package/dist/components/ds-card-navigation.js +1 -1
- package/dist/components/ds-card-overview.js +1 -1
- package/dist/components/ds-card-setting.js +1 -1
- package/dist/components/ds-card-settings-scope.js +1 -1
- package/dist/components/ds-chart-legend.js +1 -1
- package/dist/components/ds-chart.js +1 -1
- package/dist/components/ds-checkbox-group.js +1 -1
- package/dist/components/ds-checkbox.js +1 -1
- package/dist/components/ds-chip.js +1 -1
- package/dist/components/ds-code-block.js +1 -1
- package/dist/components/ds-conversation-list-item.js +1 -1
- package/dist/components/ds-conversation-list-section.js +1 -1
- package/dist/components/ds-conversation-list.js +1 -1
- package/dist/components/ds-data-customize.js +1 -1
- package/dist/components/ds-data-filter.js +1 -1
- package/dist/components/ds-data-group.js +1 -1
- package/dist/components/ds-data-preferences.js +1 -1
- package/dist/components/ds-data-preferences.js.map +1 -1
- package/dist/components/ds-data-saved-views.js +1 -1
- package/dist/components/ds-data-search.js +1 -1
- package/dist/components/ds-data-search.js.map +1 -1
- package/dist/components/ds-data-sort.js +1 -1
- package/dist/components/ds-data-toolbar.js +1 -1
- package/dist/components/ds-disclosure.d.ts +11 -0
- package/dist/components/ds-disclosure.js +2 -0
- package/dist/components/ds-disclosure.js.map +1 -0
- package/dist/components/ds-divider.js +1 -1
- package/dist/components/ds-drawer.d.ts +11 -0
- package/dist/components/ds-drawer.js +2 -0
- package/dist/components/ds-drawer.js.map +1 -0
- package/dist/components/ds-empty-state.js +1 -1
- package/dist/components/ds-field.js +1 -1
- package/dist/components/ds-filter-menu.js +1 -1
- package/dist/components/ds-inline-banner-settings.js +1 -1
- package/dist/components/ds-input-date.js +1 -1
- package/dist/components/ds-input-time.js +1 -1
- package/dist/components/ds-input.js +1 -1
- package/dist/components/ds-map-cluster-marker.js +1 -1
- package/dist/components/ds-map-entity-marker.js +1 -1
- package/dist/components/ds-map-marker.js +1 -1
- package/dist/components/ds-markdown.js +1 -1
- package/dist/components/ds-menu.js +1 -1
- package/dist/components/ds-message-actions.js +1 -1
- package/dist/components/ds-message-bubble.js +1 -1
- package/dist/components/ds-message-composer.js +1 -1
- package/dist/components/ds-message-scroller.js +1 -1
- package/dist/components/ds-message.js +1 -1
- package/dist/components/ds-mobile-bar-nav.js +1 -1
- package/dist/components/ds-mobile-header.js +1 -1
- package/dist/components/ds-mobile-section-switcher.js +1 -1
- package/dist/components/ds-mobile-sheet-nav.js +1 -1
- package/dist/components/ds-modal.js +1 -1
- package/dist/components/ds-navigation-tab-group.js +1 -1
- package/dist/components/ds-pagination.js +1 -1
- package/dist/components/ds-panel-nav.js +1 -1
- package/dist/components/ds-panel-sub-nav.js +1 -1
- package/dist/components/ds-panel-tool-header.js +1 -1
- package/dist/components/ds-panel-tool-search.js +1 -1
- package/dist/components/ds-panel-tools.js +1 -1
- package/dist/components/ds-paper-texture.js +1 -1
- package/dist/components/ds-popover.d.ts +11 -0
- package/dist/components/ds-popover.js +2 -0
- package/dist/components/ds-popover.js.map +1 -0
- package/dist/components/ds-radio-tile.js +1 -1
- package/dist/components/ds-radio.js +1 -1
- package/dist/components/ds-score.js +1 -1
- package/dist/components/ds-scroll-overlay.js +1 -1
- package/dist/components/ds-select-toggle.js +1 -1
- package/dist/components/ds-select.js +1 -1
- package/dist/components/ds-setting-row-checkbox.js +1 -1
- package/dist/components/ds-setting-row-radio.js +1 -1
- package/dist/components/ds-setting-row-radio.js.map +1 -1
- package/dist/components/ds-setting-row-toggle.js +1 -1
- package/dist/components/ds-shell-app.js +1 -1
- package/dist/components/ds-shell-app.js.map +1 -1
- package/dist/components/ds-shell-page.js +1 -1
- package/dist/components/ds-shell-tools.js +1 -1
- package/dist/components/ds-skeleton.js +1 -1
- package/dist/components/ds-slider.js +1 -1
- package/dist/components/ds-swatch-picker.js +1 -1
- package/dist/components/ds-switch.js +1 -1
- package/dist/components/ds-tab-group.js +1 -1
- package/dist/components/ds-table.js +1 -1
- package/dist/components/ds-tag.js +1 -1
- package/dist/components/ds-text.js +1 -1
- package/dist/components/ds-textarea.js +1 -1
- package/dist/components/ds-time-picker.js +1 -1
- package/dist/components/ds-toast.js +1 -1
- package/dist/components/ds-toast.js.map +1 -1
- package/dist/components/ds-tooltip-chart.js +1 -1
- package/dist/components/ds-tooltip.js +1 -1
- package/dist/components/ds-typing-indicator.js +1 -1
- package/dist/components/{p-D-Lo5uuI.js → p-3_LHRdqq.js} +2 -2
- package/dist/components/{p-Cc8p57pS.js → p-9EjkmESv.js} +2 -2
- package/dist/components/{p-CQnjpuMX.js → p-9PpepIfc.js} +2 -2
- package/dist/components/{p-C860ilDn.js → p-A47UHyiC.js} +2 -2
- package/dist/components/p-BCzvtlR2.js +2 -0
- package/dist/components/{p-D3FrQk7o.js.map → p-BCzvtlR2.js.map} +1 -1
- package/dist/components/{p-ClOvX8cD.js → p-BEj9uENH.js} +2 -2
- package/dist/components/{p-CtomYv1z.js → p-BJmAAyRk.js} +2 -2
- package/dist/components/{p-Bt1Ybvz-.js → p-BQ6p3LFP.js} +2 -2
- package/dist/components/{p-DiNEjFpy.js → p-BbndBzzZ.js} +2 -2
- package/dist/components/{p-hFe35P2l.js → p-Bg1UmSZn.js} +2 -2
- package/dist/components/{p-ClhfXMaD.js → p-Bn-cskPo.js} +2 -2
- package/dist/components/p-Bz9dI7nI.js +2 -0
- package/dist/components/{p-Dc_zSOKq.js.map → p-Bz9dI7nI.js.map} +1 -1
- package/dist/components/{p-D538svhW.js → p-C0-sabnr.js} +2 -2
- package/dist/components/{p-DQ-wfTOw.js → p-C11CnHDl.js} +2 -2
- package/dist/components/{p-xszHlNkG.js → p-C68nt4dl.js} +2 -2
- package/dist/components/p-C8UThLHd.js +2 -0
- package/dist/components/{p-CsGkdf4W.js.map → p-C8UThLHd.js.map} +1 -1
- package/dist/components/p-C8WKo0sf.js +2 -0
- package/dist/components/{p-D8iNLSCa.js.map → p-C8WKo0sf.js.map} +1 -1
- package/dist/components/p-CAYqnjyC.js +2 -0
- package/dist/components/{p-oBBzQpg_.js.map → p-CAYqnjyC.js.map} +1 -1
- package/dist/components/{p-G6r6yYg8.js → p-CBezgJ3r.js} +2 -2
- package/dist/components/p-CF6Px2b2.js +2 -0
- package/dist/components/{p-omfGVBEt.js.map → p-CF6Px2b2.js.map} +1 -1
- package/dist/components/{p-CMZAEySh.js → p-CVSE2Ool.js} +2 -2
- package/dist/components/{p-KTaiJztz.js → p-CVuSm1Sh.js} +2 -2
- package/dist/components/{p-BNvZM4jP.js → p-CZVoCdYE.js} +2 -2
- package/dist/components/{p-BYo1-6b5.js → p-CeACJYcU.js} +2 -2
- package/dist/components/{p-lnCWfvuA.js → p-Cjw63BmX.js} +2 -2
- package/dist/components/{p-N-2CZze7.js → p-CkUF45d_.js} +2 -2
- package/dist/components/{p-CbyZ7BDJ.js → p-ClRd39mR.js} +2 -2
- package/dist/components/{p-Nx52go85.js → p-Cn6Ud58k.js} +2 -2
- package/dist/components/{p-BFgJoeVV.js → p-D3r_5Fw-.js} +2 -2
- package/dist/components/p-DCqDQWVK.js +2 -0
- package/dist/components/{p-BuvVFyAA.js.map → p-DCqDQWVK.js.map} +1 -1
- package/dist/components/{p-CXrOGz7X.js → p-DIXpyBWB.js} +2 -2
- package/dist/components/{p-1lWK_LMY.js → p-DJtohrKV.js} +2 -2
- package/dist/components/{p-D5-hqLPK.js → p-DKL7n7NM.js} +2 -2
- package/dist/components/{p-DYI6UlOP.js → p-D_CfgH1L.js} +2 -2
- package/dist/components/{p-P2-eHQJU.js → p-D_N38YjY.js} +2 -2
- package/dist/components/p-Dalq5voO.js +2 -0
- package/dist/components/{p-ClgJZFkb.js.map → p-Dalq5voO.js.map} +1 -1
- package/dist/components/{p-DMPn_dt7.js → p-DcwgXLe-.js} +2 -2
- package/dist/components/{p-BckLHaey.js → p-DpA8UeSy.js} +2 -2
- package/dist/components/{p-hom6svhE.js → p-DtNCj4p0.js} +2 -2
- package/dist/components/{p-pkJ3X3s7.js → p-Dwga8azB.js} +2 -2
- package/dist/components/{p-CGtq7TnC.js → p-LFHrICnV.js} +2 -2
- package/dist/components/p-LQGcTdiv.js +2 -0
- package/dist/components/{p-BI-XuQKW.js.map → p-LQGcTdiv.js.map} +1 -1
- package/dist/components/{p-BoWpIl_m.js → p-N8tEOYt8.js} +2 -2
- package/dist/components/{p-CbJx9swS.js → p-QU-CyuBt.js} +2 -2
- package/dist/components/p-QYI9nW7a.js +2 -0
- package/dist/components/p-QYI9nW7a.js.map +1 -0
- package/dist/components/{p-DcvPY30Q.js → p-X-GPE9bP.js} +2 -2
- package/dist/components/{p-CnsuVR2e.js → p-cOkFnnZV.js} +2 -2
- package/dist/components/{p-BctjNfzt.js → p-c_qWiZ6V.js} +2 -2
- package/dist/components/{p-M-Sxsv7V.js → p-dVdTdZdu.js} +2 -2
- package/dist/components/{p-CE_S4oQ_.js → p-hHtKJMVu.js} +2 -2
- package/dist/components/{p-6r60E0i4.js → p-snYhfM1q.js} +2 -2
- package/dist/components/p-xHv_RGJh.js +2 -0
- package/dist/components/p-xHv_RGJh.js.map +1 -0
- package/dist/components/{p-cvVUXaen.js → p-zGd26CTX.js} +2 -2
- package/dist/docs/components.json +1291 -1
- package/dist/lint/index.js +1 -1
- package/dist/mcp-data/registry/disclosure.json +282 -0
- package/dist/mcp-data/registry/drawer.json +567 -0
- package/dist/mcp-data/registry/popover.json +385 -0
- package/dist/mcp-data/registry/registry.json +582 -2
- package/dist/mcp-data/registry/shell-app.json +6 -4
- package/dist/react/components.d.ts +3 -0
- package/dist/react/components.js +3 -0
- package/dist/react/ds-disclosure.d.ts +12 -0
- package/dist/react/ds-disclosure.js +12 -0
- package/dist/react/ds-drawer.d.ts +16 -0
- package/dist/react/ds-drawer.js +18 -0
- package/dist/react/ds-popover.d.ts +13 -0
- package/dist/react/ds-popover.js +15 -0
- package/dist/types/components/Disclosure/Disclosure.d.ts +22 -0
- package/dist/types/components/Drawer/Drawer.d.ts +123 -0
- package/dist/types/components/Popover/Popover.d.ts +64 -0
- package/dist/types/components.d.ts +423 -0
- package/dist/vue/components.d.ts +3 -0
- package/dist/vue/components.js +3 -0
- package/dist/vue/ds-disclosure.d.ts +3 -0
- package/dist/vue/ds-disclosure.js +15 -0
- package/dist/vue/ds-drawer.d.ts +3 -0
- package/dist/vue/ds-drawer.js +34 -0
- package/dist/vue/ds-popover.d.ts +3 -0
- package/dist/vue/ds-popover.js +21 -0
- package/package.json +1 -1
- package/dist/components/p-BI-XuQKW.js +0 -2
- package/dist/components/p-BuvVFyAA.js +0 -2
- package/dist/components/p-ClgJZFkb.js +0 -2
- package/dist/components/p-CsGkdf4W.js +0 -2
- package/dist/components/p-D3FrQk7o.js +0 -2
- package/dist/components/p-D8iNLSCa.js +0 -2
- package/dist/components/p-DTlNtCX1.js +0 -2
- package/dist/components/p-DTlNtCX1.js.map +0 -1
- package/dist/components/p-Dc_zSOKq.js +0 -2
- package/dist/components/p-oBBzQpg_.js +0 -2
- package/dist/components/p-omfGVBEt.js +0 -2
- /package/dist/components/{p-D-Lo5uuI.js.map → p-3_LHRdqq.js.map} +0 -0
- /package/dist/components/{p-Cc8p57pS.js.map → p-9EjkmESv.js.map} +0 -0
- /package/dist/components/{p-CQnjpuMX.js.map → p-9PpepIfc.js.map} +0 -0
- /package/dist/components/{p-C860ilDn.js.map → p-A47UHyiC.js.map} +0 -0
- /package/dist/components/{p-ClOvX8cD.js.map → p-BEj9uENH.js.map} +0 -0
- /package/dist/components/{p-CtomYv1z.js.map → p-BJmAAyRk.js.map} +0 -0
- /package/dist/components/{p-Bt1Ybvz-.js.map → p-BQ6p3LFP.js.map} +0 -0
- /package/dist/components/{p-DiNEjFpy.js.map → p-BbndBzzZ.js.map} +0 -0
- /package/dist/components/{p-hFe35P2l.js.map → p-Bg1UmSZn.js.map} +0 -0
- /package/dist/components/{p-ClhfXMaD.js.map → p-Bn-cskPo.js.map} +0 -0
- /package/dist/components/{p-D538svhW.js.map → p-C0-sabnr.js.map} +0 -0
- /package/dist/components/{p-DQ-wfTOw.js.map → p-C11CnHDl.js.map} +0 -0
- /package/dist/components/{p-xszHlNkG.js.map → p-C68nt4dl.js.map} +0 -0
- /package/dist/components/{p-G6r6yYg8.js.map → p-CBezgJ3r.js.map} +0 -0
- /package/dist/components/{p-CMZAEySh.js.map → p-CVSE2Ool.js.map} +0 -0
- /package/dist/components/{p-KTaiJztz.js.map → p-CVuSm1Sh.js.map} +0 -0
- /package/dist/components/{p-BNvZM4jP.js.map → p-CZVoCdYE.js.map} +0 -0
- /package/dist/components/{p-BYo1-6b5.js.map → p-CeACJYcU.js.map} +0 -0
- /package/dist/components/{p-lnCWfvuA.js.map → p-Cjw63BmX.js.map} +0 -0
- /package/dist/components/{p-N-2CZze7.js.map → p-CkUF45d_.js.map} +0 -0
- /package/dist/components/{p-CbyZ7BDJ.js.map → p-ClRd39mR.js.map} +0 -0
- /package/dist/components/{p-Nx52go85.js.map → p-Cn6Ud58k.js.map} +0 -0
- /package/dist/components/{p-BFgJoeVV.js.map → p-D3r_5Fw-.js.map} +0 -0
- /package/dist/components/{p-CXrOGz7X.js.map → p-DIXpyBWB.js.map} +0 -0
- /package/dist/components/{p-1lWK_LMY.js.map → p-DJtohrKV.js.map} +0 -0
- /package/dist/components/{p-D5-hqLPK.js.map → p-DKL7n7NM.js.map} +0 -0
- /package/dist/components/{p-DYI6UlOP.js.map → p-D_CfgH1L.js.map} +0 -0
- /package/dist/components/{p-P2-eHQJU.js.map → p-D_N38YjY.js.map} +0 -0
- /package/dist/components/{p-DMPn_dt7.js.map → p-DcwgXLe-.js.map} +0 -0
- /package/dist/components/{p-BckLHaey.js.map → p-DpA8UeSy.js.map} +0 -0
- /package/dist/components/{p-hom6svhE.js.map → p-DtNCj4p0.js.map} +0 -0
- /package/dist/components/{p-pkJ3X3s7.js.map → p-Dwga8azB.js.map} +0 -0
- /package/dist/components/{p-CGtq7TnC.js.map → p-LFHrICnV.js.map} +0 -0
- /package/dist/components/{p-BoWpIl_m.js.map → p-N8tEOYt8.js.map} +0 -0
- /package/dist/components/{p-CbJx9swS.js.map → p-QU-CyuBt.js.map} +0 -0
- /package/dist/components/{p-DcvPY30Q.js.map → p-X-GPE9bP.js.map} +0 -0
- /package/dist/components/{p-CnsuVR2e.js.map → p-cOkFnnZV.js.map} +0 -0
- /package/dist/components/{p-BctjNfzt.js.map → p-c_qWiZ6V.js.map} +0 -0
- /package/dist/components/{p-M-Sxsv7V.js.map → p-dVdTdZdu.js.map} +0 -0
- /package/dist/components/{p-CE_S4oQ_.js.map → p-hHtKJMVu.js.map} +0 -0
- /package/dist/components/{p-6r60E0i4.js.map → p-snYhfM1q.js.map} +0 -0
- /package/dist/components/{p-cvVUXaen.js.map → p-zGd26CTX.js.map} +0 -0
|
@@ -37,7 +37,8 @@
|
|
|
37
37
|
"component:ds-shell-page",
|
|
38
38
|
"component:ds-banner",
|
|
39
39
|
"component:ds-swatch-picker",
|
|
40
|
-
"component:ds-paper-texture"
|
|
40
|
+
"component:ds-paper-texture",
|
|
41
|
+
"component:ds-drawer"
|
|
41
42
|
],
|
|
42
43
|
"patterns": [
|
|
43
44
|
"pattern:application-shell",
|
|
@@ -81,7 +82,8 @@
|
|
|
81
82
|
"Route-owned pageChrome independently selects ShellPage contentInset, page-header seams, scroll compaction, expanded and compact header-divider visibility, and contentSurface. ShellApp forwards them while resolving whether desktop title geometry is page-local or shell-bar-owned.",
|
|
82
83
|
"The application filters unauthorized destinations, sections, and tools before passing them into shell components.",
|
|
83
84
|
"Application-owned header actions and the account trigger retain surface ownership across shell presentations. Supply surfaceOpen on header actions and accountMenuSurfaceOpen on navigation for rendered surface lifetimes; keep disclosure and selected-route state separate.",
|
|
84
|
-
"Managed page chrome can explicitly choose page-local title presentation for pages that must remain compact rather than following capacity and scrolling."
|
|
85
|
+
"Managed page chrome can explicitly choose page-local title presentation for pages that must remain compact rather than following capacity and scrolling.",
|
|
86
|
+
"The page content lane is a non-scrolling frame around the page scroller that contains fixed descendants, so page-owned non-modal surfaces such as a details ds-drawer cover only the content lane, stay put while the page scrolls, and hide with the content in fullscreen tools and blocked mobile stages."
|
|
85
87
|
],
|
|
86
88
|
"responsiveBehavior": [
|
|
87
89
|
"ShellApp owns the fixed system breakpoints and resolves desktop at 1200px and wider, tablet from 768px through 1199px, and mobile below 768px.",
|
|
@@ -780,12 +782,12 @@
|
|
|
780
782
|
"files": [
|
|
781
783
|
{
|
|
782
784
|
"path": "src/wc/components/ShellApp/ShellApp.css",
|
|
783
|
-
"content": ":host {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n box-sizing: border-box;\n overflow: hidden;\n overscroll-behavior: none;\n font-family: var(--typography-font-family-ui, system-ui);\n --ds-shell-panel-tools-width: var(--dimension-panel-width-xs);\n --ds-shell-gradient-context: flat;\n}\n\n:host {\n /* Shared solid Chrome base; gradient and paper layers composite above it. */\n --_shell-chrome-bg: var(--color-chrome-background-theme);\n}\n\n:host(.shell-app--gradient) {\n --ds-shell-gradient-context: gradient;\n}\n\n.shell-app__row {\n display: flex;\n flex: 1 1 0;\n min-height: 0;\n position: relative;\n}\n\n.shell-app__banner {\n --_shell-banner-edge-display: none;\n flex: 0 0 auto;\n min-width: 0;\n position: relative;\n z-index: 5;\n}\n\n.shell-app__banner ::slotted(ds-banner) {\n display: grid;\n width: 100%;\n}\n\n/*\n * One chrome layer behind the shell row. Wash uses background-attachment: fixed\n * so the pattern is locked to the viewport — panel/tools collapse only changes the\n * transparent window, not the pattern phase. JS sets wash background-size from\n * readShellViewportDimensions(), not the shell element box.\n */\n.shell-app__chrome {\n position: absolute;\n inset: 0;\n pointer-events: none;\n z-index: 0;\n overflow: hidden;\n background-color: var(--_shell-chrome-bg);\n}\n\n:host(.shell-app--gradient) .shell-app__chrome::before {\n content: '';\n position: absolute;\n inset: 0;\n pointer-events: none;\n background-position: 0 0;\n background-attachment: fixed;\n z-index: 1;\n opacity: var(--ds-shell-gradient-opacity, 1);\n background-image: var(--ds-shell-gradient-image, none);\n background-size: var(--ds-shell-gradient-size, auto);\n background-repeat: no-repeat;\n}\n\n.shell-app__paper-texture {\n z-index: 2;\n}\n\n.shell-app__panel {\n flex: 0 0 auto;\n min-height: 0;\n height: 100%;\n position: relative;\n /* Above main so resize handle line can paint over the workspace edge */\n z-index: 2;\n}\n\n.shell-app__panel ::slotted(ds-panel-nav) {\n display: block;\n height: 100%;\n}\n\n.shell-app__panel > ds-panel-nav {\n display: block;\n height: 100%;\n}\n\n.shell-app__main {\n flex: 1 1 auto;\n min-width: 0;\n min-height: 0;\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n grid-template-rows: auto minmax(0, 1fr);\n grid-template-areas:\n 'bar tools'\n 'content tools';\n position: relative;\n z-index: 1;\n}\n\n.shell-app__mobile-sheet-nav,\n.shell-app__mobile-bar-nav {\n display: none;\n}\n\n.shell-app__bar {\n grid-area: bar;\n flex: 0 0 auto;\n min-width: 0;\n}\n\n.shell-app__bar ::slotted(ds-bar-nav) {\n display: block;\n}\n\n.shell-app__bar ::slotted(ds-bar-title),\n.shell-app__bar ::slotted(ds-bar-page-title) {\n display: block;\n}\n\n.shell-app__bar > ds-bar-nav {\n display: block;\n}\n\n.shell-app__bar > ds-bar-title,\n.shell-app__bar > ds-bar-page-title {\n display: block;\n}\n\n.shell-app__content {\n grid-area: content;\n /* Full-height pages use this lane, including banner and mobile navigation reflow. */\n container-type: size;\n container-name: ds-shell-content;\n flex: 1 1 auto;\n min-width: 0;\n min-height: 0;\n
|
|
785
|
+
"content": ":host {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n box-sizing: border-box;\n overflow: hidden;\n overscroll-behavior: none;\n font-family: var(--typography-font-family-ui, system-ui);\n --ds-shell-panel-tools-width: var(--dimension-panel-width-xs);\n --ds-shell-gradient-context: flat;\n}\n\n:host {\n /* Shared solid Chrome base; gradient and paper layers composite above it. */\n --_shell-chrome-bg: var(--color-chrome-background-theme);\n}\n\n:host(.shell-app--gradient) {\n --ds-shell-gradient-context: gradient;\n}\n\n.shell-app__row {\n display: flex;\n flex: 1 1 0;\n min-height: 0;\n position: relative;\n}\n\n.shell-app__banner {\n --_shell-banner-edge-display: none;\n flex: 0 0 auto;\n min-width: 0;\n position: relative;\n z-index: 5;\n}\n\n.shell-app__banner ::slotted(ds-banner) {\n display: grid;\n width: 100%;\n}\n\n/*\n * One chrome layer behind the shell row. Wash uses background-attachment: fixed\n * so the pattern is locked to the viewport — panel/tools collapse only changes the\n * transparent window, not the pattern phase. JS sets wash background-size from\n * readShellViewportDimensions(), not the shell element box.\n */\n.shell-app__chrome {\n position: absolute;\n inset: 0;\n pointer-events: none;\n z-index: 0;\n overflow: hidden;\n background-color: var(--_shell-chrome-bg);\n}\n\n:host(.shell-app--gradient) .shell-app__chrome::before {\n content: '';\n position: absolute;\n inset: 0;\n pointer-events: none;\n background-position: 0 0;\n background-attachment: fixed;\n z-index: 1;\n opacity: var(--ds-shell-gradient-opacity, 1);\n background-image: var(--ds-shell-gradient-image, none);\n background-size: var(--ds-shell-gradient-size, auto);\n background-repeat: no-repeat;\n}\n\n.shell-app__paper-texture {\n z-index: 2;\n}\n\n.shell-app__panel {\n flex: 0 0 auto;\n min-height: 0;\n height: 100%;\n position: relative;\n /* Above main so resize handle line can paint over the workspace edge */\n z-index: 2;\n}\n\n.shell-app__panel ::slotted(ds-panel-nav) {\n display: block;\n height: 100%;\n}\n\n.shell-app__panel > ds-panel-nav {\n display: block;\n height: 100%;\n}\n\n.shell-app__main {\n flex: 1 1 auto;\n min-width: 0;\n min-height: 0;\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n grid-template-rows: auto minmax(0, 1fr);\n grid-template-areas:\n 'bar tools'\n 'content tools';\n position: relative;\n z-index: 1;\n}\n\n.shell-app__mobile-sheet-nav,\n.shell-app__mobile-bar-nav {\n display: none;\n}\n\n.shell-app__bar {\n grid-area: bar;\n flex: 0 0 auto;\n min-width: 0;\n}\n\n.shell-app__bar ::slotted(ds-bar-nav) {\n display: block;\n}\n\n.shell-app__bar ::slotted(ds-bar-title),\n.shell-app__bar ::slotted(ds-bar-page-title) {\n display: block;\n}\n\n.shell-app__bar > ds-bar-nav {\n display: block;\n}\n\n.shell-app__bar > ds-bar-title,\n.shell-app__bar > ds-bar-page-title {\n display: block;\n}\n\n/*\n * Non-scrolling frame for the page column. It owns the size container and\n * layout containment, so page-owned fixed surfaces such as a non-modal\n * ds-drawer cover exactly this lane and stay put while the page scrolls.\n */\n.shell-app__content-frame {\n grid-area: content;\n /* Full-height pages use this lane, including banner and mobile navigation reflow. */\n container-type: size;\n container-name: ds-shell-content;\n flex: 1 1 auto;\n min-width: 0;\n min-height: 0;\n position: relative;\n z-index: 1;\n overflow: clip;\n /* Limit reflow to the page column — not on __main (breaks tools flex shrink). */\n contain: layout;\n}\n\n.shell-app__content {\n block-size: 100%;\n min-width: 0;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: none;\n}\n\n/* Layout scroller, not a control: keep it keyboard-focusable without a page-sized ring. */\n.shell-app__content:focus,\n.shell-app__content:focus-visible {\n outline: none;\n}\n\n/* Opaque page surface — masks the fixed chrome layer in the content column. */\n.shell-app__content {\n position: relative;\n z-index: 1;\n background-color: var(--color-background-primary);\n}\n\n.shell-app__tools {\n grid-area: tools;\n flex: 0 0 auto;\n min-width: 0;\n min-height: 0;\n height: 100%;\n display: flex;\n position: relative;\n z-index: 0;\n overflow: hidden;\n /* Chrome surface context inherited by PanelTools and its slotted products. */\n --_shell-tools-border: var(--color-chrome-border-tertiary);\n --_shell-tools-border-secondary: var(--color-chrome-border-secondary);\n --_shell-tools-divider: var(--color-chrome-divider);\n --_shell-tools-background-theme: var(--color-chrome-background-theme);\n --_shell-tools-fg-primary: var(--color-chrome-foreground-primary);\n --_shell-tools-fg-secondary: var(--color-chrome-foreground-secondary);\n}\n\n.shell-app__tools ::slotted(ds-panel-tools) {\n display: block;\n height: 100%;\n min-height: 0;\n}\n\n.shell-app__tools ::slotted(ds-shell-tools) {\n display: block;\n height: 100%;\n min-height: 0;\n}\n\n.shell-app__tools > ds-shell-tools {\n display: block;\n height: 100%;\n min-height: 0;\n}\n\n.shell-app__tool-slot-proxy {\n display: block;\n width: 100%;\n height: 100%;\n min-width: 0;\n min-height: 0;\n}\n\n.shell-app__content > ds-shell-page {\n --ds-shell-content-block-size: 100cqh;\n display: block;\n min-height: 100%;\n}\n\n:host(.shell-app--tools-fullscreen) .shell-app__row {\n /* Anchor fixed fullscreen tools to the available stage below the banner. */\n contain: layout;\n}\n\n:host(.shell-app--tools-fullscreen) .shell-app__main {\n /* Above panel chrome while remaining below the global banner layer. */\n z-index: 3;\n}\n\n:host(.shell-app--tools-fullscreen) .shell-app__tools {\n /* Fullscreen tools must outrank retained page-owned overlays in content. */\n z-index: 2;\n}\n\n:host(.shell-app--tools-fullscreen) .shell-app__panel,\n:host(.shell-app--tools-fullscreen) .shell-app__bar,\n:host(.shell-app--tools-fullscreen) .shell-app__content-frame {\n visibility: hidden;\n /* Descendants can override visibility, but must not paint through tools. */\n content-visibility: hidden;\n}\n\n:host(.shell-app--mobile) {\n --_shell-chrome-bg: var(--color-background-primary);\n padding-block-start: env(safe-area-inset-top);\n background: var(--ds-shell-safe-area-background, var(--color-background-primary));\n}\n\n:host(.shell-app--mobile) .shell-app__banner {\n --_shell-banner-edge-display: block;\n}\n\n/* Refresh the Mobile WebKit stage layer without remounting app content. */\n:host(.shell-app--mobile.shell-app--foreground-refresh) .shell-app__main {\n transform: translateZ(0);\n}\n\n:host(.shell-app--mobile) .shell-app__chrome,\n:host(.shell-app--mobile) .shell-app__panel,\n:host(.shell-app--mobile) .shell-app__bar {\n display: none;\n}\n\n:host(.shell-app--mobile) .shell-app__main {\n width: 100%;\n box-sizing: border-box;\n grid-template-columns: minmax(0, 1fr);\n grid-template-rows: minmax(0, 1fr) auto;\n grid-template-areas:\n 'stage'\n 'mobile-bar-nav';\n background: var(--color-background-primary);\n}\n\n:host(.shell-app--mobile) .shell-app__content-frame,\n:host(.shell-app--mobile) .shell-app__tools,\n:host(.shell-app--mobile) .shell-app__mobile-sheet-nav {\n grid-area: stage;\n min-width: 0;\n min-height: 0;\n height: 100%;\n position: relative;\n}\n\n:host(.shell-app--mobile) .shell-app__content-frame {\n z-index: 1;\n}\n\n:host(.shell-app--mobile) .shell-app__tools {\n z-index: 2;\n width: 100%;\n overflow: hidden;\n background: var(--color-background-primary);\n}\n\n:host(.shell-app--mobile) .shell-app__mobile-sheet-nav {\n display: block;\n z-index: 3;\n overflow: hidden;\n background: var(--color-background-primary);\n}\n\n:host(.shell-app--mobile) .shell-app__mobile-bar-nav {\n grid-area: mobile-bar-nav;\n display: block;\n min-width: 0;\n position: relative;\n z-index: 4;\n}\n\n:host(.shell-app--mobile) .shell-app__tools[hidden],\n:host(.shell-app--mobile) .shell-app__mobile-sheet-nav[hidden],\n:host(.shell-app--mobile) .shell-app__mobile-bar-nav[hidden] {\n display: none;\n}\n\n:host(.shell-app--mobile-tool-active) .shell-app__content-frame,\n:host(.shell-app--mobile-sheet-nav-open) .shell-app__content-frame,\n:host(.shell-app--mobile-sheet-nav-open) .shell-app__tools {\n visibility: hidden;\n}\n",
|
|
784
786
|
"type": "registry:ui"
|
|
785
787
|
},
|
|
786
788
|
{
|
|
787
789
|
"path": "src/wc/components/ShellApp/ShellApp.tsx",
|
|
788
|
-
"content": "import {\n Component,\n Prop,\n Element,\n Event,\n EventEmitter,\n State,\n Watch,\n Listen,\n Method,\n h,\n Host,\n} from '@stencil/core';\nimport type { NavChromeStyle } from '../../shell/nav-chrome';\nimport { ConnectionTasks } from '../../utils/connection-tasks';\nimport { isEditableShortcutTarget, resolveShellShortcut } from '../../shell/shell-shortcuts';\nimport type {\n PanelToolsHeaderAction,\n PanelToolsHeaderActionDetail,\n PanelToolsRailAccessoryActionDetail,\n PanelToolsToolId,\n} from '../PanelTools/panel-tools-types';\nimport { PANEL_TOOLS_DEFAULT_ITEMS } from '../PanelTools/panel-tools-types';\nimport type { BarTitleActionConfigItem, BarTitleSectionItem } from '../BarTitle/bar-title-types';\nimport type { BarTitlePlacement } from '../BarTitle/bar-title-types';\nimport {\n findBarTitleAction,\n overflowBarTitleActionSections,\n resolveBarTitleActionItems,\n visibleBarTitleActions,\n} from '../BarTitle/bar-title-actions';\nimport type { BarNavTab } from '../BarNav/bar-nav-types';\nimport type { BreadcrumbSelectDetail } from '../Breadcrumb/breadcrumb-types';\nimport type { MenuItemData } from '../Menu/menu-types';\nimport {\n DEFAULT_SHELL_GRADIENT_PRESET,\n normalizeShellGradientPreset,\n type ShellGradientPreset,\n} from '../../shell/shell-gradient-presets';\nimport {\n CHROME_TRANSITION_END,\n CHROME_TRANSITION_START,\n ChromeTransitionDepth,\n createRafCoalescer,\n readChromeTransitionSource,\n} from '../../shell/chrome-transition';\nimport {\n isPanelNavCollapsed,\n panelWidthPxFromTokens,\n readPanelNavWidthTokens,\n type PanelNavWidthTokens,\n} from '../../shell/shell-chrome-metrics';\nimport {\n SHELL_GRADIENT_IMAGE_VAR,\n SHELL_GRADIENT_OPACITY_VAR,\n SHELL_GRADIENT_POSITION_BAR_VAR,\n SHELL_GRADIENT_POSITION_PANEL_VAR,\n SHELL_GRADIENT_SIZE_VAR,\n buildShellRadialGradient,\n readShellViewportDimensions,\n shellGradientPositionBar,\n shellGradientPositionPanel,\n shellGradientPresetOpacity,\n shellGradientSize,\n} from '../../shell/shell-gradient';\nimport {\n itemUsesShellInbox,\n resolveAvailableInboxTool,\n resolveManagedShellPageCapacity,\n resolveShellResponsiveMode,\n SHELL_DESKTOP_BREAKPOINT,\n SHELL_WIDE_DESKTOP_BREAKPOINT,\n shellMobileDestinationForTool,\n type MobileDestination,\n type ShellInboxToolId,\n type ShellResponsiveMode,\n} from '../../shell/shell-responsive';\nimport { deriveActiveIdFromUrl } from '../PanelNav/panel-nav-utils';\nimport type { PanelNavChildSelectDetail } from '../PanelNav/panel-nav-types';\nimport type { PanelNavItem } from '../PanelNav/panel-nav-types';\nimport type { PanelNavUserActionDetail } from '../PanelNav/panel-nav-types';\nimport type { MobileBarNavDestinationDetail } from '../MobileBarNav/mobile-bar-nav-types';\nimport type {\n ShellAppComposition,\n ShellNavigationConfig,\n ShellPageChromeConfig,\n ShellSectionNavigation,\n ShellToolsConfig,\n} from './shell-app-types';\nimport type { PaperTextureConfig } from '../PaperTexture/paper-texture-types';\n\nlet nextShellAppId = 0;\ntype FocusableButton = HTMLElement & { setFocus?: () => Promise<void> };\n\n@Component({\n tag: 'ds-shell-app',\n styleUrl: 'ShellApp.css',\n scoped: true,\n})\nexport class ShellApp {\n private static readonly FOREGROUND_REFRESH_MS = 50;\n\n /** Managed renders the complete responsive chrome; slotted exposes the advanced composition. */\n @Prop({ reflect: true }) composition: ShellAppComposition = 'managed';\n\n /** Router-owned navigation data used by managed composition. */\n @Prop() navigation: ShellNavigationConfig = {};\n\n /** Route sections in bar/header pickers, or nested beneath desktop and mobile menu parents. */\n @Prop({ attribute: 'section-navigation', reflect: true })\n sectionNavigation: ShellSectionNavigation = 'bar';\n\n /** Route-owned page title and section data used by managed composition. */\n @Prop({ attribute: 'page-chrome' }) pageChrome: ShellPageChromeConfig = {};\n\n /** Product-owned global tool definitions used by managed composition. */\n @Prop() tools: ShellToolsConfig = {};\n\n /** Chrome style propagated to slotted `ds-panel-nav` and `ds-bar-nav`. */\n @Prop({ attribute: 'nav-style', reflect: true }) navStyle: NavChromeStyle = 'dashboard';\n\n /**\n * Shell chrome wash preset. `none` renders solid chrome; the remaining\n * presets use token-based washes that adapt to the active color theme.\n */\n @Prop({ attribute: 'gradient-preset', reflect: true }) gradientPreset: ShellGradientPreset =\n DEFAULT_SHELL_GRADIENT_PRESET;\n\n /** Optional decorative Paper Design texture layered above the shell wash. */\n @Prop({ attribute: 'paper-texture' }) paperTexture?: PaperTextureConfig;\n\n /** When `true` (default), registers global shell keyboard shortcuts. `[` toggles panel nav; `]` closes tools; K, A, S, M, N, and / toggle tool drawers. Modifiers are ignored so browser chords like ⌘N stay native. */\n @Prop({ attribute: 'shortcuts-enabled' }) shortcutsEnabled: boolean = true;\n\n /** Controlled mobile surface shown above the persistent bottom bar. */\n @Prop({ attribute: 'mobile-destination' })\n mobileDestination: MobileDestination = 'area';\n\n /** Controlled full-stage Mobile Sheet Nav state. */\n @Prop({ attribute: 'mobile-sheet-nav-open' })\n mobileSheetNavOpen: boolean = false;\n\n /** Emitted after crossing the fixed 768px or 1200px shell boundaries. */\n @Event() dsResponsiveModeChange!: EventEmitter<{ mode: ShellResponsiveMode }>;\n\n /** Managed primary-navigation intent; the application router performs navigation. */\n @Event() dsNavSelect!: EventEmitter<string>;\n\n /** Managed desktop BarNav or mobile section-selection intent. */\n @Event() dsTabChange!: EventEmitter<string>;\n\n /** Managed nested PanelNav child-route intent. */\n @Event() dsNavChildSelect!: EventEmitter<PanelNavChildSelectDetail>;\n\n /** Managed page-subsection intent from BarTitle or MobileHeader. */\n @Event() dsSubsectionChange!: EventEmitter<string>;\n\n /** Managed page-level Back intent. */\n @Event() dsPageBack!: EventEmitter<MouseEvent>;\n\n /** Managed breadcrumb intent from BarTitle. */\n @Event({ cancelable: true })\n dsBreadcrumbSelect!: EventEmitter<BreadcrumbSelectDetail>;\n\n /** Managed page action intent from BarTitle. */\n @Event() dsPageAction!: EventEmitter<string>;\n\n /** Managed tools selection state. */\n @Event() dsToolChange!: EventEmitter<{ id: PanelToolsToolId; selected: boolean }>;\n\n /** Managed tool-header Back intent. */\n @Event() dsHeaderBack!: EventEmitter<{ tool: PanelToolsToolId }>;\n\n /** Managed tool-header action intent. */\n @Event() dsHeaderAction!: EventEmitter<PanelToolsHeaderActionDetail>;\n\n /** Managed desktop/tablet rail accessory intent. */\n @Event() dsRailAccessoryAction!: EventEmitter<PanelToolsRailAccessoryActionDetail>;\n\n /** Managed drawer/fullscreen presentation state. */\n @Event() dsPresentationChange!: EventEmitter<{ presentation: 'drawer' | 'fullscreen' }>;\n\n /** Managed mobile Dashboard/Settings browsing intent. */\n @Event() dsBrowseContextChange!: EventEmitter<NavChromeStyle>;\n\n /** Managed account-footer action intent. */\n @Event() dsNavFooterAction!: EventEmitter<void>;\n\n /** Managed user/account trigger intent. */\n @Event() dsNavUserAction!: EventEmitter<PanelNavUserActionDetail>;\n\n @Element() el!: HTMLElement;\n @State() private toolsFullscreen = false;\n @State() private resolvedMode: ShellResponsiveMode = 'desktop';\n @State() private wideViewport = false;\n @State() private managedToolsOpen = false;\n @State() private managedActiveTool: PanelToolsToolId | '' = '';\n @State() private managedToolPresentation: 'drawer' | 'fullscreen' = 'drawer';\n @State() private managedMobileDestination: MobileDestination = 'area';\n @State() private managedMobilePrimaryDestination: 'area' | 'help' = 'area';\n @State() private managedMobileSheetNavOpen = false;\n @State() private managedInboxTool: ShellInboxToolId | '' = '';\n @State() private managedBrowseContext: NavChromeStyle = 'dashboard';\n @State() private mobileActionMenuOpen = false;\n @State() private mobileActionSurfaceOpen = false;\n @State() private mobileActionMenuInitialFocusVisible = false;\n\n private readonly instanceId = nextShellAppId++;\n private readonly mobileActionMenuTriggerId = `shell-app-mobile-action-trigger-${this.instanceId}`;\n private readonly mobileActionMenuId = `shell-app-mobile-action-menu-${this.instanceId}`;\n private mobileActionTriggerEl: FocusableButton | null = null;\n private resizeObserver: ResizeObserver | null = null;\n private hasLoaded = false;\n private readonly connectionTasks = new ConnectionTasks(() => this.el.isConnected);\n private foregroundRefreshTimer: number | null = null;\n private readonly panelNavTransition = new ChromeTransitionDepth();\n private readonly chromeSyncCoalescer = createRafCoalescer(() => this.syncChrome());\n private panelWidthTokens: PanelNavWidthTokens = { expandedPx: 0, collapsedPx: 0 };\n private cachedViewportWidth = 0;\n private cachedViewportHeight = 0;\n private onWindowResize = () => {\n this.updateResponsiveMode();\n if (this.panelNavTransition.isActive) return;\n this.scheduleChromeSync();\n };\n private onVisualViewportChange = () => {\n if (this.panelNavTransition.isActive) return;\n this.scheduleChromeSync();\n };\n\n private get managed(): boolean {\n return this.composition === 'managed';\n }\n\n private get effectiveMobileDestination(): MobileDestination {\n return this.managed ? this.managedMobileDestination : this.mobileDestination;\n }\n\n private get effectiveMobileSheetNavOpen(): boolean {\n return this.managed ? this.managedMobileSheetNavOpen : this.mobileSheetNavOpen;\n }\n\n private get resolvedNavigationGroups() {\n if (this.navigation.groups?.length) return this.navigation.groups;\n return this.managedBrowseContext === 'settings'\n ? (this.navigation.settingsGroups ?? [])\n : (this.navigation.dashboardGroups ?? []);\n }\n\n private get resolvedToolItems() {\n return this.tools.items ?? PANEL_TOOLS_DEFAULT_ITEMS;\n }\n\n private get resolvedPageTabs(): BarNavTab[] {\n const children = this.currentArea.children;\n if (!children?.length) return this.pageChrome.tabs ?? [];\n return children.map(child => ({\n id: child.id,\n label: child.label,\n dot: child.dot,\n isInactive: child.isInactive,\n }));\n }\n\n private get availableInboxTools(): ShellInboxToolId[] {\n return this.resolvedToolItems\n .filter(item => !item.isInactive && itemUsesShellInbox(item))\n .map(item => item.id as ShellInboxToolId);\n }\n\n componentWillLoad() {\n if (typeof window !== 'undefined') {\n this.resolvedMode = resolveShellResponsiveMode(window.innerWidth);\n this.wideViewport = window.innerWidth >= SHELL_WIDE_DESKTOP_BREAKPOINT;\n }\n this.managedBrowseContext = this.navigation.browseContext ?? this.navStyle;\n this.managedMobileDestination = this.mobileDestination;\n this.managedMobileSheetNavOpen = this.mobileSheetNavOpen;\n this.managedInboxTool = resolveAvailableInboxTool('', this.availableInboxTools);\n }\n\n connectedCallback() {\n if (this.hasLoaded) this.connectResources();\n }\n\n componentDidLoad() {\n this.hasLoaded = true;\n this.connectResources();\n }\n\n private connectResources() {\n if (!this.el.isConnected) return;\n this.updateResponsiveMode();\n this.syncSlottedNavStyle();\n this.syncSlottedMobileState();\n this.connectMetricsObserver();\n this.connectViewportListeners();\n this.connectPageLifecycleListeners();\n this.el.addEventListener(CHROME_TRANSITION_START, this.onChromeTransitionStart);\n this.el.addEventListener(CHROME_TRANSITION_END, this.onChromeTransitionEnd);\n this.connectionTasks.frame(() => {\n this.cachePanelWidthTokens();\n this.scheduleChromeSync();\n });\n const tools = this.el.querySelector('ds-shell-tools, ds-panel-tools');\n this.toolsFullscreen = tools?.getAttribute('presentation') === 'fullscreen';\n }\n\n @Listen('dsPresentationChange')\n handleToolsPresentation(event: CustomEvent<{ presentation?: string }>) {\n this.toolsFullscreen = event.detail?.presentation === 'fullscreen';\n }\n\n disconnectedCallback() {\n this.connectionTasks.cancel();\n this.resizeObserver?.disconnect();\n this.resizeObserver = null;\n this.disconnectViewportListeners();\n this.disconnectPageLifecycleListeners();\n this.cancelForegroundRefresh();\n this.chromeSyncCoalescer.cancel();\n this.panelNavTransition.reset();\n this.el.removeEventListener(CHROME_TRANSITION_START, this.onChromeTransitionStart);\n this.el.removeEventListener(CHROME_TRANSITION_END, this.onChromeTransitionEnd);\n }\n\n @Watch('navStyle')\n @Watch('gradientPreset')\n @Watch('paperTexture')\n @Watch('sectionNavigation')\n onShellPropsChange() {\n this.syncSlottedNavStyle();\n this.scheduleChromeSync();\n }\n\n @Watch('navigation')\n handleNavigationChange(next: ShellNavigationConfig, previous: ShellNavigationConfig) {\n this.managedBrowseContext = this.navigation.browseContext ?? this.navStyle;\n if (next.currentUrl !== previous.currentUrl || next.activeId !== previous.activeId) {\n this.managedMobilePrimaryDestination = 'area';\n }\n }\n\n @Watch('pageChrome')\n handlePageChromeChange() {\n if (this.mobileActionMenuSections.length === 0) {\n this.mobileActionMenuOpen = false;\n this.mobileActionSurfaceOpen = false;\n }\n }\n\n @Watch('tools')\n handleToolsChange() {\n if (!this.resolvedToolItems.some(item => item.id === 'help' && !item.isInactive)) {\n this.managedMobilePrimaryDestination = 'area';\n }\n if (\n this.managedActiveTool &&\n !this.resolvedToolItems.some(item => item.id === this.managedActiveTool)\n ) {\n this.managedToolsOpen = false;\n this.managedActiveTool = '';\n this.managedMobileDestination = 'area';\n }\n this.managedInboxTool = resolveAvailableInboxTool(\n this.managedInboxTool,\n this.availableInboxTools\n );\n }\n\n @Watch('mobileDestination')\n @Watch('mobileSheetNavOpen')\n onMobileStateChange(_next: unknown, previous: unknown) {\n if (this.managed) {\n this.managedMobileDestination = this.mobileDestination;\n this.managedMobileSheetNavOpen = this.mobileSheetNavOpen;\n }\n this.syncSlottedMobileState();\n if (previous === true && !this.effectiveMobileSheetNavOpen && this.resolvedMode === 'mobile') {\n this.connectionTasks.frame(() => {\n const bar = this.el.querySelector('ds-mobile-bar-nav') as\n | (HTMLElement & {\n focusDestination?: (destination: MobileDestination | 'sheet-nav') => Promise<void>;\n })\n | null;\n const activeElement = document.activeElement;\n if (activeElement instanceof HTMLElement && bar?.contains(activeElement)) return;\n void bar?.focusDestination?.('sheet-nav');\n });\n }\n }\n\n private updateResponsiveMode() {\n if (!this.el.isConnected || typeof window === 'undefined') return;\n const next = resolveShellResponsiveMode(window.innerWidth);\n this.wideViewport = window.innerWidth >= SHELL_WIDE_DESKTOP_BREAKPOINT;\n if (next === this.resolvedMode) return;\n this.resolvedMode = next;\n this.syncSlottedMobileState();\n this.scheduleChromeSync();\n if (this.hasLoaded) this.dsResponsiveModeChange.emit({ mode: next });\n }\n\n private onChromeTransitionStart = (event: Event) => {\n const source = readChromeTransitionSource(event);\n if (source !== 'panel-nav') return;\n\n this.panelNavTransition.enter();\n const viewport = readShellViewportDimensions();\n this.cachedViewportWidth = viewport.width;\n this.cachedViewportHeight = viewport.height;\n this.syncChrome();\n };\n\n private onChromeTransitionEnd = (event: Event) => {\n const source = readChromeTransitionSource(event);\n if (source !== 'panel-nav') return;\n\n this.panelNavTransition.exit();\n if (!this.panelNavTransition.isActive) {\n this.scheduleChromeSync();\n }\n };\n\n /** Coalesce ResizeObserver bursts to one layout read per frame. */\n private scheduleChromeSync() {\n if (!this.el.isConnected) return;\n this.chromeSyncCoalescer.schedule();\n }\n\n private syncSlottedNavStyle() {\n const panel = this.el.querySelector('ds-panel-nav') as\n | (HTMLElement & { navStyle: NavChromeStyle })\n | null;\n const bar = this.el.querySelector('ds-bar-nav') as\n | (HTMLElement & { navStyle: NavChromeStyle })\n | null;\n if (panel) {\n panel.setAttribute('nav-style', this.navStyle);\n panel.navStyle = this.navStyle;\n }\n if (bar) {\n bar.setAttribute('nav-style', this.navStyle);\n bar.navStyle = this.navStyle;\n }\n }\n\n private syncSlottedMobileState() {\n const tools = this.el.querySelector('ds-shell-tools') as\n | (HTMLElement & { responsiveMode: ShellResponsiveMode })\n | null;\n const bar = this.el.querySelector('ds-mobile-bar-nav') as\n | (HTMLElement & {\n activeDestination: MobileDestination;\n sheetNavExpanded: boolean;\n })\n | null;\n const sheetNav = this.el.querySelector('ds-mobile-sheet-nav') as\n | (HTMLElement & { open: boolean })\n | null;\n\n if (tools) {\n tools.setAttribute('responsive-mode', this.resolvedMode);\n tools.responsiveMode = this.resolvedMode;\n }\n if (bar) {\n bar.activeDestination = this.effectiveMobileDestination;\n bar.sheetNavExpanded = this.effectiveMobileSheetNavOpen;\n }\n if (sheetNav) {\n sheetNav.open = this.effectiveMobileSheetNavOpen;\n }\n }\n\n private connectMetricsObserver() {\n this.resizeObserver?.disconnect();\n this.resizeObserver = new ResizeObserver(() => {\n if (this.panelNavTransition.isActive) return;\n this.scheduleChromeSync();\n });\n this.resizeObserver.observe(this.el);\n const panelWrap = this.el.querySelector('.shell-app__panel');\n if (panelWrap) this.resizeObserver.observe(panelWrap);\n }\n\n private connectViewportListeners() {\n if (typeof window === 'undefined') return;\n\n window.addEventListener('resize', this.onWindowResize, { passive: true });\n\n const visual = window.visualViewport;\n if (visual) {\n visual.addEventListener('resize', this.onVisualViewportChange, { passive: true });\n visual.addEventListener('scroll', this.onVisualViewportChange, { passive: true });\n }\n }\n\n private disconnectViewportListeners() {\n if (typeof window === 'undefined') return;\n\n window.removeEventListener('resize', this.onWindowResize);\n\n const visual = window.visualViewport;\n if (visual) {\n visual.removeEventListener('resize', this.onVisualViewportChange);\n visual.removeEventListener('scroll', this.onVisualViewportChange);\n }\n }\n\n private connectPageLifecycleListeners() {\n if (typeof window === 'undefined' || typeof document === 'undefined') return;\n document.addEventListener('visibilitychange', this.onDocumentVisibilityChange);\n window.addEventListener('pageshow', this.onPageShow);\n }\n\n private disconnectPageLifecycleListeners() {\n if (typeof window === 'undefined' || typeof document === 'undefined') return;\n document.removeEventListener('visibilitychange', this.onDocumentVisibilityChange);\n window.removeEventListener('pageshow', this.onPageShow);\n }\n\n private onDocumentVisibilityChange = () => {\n if (!document.hidden) this.refreshAfterForegroundRestore();\n };\n\n private onPageShow = (event: PageTransitionEvent) => {\n if (event.persisted) this.refreshAfterForegroundRestore();\n };\n\n private cancelForegroundRefresh() {\n if (this.foregroundRefreshTimer !== null) {\n window.clearTimeout(this.foregroundRefreshTimer);\n this.foregroundRefreshTimer = null;\n }\n this.el.classList.remove('shell-app--foreground-refresh');\n }\n\n private refreshAfterForegroundRestore() {\n if (!this.el.isConnected) return;\n this.updateResponsiveMode();\n this.syncSlottedMobileState();\n this.scheduleChromeSync();\n\n if (this.resolvedMode !== 'mobile') return;\n\n /*\n * Mobile WebKit can restore a live, correctly sized DOM without repainting\n * its suspended shell layer. Re-promote the complete mobile stage for one\n * painted frame; routed and tool owners remain mounted and keep their state.\n */\n this.cancelForegroundRefresh();\n this.el.classList.add('shell-app--foreground-refresh');\n void this.el.offsetHeight;\n // A short timer spans a real compositor commit; resumed WebKit may coalesce\n // nested animation frames before presenting either one.\n this.foregroundRefreshTimer = window.setTimeout(() => {\n this.foregroundRefreshTimer = null;\n this.el.classList.remove('shell-app--foreground-refresh');\n // WebKit otherwise may keep presenting the retired composite layer.\n void this.el.querySelector<HTMLElement>('.shell-app__main')?.offsetHeight;\n }, ShellApp.FOREGROUND_REFRESH_MS);\n }\n\n private cachePanelWidthTokens() {\n const navRoot = this.el.querySelector('ds-panel-nav .panel-nav') as HTMLElement | null;\n if (!navRoot) return;\n this.panelWidthTokens = readPanelNavWidthTokens(navRoot);\n }\n\n private applyGradientVars(target: HTMLElement, vars: Record<string, string | null>) {\n const style = target.style;\n for (const [name, value] of Object.entries(vars)) {\n if (value === null) style.removeProperty(name);\n else style.setProperty(name, value);\n }\n }\n\n private clearGradientPaintVars(targets: HTMLElement[]) {\n const keys = [SHELL_GRADIENT_IMAGE_VAR, SHELL_GRADIENT_SIZE_VAR, SHELL_GRADIENT_OPACITY_VAR];\n for (const target of targets) {\n for (const key of keys) target.style.removeProperty(key);\n }\n }\n\n /** Panel/bar wash offsets for scroll fades and badge rings. */\n private syncChromeLayoutVars(\n panel: HTMLElement | null,\n bar: HTMLElement | null,\n panelPosition: string,\n barPosition: string\n ) {\n if (panel) {\n panel.style.setProperty(SHELL_GRADIENT_POSITION_PANEL_VAR, panelPosition);\n }\n if (bar) {\n bar.style.setProperty(SHELL_GRADIENT_POSITION_BAR_VAR, barPosition);\n }\n }\n\n private resolvePanelWidthPx(panelNav: HTMLElement | null): number {\n const navRoot = panelNav?.querySelector('.panel-nav') as HTMLElement | null;\n\n if (this.panelNavTransition.isActive && this.panelWidthTokens.expandedPx > 0) {\n const collapsed = isPanelNavCollapsed(panelNav, navRoot);\n return panelWidthPxFromTokens(this.panelWidthTokens, collapsed);\n }\n\n const panelWrap = this.el.querySelector('.shell-app__panel') as HTMLElement | null;\n const measured = panelWrap?.getBoundingClientRect().width ?? 0;\n if (measured > 0) return measured;\n\n if (this.panelWidthTokens.expandedPx > 0) {\n const collapsed = isPanelNavCollapsed(panelNav, navRoot);\n return panelWidthPxFromTokens(this.panelWidthTokens, collapsed);\n }\n\n return 0;\n }\n\n /** Fixed-attachment wash/grid size — always the viewport, never the shell element box. */\n private resolveViewportDimensions(): { width: number; height: number } {\n if (this.panelNavTransition.isActive && this.cachedViewportWidth > 0) {\n return {\n width: this.cachedViewportWidth,\n height: this.cachedViewportHeight,\n };\n }\n\n return readShellViewportDimensions();\n }\n\n private chromeLayerActive(): boolean {\n return this.gradientLayerActive() || this.paperTextureLayerActive();\n }\n\n private gradientLayerActive(): boolean {\n return (\n this.resolvedMode !== 'mobile' && normalizeShellGradientPreset(this.gradientPreset) !== 'none'\n );\n }\n\n private paperTextureLayerActive(): boolean {\n return this.resolvedMode !== 'mobile' && this.paperTexture != null;\n }\n\n @Listen('keydown', { target: 'window', capture: true })\n handleWindowKeyDown(e: KeyboardEvent) {\n if (!this.shortcutsEnabled) return;\n if (isEditableShortcutTarget(e.target)) return;\n\n const action = resolveShellShortcut(e);\n if (!action) return;\n\n e.preventDefault();\n\n const panel = this.el.querySelector('ds-panel-nav') as HTMLDsPanelNavElement | null;\n const tools = this.el.querySelector('ds-shell-tools, ds-panel-tools') as\n | (HTMLElement & {\n closeDrawer?: () => Promise<void>;\n activateTool?: (id: PanelToolsToolId) => Promise<void>;\n })\n | null;\n\n if (action === 'toggle-panel-nav') {\n void panel?.toggleCollapsed();\n return;\n }\n\n if (action === 'close-panel-tools') {\n void tools?.closeDrawer?.();\n return;\n }\n\n if (action.startsWith('open-tool:') && tools) {\n const toolId = action.slice('open-tool:'.length) as PanelToolsToolId;\n void tools.activateTool?.(toolId);\n }\n }\n\n /** Open or toggle a managed global tool from application-owned UI. */\n @Method()\n async activateTool(id: PanelToolsToolId) {\n const tools = this.el.querySelector('ds-shell-tools, ds-panel-tools') as\n | (HTMLElement & { activateTool?: (tool: PanelToolsToolId) => Promise<void> })\n | null;\n await tools?.activateTool?.(id);\n }\n\n /** Close whichever managed global surface is currently visible. */\n @Method()\n async closeGlobalSurface() {\n if (this.managedMobileSheetNavOpen) {\n this.managedMobileSheetNavOpen = false;\n return;\n }\n const tools = this.el.querySelector('ds-shell-tools, ds-panel-tools') as\n | (HTMLElement & { closeDrawer?: () => Promise<void> })\n | null;\n await tools?.closeDrawer?.();\n }\n\n /** Request drawer or fullscreen presentation for the active managed tool. */\n @Method()\n async setToolPresentation(presentation: 'drawer' | 'fullscreen') {\n this.managedToolPresentation = presentation;\n this.toolsFullscreen = presentation === 'fullscreen';\n }\n\n private handleManagedNavSelect = (event: CustomEvent<string>) => {\n event.stopPropagation();\n this.dsNavSelect.emit(event.detail);\n };\n\n private handleManagedNavChildSelect = (event: CustomEvent<PanelNavChildSelectDetail>) => {\n event.stopPropagation();\n this.dsNavChildSelect.emit(event.detail);\n };\n\n private handleManagedTabChange = (event: CustomEvent<string>) => {\n event.stopPropagation();\n this.dsTabChange.emit(event.detail);\n };\n\n private handleManagedPageBack = (event: CustomEvent<MouseEvent>) => {\n event.stopPropagation();\n this.dsPageBack.emit(event.detail);\n };\n\n private handleManagedBreadcrumbSelect = (event: CustomEvent<BreadcrumbSelectDetail>) => {\n event.stopPropagation();\n const forwarded = this.dsBreadcrumbSelect.emit(event.detail);\n if (forwarded.defaultPrevented) event.preventDefault();\n };\n\n private handleManagedSubsectionChange = (event: CustomEvent<string>) => {\n event.stopPropagation();\n this.dsSubsectionChange.emit(event.detail);\n };\n\n private handleManagedPageAction = (event: CustomEvent<string>) => {\n event.stopPropagation();\n this.dsPageAction.emit(event.detail);\n };\n\n private handleManagedToolChange = (\n event: CustomEvent<{ id: PanelToolsToolId; selected: boolean }>\n ) => {\n event.stopPropagation();\n const { id, selected } = event.detail;\n this.managedActiveTool = id;\n this.managedToolsOpen = selected;\n const item = this.resolvedToolItems.find(candidate => candidate.id === id);\n if (item && itemUsesShellInbox(item)) {\n this.managedInboxTool = id;\n }\n this.managedMobileDestination = selected\n ? (item?.mobileDestination ?? shellMobileDestinationForTool(true, id))\n : 'area';\n this.managedMobileSheetNavOpen = false;\n this.dsToolChange.emit(event.detail);\n };\n\n private handleManagedPresentationChange = (\n event: CustomEvent<{ presentation: 'drawer' | 'fullscreen' }>\n ) => {\n event.stopPropagation();\n this.managedToolPresentation = event.detail.presentation;\n this.toolsFullscreen = event.detail.presentation === 'fullscreen';\n this.dsPresentationChange.emit(event.detail);\n };\n\n private handleManagedHeaderBack = (event: CustomEvent<{ tool: PanelToolsToolId }>) => {\n event.stopPropagation();\n this.dsHeaderBack.emit(event.detail);\n };\n\n private handleManagedHeaderAction = (event: CustomEvent<PanelToolsHeaderActionDetail>) => {\n event.stopPropagation();\n this.dsHeaderAction.emit(event.detail);\n };\n\n private handleManagedRailAccessoryAction = (\n event: CustomEvent<PanelToolsRailAccessoryActionDetail>\n ) => {\n event.stopPropagation();\n this.dsRailAccessoryAction.emit(event.detail);\n };\n\n private handleManagedSheetToggle = (event: CustomEvent<boolean>) => {\n event.stopPropagation();\n this.managedMobileSheetNavOpen = event.detail;\n };\n\n private activateManagedMobileTool(destination: Exclude<MobileDestination, 'area'>): boolean {\n const canonicalId =\n destination === 'inbox'\n ? resolveAvailableInboxTool(this.managedInboxTool, this.availableInboxTools)\n : destination;\n if (!canonicalId) return false;\n\n const item = this.resolvedToolItems.find(\n candidate =>\n candidate.id === canonicalId ||\n (destination !== 'inbox' && candidate.mobileDestination === destination)\n );\n if (!item || item.isInactive) return false;\n\n const id = item.id;\n this.managedActiveTool = id;\n this.managedToolsOpen = true;\n if (itemUsesShellInbox(item)) this.managedInboxTool = id;\n if (destination === 'help') this.managedMobilePrimaryDestination = 'help';\n this.dsToolChange.emit({ id: id as PanelToolsToolId, selected: true });\n return true;\n }\n\n private handleManagedMobileDestination = (event: CustomEvent<MobileBarNavDestinationDetail>) => {\n event.stopPropagation();\n const destination = event.detail.destination;\n this.managedMobileSheetNavOpen = false;\n this.managedMobileDestination = destination;\n\n if (destination === 'area') {\n this.managedToolsOpen = false;\n return;\n }\n\n this.activateManagedMobileTool(destination);\n };\n\n private handleManagedAreaSelect = (event: CustomEvent<string>) => {\n event.stopPropagation();\n if (event.detail === 'help') {\n this.managedMobileSheetNavOpen = false;\n this.managedMobileDestination = 'help';\n if (this.activateManagedMobileTool('help')) return;\n }\n\n this.managedMobileSheetNavOpen = false;\n this.managedMobileDestination = 'area';\n this.managedMobilePrimaryDestination = 'area';\n this.managedToolsOpen = false;\n this.dsNavSelect.emit(event.detail);\n };\n\n private handleManagedMobileChildSelect = (event: CustomEvent<PanelNavChildSelectDetail>) => {\n event.stopPropagation();\n this.managedMobileSheetNavOpen = false;\n this.managedMobileDestination = 'area';\n this.managedMobilePrimaryDestination = 'area';\n this.managedToolsOpen = false;\n this.dsNavChildSelect.emit(event.detail);\n };\n\n private handleManagedBrowseContext = (event: CustomEvent<NavChromeStyle>) => {\n event.stopPropagation();\n this.managedBrowseContext = event.detail;\n this.dsBrowseContextChange.emit(event.detail);\n };\n\n private handleManagedSheetClose = (event: CustomEvent<void>) => {\n event.stopPropagation();\n this.managedMobileSheetNavOpen = false;\n };\n\n private handleManagedNavFooterAction = (event: CustomEvent<void>) => {\n event.stopPropagation();\n this.dsNavFooterAction.emit();\n };\n\n private handleManagedNavUserAction = (event: CustomEvent<PanelNavUserActionDetail>) => {\n event.stopPropagation();\n this.dsNavUserAction.emit(event.detail);\n };\n\n private get currentArea(): PanelNavItem {\n const groups = [\n ...(this.navigation.dashboardGroups ?? []),\n ...(this.navigation.settingsGroups ?? []),\n ...this.resolvedNavigationGroups,\n ];\n const items = groups.flatMap(group => group.items);\n const activeId =\n this.navigation.activeId ||\n deriveActiveIdFromUrl(this.navigation.currentUrl ?? this.pageChrome.currentUrl ?? '', items);\n return (\n items.find(item => item.id === activeId) ??\n items[0] ?? {\n id: 'area',\n icon: 'MapPage',\n label: this.pageChrome.heading || 'Area',\n }\n );\n }\n\n private toolDot(id: PanelToolsToolId): boolean {\n return this.resolvedToolItems.find(item => item.id === id)?.dot ?? false;\n }\n\n private syncChrome() {\n if (!this.el.isConnected) return;\n const panelNav = this.el.querySelector('ds-panel-nav') as HTMLElement | null;\n const bar = this.el.querySelector(\n 'ds-bar-nav, ds-bar-page-title, ds-bar-title[placement=\"shell-bar\"]'\n ) as HTMLElement | null;\n const targets = [this.el, panelNav, bar].filter((el): el is HTMLElement => el !== null);\n\n const clearLayoutVars = () => {\n if (panelNav) panelNav.style.removeProperty(SHELL_GRADIENT_POSITION_PANEL_VAR);\n if (bar) bar.style.removeProperty(SHELL_GRADIENT_POSITION_BAR_VAR);\n };\n\n const preset = normalizeShellGradientPreset(this.gradientPreset);\n if (this.resolvedMode === 'mobile') {\n this.clearGradientPaintVars(targets);\n clearLayoutVars();\n return;\n }\n\n const viewport = this.resolveViewportDimensions();\n if (preset === 'none') {\n this.clearGradientPaintVars(targets);\n clearLayoutVars();\n this.syncPaperTextureLayout(viewport);\n return;\n }\n\n const panelWidth = this.resolvePanelWidthPx(panelNav);\n\n const panelPosition = shellGradientPositionPanel();\n const barPosition = shellGradientPositionBar(panelWidth);\n\n this.syncChromeLayoutVars(panelNav, bar, panelPosition, barPosition);\n\n const image = buildShellRadialGradient(preset);\n\n const size = shellGradientSize({\n width: viewport.width,\n height: viewport.height,\n });\n const opacity = shellGradientPresetOpacity(preset);\n\n for (const target of targets) {\n if (target === this.el) {\n this.applyGradientVars(target, {\n [SHELL_GRADIENT_IMAGE_VAR]: image,\n [SHELL_GRADIENT_SIZE_VAR]: size,\n [SHELL_GRADIENT_POSITION_PANEL_VAR]: panelPosition,\n [SHELL_GRADIENT_POSITION_BAR_VAR]: barPosition,\n [SHELL_GRADIENT_OPACITY_VAR]: opacity,\n });\n continue;\n }\n\n const isBar = target === bar;\n this.applyGradientVars(target, {\n [SHELL_GRADIENT_IMAGE_VAR]: image,\n [SHELL_GRADIENT_SIZE_VAR]: size,\n [SHELL_GRADIENT_OPACITY_VAR]: opacity,\n [SHELL_GRADIENT_POSITION_PANEL_VAR]: isBar ? null : panelPosition,\n [SHELL_GRADIENT_POSITION_BAR_VAR]: isBar ? barPosition : null,\n });\n }\n\n this.syncPaperTextureLayout(viewport);\n }\n\n private syncPaperTextureLayout(viewport: { width: number; height: number }) {\n const chrome = this.el.querySelector<HTMLElement>('.shell-app__chrome');\n const paperTexture = this.el.querySelector<HTMLElement>(\n '.shell-app__chrome > ds-paper-texture'\n );\n if (!chrome || !paperTexture) return;\n\n const chromeRect = chrome.getBoundingClientRect();\n paperTexture.style.setProperty('width', `${Math.round(viewport.width)}px`);\n paperTexture.style.setProperty('height', `${Math.round(viewport.height)}px`);\n paperTexture.style.setProperty('left', `${-Math.round(chromeRect.left)}px`);\n paperTexture.style.setProperty('top', `${-Math.round(chromeRect.top)}px`);\n }\n\n private renderPaperTexture() {\n if (!this.paperTextureLayerActive()) return null;\n return <ds-paper-texture class=\"shell-app__paper-texture\" config={this.paperTexture} />;\n }\n\n private renderManagedPanelNav() {\n const navigation = this.navigation;\n return (\n <ds-panel-nav\n navStyle={this.navStyle}\n presentation={this.sectionNavigation === 'panel' ? 'nested' : 'flat'}\n groups={this.resolvedNavigationGroups}\n breakpoint={SHELL_DESKTOP_BREAKPOINT}\n routerMode={navigation.routerMode ?? 'event'}\n activeId={navigation.activeId ?? ''}\n activeChildId={this.pageChrome.value ?? ''}\n currentUrl={navigation.currentUrl ?? this.pageChrome.currentUrl ?? ''}\n storageKey={navigation.storageKey ?? ''}\n userName={navigation.userName ?? ''}\n userInitial={navigation.userInitial ?? ''}\n accountMenuExpanded={navigation.accountMenuExpanded ?? false}\n accountMenuSurfaceOpen={navigation.accountMenuSurfaceOpen}\n dashboardLabel={navigation.dashboardLabel ?? 'Dashboard'}\n settingsLabel={navigation.settingsLabel ?? 'Settings'}\n accountLabel={navigation.accountLabel ?? 'Account'}\n dashboardNavigationLabel={navigation.dashboardNavigationLabel ?? 'Dashboard navigation'}\n settingsNavigationLabel={navigation.settingsNavigationLabel ?? 'Settings navigation'}\n navigationItemsLabel={navigation.navigationLabel ?? 'Navigation items'}\n onDsNavSelect={this.handleManagedNavSelect}\n onDsNavChildSelect={this.handleManagedNavChildSelect}\n onDsNavFooterAction={this.handleManagedNavFooterAction}\n onDsNavUserAction={this.handleManagedNavUserAction}\n />\n );\n }\n\n private renderManagedBarNav() {\n return (\n <ds-bar-nav\n navStyle={this.navStyle}\n tabs={this.resolvedPageTabs}\n value={this.pageChrome.value ?? ''}\n heading={this.pageChrome.routeHeading ?? this.pageChrome.heading}\n basePath={this.pageChrome.basePath ?? ''}\n currentUrl={this.pageChrome.currentUrl ?? this.navigation.currentUrl ?? ''}\n onDsTabChange={this.handleManagedTabChange}\n />\n );\n }\n\n private renderManagedTopBar() {\n return this.sectionNavigation === 'panel'\n ? this.renderManagedBarPageTitle()\n : this.renderManagedBarNav();\n }\n\n private renderManagedToolSlots() {\n const ids = [...new Set(this.resolvedToolItems.map(item => item.id))].sort();\n return ids.flatMap(id => [\n <div key={`tool:${id}`} class=\"shell-app__tool-slot-proxy\" slot={id}>\n <slot name={id} />\n </div>,\n <div key={`tool-view:${id}`} class=\"shell-app__tool-slot-proxy\" slot={`${id}-view`}>\n <slot name={`${id}-view`} />\n </div>,\n ]);\n }\n\n private renderManagedTools() {\n const tools = this.tools;\n return (\n <ds-shell-tools\n responsiveMode={this.resolvedMode}\n open={this.managedToolsOpen}\n activeTool={this.managedActiveTool}\n presentation={this.managedToolPresentation}\n fullscreenHeaderMode={tools.fullscreenHeaderMode ?? 'shared'}\n items={this.resolvedToolItems}\n accessories={tools.accessories ?? []}\n headers={tools.headers ?? {}}\n storageKey={tools.storageKey ?? ''}\n toolsLabel={tools.toolsLabel ?? 'Tools'}\n toolShortcutsLabel={tools.toolShortcutsLabel ?? 'Tool shortcuts'}\n inboxLabel={tools.inboxLabel ?? 'Inbox'}\n inboxNavigationLabel={tools.inboxNavigationLabel ?? 'Inbox sections'}\n onDsToolChange={this.handleManagedToolChange}\n onDsPresentationChange={this.handleManagedPresentationChange}\n onDsHeaderBack={this.handleManagedHeaderBack}\n onDsHeaderAction={this.handleManagedHeaderAction}\n onDsRailAccessoryAction={this.handleManagedRailAccessoryAction}\n >\n {this.renderManagedToolSlots()}\n </ds-shell-tools>\n );\n }\n\n private renderManagedMobileSheetNav() {\n const navigation = this.navigation;\n return (\n <ds-mobile-sheet-nav\n open={this.managedMobileSheetNavOpen}\n presentation={this.sectionNavigation === 'panel' ? 'nested' : 'flat'}\n browseContext={this.managedBrowseContext}\n dashboardGroups={navigation.dashboardGroups ?? navigation.groups ?? []}\n settingsGroups={navigation.settingsGroups ?? []}\n currentUrl={navigation.currentUrl ?? this.pageChrome.currentUrl ?? ''}\n routeSelectionActive={this.managedMobileDestination === 'area'}\n navigationLabel={navigation.navigationLabel ?? 'Application navigation'}\n dashboardLabel={navigation.dashboardLabel ?? 'Dashboard'}\n settingsLabel={navigation.settingsLabel ?? 'Settings'}\n accountLabel={navigation.accountLabel ?? 'Account'}\n showAccount={navigation.showMobileAccount ?? true}\n onDsAreaSelect={this.handleManagedAreaSelect}\n onDsNavChildSelect={this.handleManagedMobileChildSelect}\n onDsBrowseContextChange={this.handleManagedBrowseContext}\n onDsClose={this.handleManagedSheetClose}\n />\n );\n }\n\n private renderManagedMobileBarNav() {\n const item = (id: PanelToolsToolId) =>\n this.resolvedToolItems.find(candidate => candidate.id === id);\n const label = (id: PanelToolsToolId, fallback: string) =>\n item(id)?.ariaLabel ?? item(id)?.label ?? fallback;\n const directActivity = this.resolvedToolItems.some(\n candidate => !candidate.isInactive && candidate.mobileDestination === 'activity'\n );\n\n return (\n <ds-mobile-bar-nav\n hidden={this.tools.mobileBarHidden ?? false}\n activeDestination={this.managedMobileDestination}\n primaryDestination={this.managedMobilePrimaryDestination}\n currentArea={this.currentArea}\n sheetNavExpanded={this.managedMobileSheetNavOpen}\n searchLabel={label('search', 'Search')}\n activityMode={directActivity ? 'direct' : 'inbox'}\n activityLabel={label('activity', 'Activity')}\n inboxLabel={this.tools.inboxLabel ?? 'Inbox'}\n messagesLabel={label('messages', 'Messages')}\n agentsLabel={label('agents', 'Agents')}\n helpLabel={label('help', 'Help & Support')}\n searchDot={this.toolDot('search')}\n activityDot={this.toolDot('activity')}\n inboxDot={this.toolDot('stacks') || this.toolDot('activity')}\n messagesDot={this.toolDot('messages')}\n agentsDot={this.toolDot('agents')}\n onDsSheetNavToggle={this.handleManagedSheetToggle}\n onDsDestinationChange={this.handleManagedMobileDestination}\n />\n );\n }\n\n private renderManagedMobileLeading() {\n if (!this.pageChrome.showBack) {\n return <slot name=\"page-header-leading\" slot=\"leading\" />;\n }\n return [\n <ds-tooltip\n slot=\"leading\"\n label={this.pageChrome.backAriaLabel ?? 'Back'}\n side=\"bottom\"\n size=\"sm\"\n >\n <ds-button-unfilled\n variant=\"icon\"\n icon={this.pageChrome.backIcon ?? 'ChevronLeft'}\n size=\"md\"\n aria-label={this.pageChrome.backAriaLabel ?? 'Back'}\n activeFill={false}\n hasBorder={false}\n onDsClick={(event: CustomEvent<MouseEvent>) => this.dsPageBack.emit(event.detail)}\n />\n </ds-tooltip>,\n <slot name=\"page-header-leading\" slot=\"leading\" />,\n ];\n }\n\n private renderManagedMobileActions() {\n const legacyActions = this.pageChrome.mobileActions ?? [];\n const visibleActions = visibleBarTitleActions(this.resolvedHeaderActions, 'mobile');\n return [\n ...legacyActions.map((action: PanelToolsHeaderAction) => (\n <ds-tooltip\n slot=\"trailing\"\n key={action.id}\n label={action.ariaLabel}\n side=\"bottom\"\n size=\"sm\"\n >\n <ds-button-unfilled\n id={action.triggerId || undefined}\n variant=\"icon\"\n icon={action.icon}\n size=\"md\"\n aria-label={action.ariaLabel}\n haspopup={action.haspopup}\n controls={action.controls}\n expanded={action.expanded}\n surfaceOpen={action.surfaceOpen}\n pressed={action.pressed}\n isInactive={action.isInactive}\n activeFill={false}\n hasBorder={false}\n onDsClick={() => this.dsPageAction.emit(action.id)}\n />\n </ds-tooltip>\n )),\n ...visibleActions.map(action => {\n if (action.type !== 'icon') return null;\n const button =\n (action.appearance ?? 'unfilled') === 'filled' ? (\n <ds-button-filled\n variant=\"icon\"\n icon={action.icon}\n aria-label={action.ariaLabel}\n size=\"md\"\n intent={action.intent ?? 'brand'}\n contrast={action.contrast ?? 'bold'}\n isInactive={action.isInactive}\n isLoading={action.isLoading}\n onDsClick={() => this.dsPageAction.emit(action.id)}\n />\n ) : (\n <ds-button-unfilled\n variant=\"icon\"\n icon={action.icon}\n aria-label={action.ariaLabel}\n size=\"md\"\n isInactive={action.isInactive}\n isLoading={action.isLoading}\n activeFill={false}\n hasBorder={false}\n onDsClick={() => this.dsPageAction.emit(action.id)}\n />\n );\n return (\n <ds-tooltip slot=\"trailing\" key={action.id} label={action.label} side=\"bottom\" size=\"sm\">\n {button}\n </ds-tooltip>\n );\n }),\n ...(this.mobileActionMenuSections.length > 0\n ? [\n <ds-tooltip slot=\"trailing\" label=\"Page options\" side=\"bottom\" size=\"sm\">\n <ds-button-unfilled\n ref={el => {\n this.mobileActionTriggerEl = el ?? null;\n }}\n id={this.mobileActionMenuTriggerId}\n variant=\"icon\"\n icon=\"Ellipses\"\n aria-label={this.pageChrome.actionsAriaLabel ?? 'More page actions'}\n size=\"md\"\n activeFill={false}\n hasBorder={false}\n haspopup=\"menu\"\n controls={this.mobileActionMenuId}\n expanded={this.mobileActionMenuOpen}\n surfaceOpen={this.mobileActionSurfaceOpen}\n onDsClick={(event: CustomEvent<MouseEvent>) => {\n this.mobileActionMenuInitialFocusVisible = event.detail.detail === 0;\n this.mobileActionMenuOpen = !this.mobileActionMenuOpen;\n if (this.mobileActionMenuOpen) this.mobileActionSurfaceOpen = true;\n }}\n />\n </ds-tooltip>,\n ]\n : []),\n <slot name=\"page-header-trailing\" slot=\"trailing\" />,\n ];\n }\n\n private get resolvedHeaderActions(): BarTitleActionConfigItem[] {\n return resolveBarTitleActionItems(\n this.pageChrome.actionItems,\n this.pageChrome.primaryAction ?? null,\n this.pageChrome.actions ?? []\n );\n }\n\n private get mobileActionMenuSections() {\n return overflowBarTitleActionSections(this.resolvedHeaderActions, 'mobile');\n }\n\n private closeMobileActionMenu = () => {\n this.mobileActionMenuOpen = false;\n };\n\n private handleMobileActionSelect = (event: CustomEvent<MenuItemData>) => {\n const id = String(event.detail.value ?? '');\n const action = findBarTitleAction(this.resolvedHeaderActions, id);\n if (!action || action.isInactive || ('isLoading' in action && action.isLoading)) return;\n this.mobileActionMenuOpen = false;\n this.dsPageAction.emit(id);\n this.connectionTasks.frame(() => void this.mobileActionTriggerEl?.setFocus?.());\n };\n\n private renderManagedMobileActionMenu() {\n if (this.mobileActionMenuSections.length === 0) return null;\n return (\n <ds-menu\n id={this.mobileActionMenuId}\n anchorId={this.mobileActionMenuTriggerId}\n align=\"end\"\n menuLabel={this.pageChrome.actionsAriaLabel ?? 'More page actions'}\n open={this.mobileActionMenuOpen}\n initialFocusVisible={this.mobileActionMenuInitialFocusVisible}\n sections={this.mobileActionMenuSections}\n onDsClose={this.closeMobileActionMenu}\n onDsAfterClose={() => {\n if (!this.mobileActionMenuOpen) this.mobileActionSurfaceOpen = false;\n }}\n onDsSelect={this.handleMobileActionSelect}\n />\n );\n }\n\n private get barTitleSections(): BarTitleSectionItem[] {\n return (this.pageChrome.subsections ?? []).map(item =>\n 'type' in item\n ? { type: 'divider' }\n : {\n id: item.id,\n label: item.label,\n isInactive: item.isInactive,\n }\n );\n }\n\n private renderManagedBarPageTitle() {\n const page = this.pageChrome;\n return (\n <ds-bar-page-title\n data-shell-bar=\"\"\n ref={el => {\n if (el) {\n (el as HTMLElement & { actionItems?: BarTitleActionConfigItem[] }).actionItems =\n page.actionItems;\n }\n }}\n heading={page.heading ?? ''}\n showBack={page.showBack ?? false}\n backAriaLabel={page.backAriaLabel ?? 'Back'}\n sections={this.barTitleSections}\n value={page.subvalue ?? ''}\n sectionsAriaLabel={page.subsectionsAriaLabel ?? 'Change page subsection'}\n primaryAction={page.primaryAction ?? null}\n actions={page.actions ?? []}\n actionsAriaLabel={page.actionsAriaLabel ?? 'More page actions'}\n showDivider={page.showHeaderDivider ?? true}\n showCompactDivider={page.showCompactHeaderDivider}\n onDsBack={this.handleManagedPageBack}\n onDsSectionChange={this.handleManagedSubsectionChange}\n onDsAction={this.handleManagedPageAction}\n />\n );\n }\n\n private renderManagedBarTitle(placement: BarTitlePlacement) {\n const page = this.pageChrome;\n return (\n <ds-bar-title\n ref={el => {\n if (el) {\n (el as HTMLElement & { actionItems?: BarTitleActionConfigItem[] }).actionItems =\n page.actionItems;\n }\n }}\n slot={placement === 'page' ? 'header' : undefined}\n placement={placement}\n variant={placement === 'shell-bar' ? 'compact' : 'expanded'}\n heading={page.heading ?? ''}\n description={page.description ?? ''}\n showBack={page.showBack ?? false}\n backAriaLabel={page.backAriaLabel ?? 'Back'}\n backLabel={page.backLabel ?? 'Back'}\n breadcrumbs={page.breadcrumbs ?? []}\n breadcrumbAriaLabel={page.breadcrumbAriaLabel ?? 'Breadcrumb'}\n sections={this.barTitleSections}\n value={page.subvalue ?? ''}\n sectionsAriaLabel={page.subsectionsAriaLabel ?? 'Change page subsection'}\n primaryAction={page.primaryAction ?? null}\n actions={page.actions ?? []}\n actionsAriaLabel={page.actionsAriaLabel ?? 'More page actions'}\n showDivider={page.showHeaderDivider ?? true}\n showCompactDivider={page.showCompactHeaderDivider}\n onDsBack={this.handleManagedPageBack}\n onDsBreadcrumbSelect={this.handleManagedBreadcrumbSelect}\n onDsSectionChange={this.handleManagedSubsectionChange}\n onDsAction={this.handleManagedPageAction}\n />\n );\n }\n\n private renderManagedPage() {\n const page = this.pageChrome;\n const nestedNavigation = this.sectionNavigation === 'panel';\n const mobileSections = page.showBack || nestedNavigation ? [] : this.resolvedPageTabs;\n return [\n <ds-shell-page\n responsiveMode={this.resolvedMode}\n headerCapacity={resolveManagedShellPageCapacity(this.resolvedMode)}\n headerPresentation={page.headerPresentation ?? 'auto'}\n desktopHeaderPlacement={this.sectionNavigation === 'panel' ? 'shell-bar' : 'page'}\n contentInset={page.contentInset ?? 'default'}\n contentInsetBlockStart={page.contentInsetBlockStart ?? 'default'}\n contentInsetBlockStartSize={page.contentInsetBlockStartSize}\n compactContentInsetBlockStartSize={page.compactContentInsetBlockStartSize}\n scrollCompaction={page.scrollCompaction ?? true}\n contentSurface={page.contentSurface ?? 'primary'}\n >\n {this.sectionNavigation === 'bar' ? this.renderManagedBarTitle('page') : null}\n <ds-mobile-header\n slot=\"mobile-header\"\n heading={page.heading ?? ''}\n headingLevel={page.headingLevel ?? 'h1'}\n sections={mobileSections}\n value={page.showBack ? '' : (page.value ?? '')}\n sectionsAriaLabel={page.sectionsAriaLabel ?? 'Change page section'}\n subsections={page.subsections ?? []}\n subsectionsPlacement={page.showBack || nestedNavigation ? 'combined' : 'below'}\n subvalue={page.subvalue ?? ''}\n subsectionsAriaLabel={page.subsectionsAriaLabel ?? 'Change page subsection'}\n tone={page.tone ?? 'default'}\n onDsSectionChange={this.handleManagedTabChange}\n onDsSubsectionChange={this.handleManagedSubsectionChange}\n >\n {this.renderManagedMobileLeading()}\n {this.renderManagedMobileActions()}\n </ds-mobile-header>\n <slot />\n </ds-shell-page>,\n this.renderManagedMobileActionMenu(),\n ];\n }\n\n private renderSlottedShell(\n shellCls: Record<string, boolean>,\n mobile: boolean,\n mobileToolActive: boolean,\n mobileStageBlocked: boolean,\n fullscreen: boolean\n ) {\n return (\n <Host\n class={shellCls}\n responsive-mode={this.resolvedMode}\n data-wide-viewport={String(this.wideViewport)}\n >\n <div class=\"shell-app__banner\">\n <slot name=\"banner\" />\n </div>\n <div class=\"shell-app__row\">\n <div class=\"shell-app__chrome\" aria-hidden=\"true\">\n {this.renderPaperTexture()}\n </div>\n <div\n class=\"shell-app__panel\"\n aria-hidden={fullscreen ? 'true' : undefined}\n inert={fullscreen ? true : undefined}\n >\n <slot name=\"panel\" />\n </div>\n <div class=\"shell-app__main\">\n <div\n class=\"shell-app__bar\"\n aria-hidden={fullscreen ? 'true' : undefined}\n inert={fullscreen ? true : undefined}\n >\n <slot name=\"bar\" />\n </div>\n <div\n class=\"shell-app__tools\"\n data-ds-overlay-boundary={fullscreen ? undefined : true}\n aria-hidden={\n mobile && (!mobileToolActive || this.mobileSheetNavOpen) ? 'true' : undefined\n }\n inert={mobile && (!mobileToolActive || this.mobileSheetNavOpen) ? true : undefined}\n hidden={mobile && !mobileToolActive}\n >\n <slot name=\"tools\" />\n </div>\n <div\n class=\"shell-app__content\"\n data-ds-overlay-boundary\n aria-hidden={fullscreen || mobileStageBlocked ? 'true' : undefined}\n inert={fullscreen || mobileStageBlocked ? true : undefined}\n tabIndex={0}\n >\n <slot />\n </div>\n <div\n class=\"shell-app__mobile-sheet-nav\"\n hidden={!mobile || !this.mobileSheetNavOpen}\n aria-hidden={mobile && this.mobileSheetNavOpen ? undefined : 'true'}\n inert={mobile && this.mobileSheetNavOpen ? undefined : true}\n >\n <slot name=\"mobile-sheet-nav\" />\n </div>\n <div class=\"shell-app__mobile-bar-nav\" hidden={!mobile}>\n <slot name=\"mobile-bar-nav\" />\n </div>\n </div>\n </div>\n </Host>\n );\n }\n\n render() {\n const chromeActive = this.chromeLayerActive();\n const gradientActive = this.gradientLayerActive();\n const paperTextureActive = this.paperTextureLayerActive();\n const mobile = this.resolvedMode === 'mobile';\n const mobileToolActive = mobile && this.effectiveMobileDestination !== 'area';\n const mobileStageBlocked = mobile && (mobileToolActive || this.effectiveMobileSheetNavOpen);\n const fullscreen = !mobile && this.toolsFullscreen;\n const shellCls: Record<string, boolean> = {\n 'shell-app': true,\n 'shell-app--chrome': chromeActive,\n 'shell-app--gradient': gradientActive,\n 'shell-app--paper-texture': paperTextureActive,\n [`shell-app--${this.navStyle}`]: true,\n [`shell-app--${this.resolvedMode}`]: true,\n 'shell-app--tools-fullscreen': fullscreen,\n 'shell-app--mobile-tool-active': mobileToolActive,\n 'shell-app--mobile-sheet-nav-open': mobile && this.effectiveMobileSheetNavOpen,\n 'shell-app--managed': this.managed,\n 'shell-app--slotted': !this.managed,\n };\n\n if (!this.managed) {\n return this.renderSlottedShell(\n shellCls,\n mobile,\n mobileToolActive,\n mobileStageBlocked,\n fullscreen\n );\n }\n\n return (\n <Host\n class={shellCls}\n responsive-mode={this.resolvedMode}\n data-wide-viewport={String(this.wideViewport)}\n >\n <div class=\"shell-app__banner\">\n <slot name=\"banner\" />\n </div>\n <div class=\"shell-app__row\">\n <div class=\"shell-app__chrome\" aria-hidden=\"true\">\n {this.renderPaperTexture()}\n </div>\n <div\n class=\"shell-app__panel\"\n aria-hidden={fullscreen ? 'true' : undefined}\n inert={fullscreen ? true : undefined}\n >\n {this.renderManagedPanelNav()}\n </div>\n <div class=\"shell-app__main\">\n <div\n class=\"shell-app__bar\"\n aria-hidden={fullscreen ? 'true' : undefined}\n inert={fullscreen ? true : undefined}\n >\n {this.renderManagedTopBar()}\n </div>\n <div\n class=\"shell-app__tools\"\n data-ds-overlay-boundary={fullscreen ? undefined : true}\n aria-hidden={\n mobile && (!mobileToolActive || this.effectiveMobileSheetNavOpen)\n ? 'true'\n : undefined\n }\n inert={\n mobile && (!mobileToolActive || this.effectiveMobileSheetNavOpen) ? true : undefined\n }\n hidden={mobile && !mobileToolActive}\n >\n {this.renderManagedTools()}\n </div>\n <div\n class=\"shell-app__content\"\n data-ds-overlay-boundary\n aria-hidden={fullscreen || mobileStageBlocked ? 'true' : undefined}\n inert={fullscreen || mobileStageBlocked ? true : undefined}\n tabIndex={0}\n >\n {this.renderManagedPage()}\n </div>\n <div\n class=\"shell-app__mobile-sheet-nav\"\n hidden={!mobile || !this.effectiveMobileSheetNavOpen}\n aria-hidden={mobile && this.effectiveMobileSheetNavOpen ? undefined : 'true'}\n inert={mobile && this.effectiveMobileSheetNavOpen ? undefined : true}\n >\n {this.renderManagedMobileSheetNav()}\n </div>\n <div class=\"shell-app__mobile-bar-nav\" hidden={!mobile}>\n {this.renderManagedMobileBarNav()}\n </div>\n </div>\n </div>\n </Host>\n );\n }\n}\n",
|
|
790
|
+
"content": "import {\n Component,\n Prop,\n Element,\n Event,\n EventEmitter,\n State,\n Watch,\n Listen,\n Method,\n h,\n Host,\n} from '@stencil/core';\nimport type { NavChromeStyle } from '../../shell/nav-chrome';\nimport { ConnectionTasks } from '../../utils/connection-tasks';\nimport { isEditableShortcutTarget, resolveShellShortcut } from '../../shell/shell-shortcuts';\nimport type {\n PanelToolsHeaderAction,\n PanelToolsHeaderActionDetail,\n PanelToolsRailAccessoryActionDetail,\n PanelToolsToolId,\n} from '../PanelTools/panel-tools-types';\nimport { PANEL_TOOLS_DEFAULT_ITEMS } from '../PanelTools/panel-tools-types';\nimport type { BarTitleActionConfigItem, BarTitleSectionItem } from '../BarTitle/bar-title-types';\nimport type { BarTitlePlacement } from '../BarTitle/bar-title-types';\nimport {\n findBarTitleAction,\n overflowBarTitleActionSections,\n resolveBarTitleActionItems,\n visibleBarTitleActions,\n} from '../BarTitle/bar-title-actions';\nimport type { BarNavTab } from '../BarNav/bar-nav-types';\nimport type { BreadcrumbSelectDetail } from '../Breadcrumb/breadcrumb-types';\nimport type { MenuItemData } from '../Menu/menu-types';\nimport {\n DEFAULT_SHELL_GRADIENT_PRESET,\n normalizeShellGradientPreset,\n type ShellGradientPreset,\n} from '../../shell/shell-gradient-presets';\nimport {\n CHROME_TRANSITION_END,\n CHROME_TRANSITION_START,\n ChromeTransitionDepth,\n createRafCoalescer,\n readChromeTransitionSource,\n} from '../../shell/chrome-transition';\nimport {\n isPanelNavCollapsed,\n panelWidthPxFromTokens,\n readPanelNavWidthTokens,\n type PanelNavWidthTokens,\n} from '../../shell/shell-chrome-metrics';\nimport {\n SHELL_GRADIENT_IMAGE_VAR,\n SHELL_GRADIENT_OPACITY_VAR,\n SHELL_GRADIENT_POSITION_BAR_VAR,\n SHELL_GRADIENT_POSITION_PANEL_VAR,\n SHELL_GRADIENT_SIZE_VAR,\n buildShellRadialGradient,\n readShellViewportDimensions,\n shellGradientPositionBar,\n shellGradientPositionPanel,\n shellGradientPresetOpacity,\n shellGradientSize,\n} from '../../shell/shell-gradient';\nimport {\n itemUsesShellInbox,\n resolveAvailableInboxTool,\n resolveManagedShellPageCapacity,\n resolveShellResponsiveMode,\n SHELL_DESKTOP_BREAKPOINT,\n SHELL_WIDE_DESKTOP_BREAKPOINT,\n shellMobileDestinationForTool,\n type MobileDestination,\n type ShellInboxToolId,\n type ShellResponsiveMode,\n} from '../../shell/shell-responsive';\nimport { deriveActiveIdFromUrl } from '../PanelNav/panel-nav-utils';\nimport type { PanelNavChildSelectDetail } from '../PanelNav/panel-nav-types';\nimport type { PanelNavItem } from '../PanelNav/panel-nav-types';\nimport type { PanelNavUserActionDetail } from '../PanelNav/panel-nav-types';\nimport type { MobileBarNavDestinationDetail } from '../MobileBarNav/mobile-bar-nav-types';\nimport type {\n ShellAppComposition,\n ShellNavigationConfig,\n ShellPageChromeConfig,\n ShellSectionNavigation,\n ShellToolsConfig,\n} from './shell-app-types';\nimport type { PaperTextureConfig } from '../PaperTexture/paper-texture-types';\n\nlet nextShellAppId = 0;\ntype FocusableButton = HTMLElement & { setFocus?: () => Promise<void> };\n\n@Component({\n tag: 'ds-shell-app',\n styleUrl: 'ShellApp.css',\n scoped: true,\n})\nexport class ShellApp {\n private static readonly FOREGROUND_REFRESH_MS = 50;\n\n /** Managed renders the complete responsive chrome; slotted exposes the advanced composition. */\n @Prop({ reflect: true }) composition: ShellAppComposition = 'managed';\n\n /** Router-owned navigation data used by managed composition. */\n @Prop() navigation: ShellNavigationConfig = {};\n\n /** Route sections in bar/header pickers, or nested beneath desktop and mobile menu parents. */\n @Prop({ attribute: 'section-navigation', reflect: true })\n sectionNavigation: ShellSectionNavigation = 'bar';\n\n /** Route-owned page title and section data used by managed composition. */\n @Prop({ attribute: 'page-chrome' }) pageChrome: ShellPageChromeConfig = {};\n\n /** Product-owned global tool definitions used by managed composition. */\n @Prop() tools: ShellToolsConfig = {};\n\n /** Chrome style propagated to slotted `ds-panel-nav` and `ds-bar-nav`. */\n @Prop({ attribute: 'nav-style', reflect: true }) navStyle: NavChromeStyle = 'dashboard';\n\n /**\n * Shell chrome wash preset. `none` renders solid chrome; the remaining\n * presets use token-based washes that adapt to the active color theme.\n */\n @Prop({ attribute: 'gradient-preset', reflect: true }) gradientPreset: ShellGradientPreset =\n DEFAULT_SHELL_GRADIENT_PRESET;\n\n /** Optional decorative Paper Design texture layered above the shell wash. */\n @Prop({ attribute: 'paper-texture' }) paperTexture?: PaperTextureConfig;\n\n /** When `true` (default), registers global shell keyboard shortcuts. `[` toggles panel nav; `]` closes tools; K, A, S, M, N, and / toggle tool drawers. Modifiers are ignored so browser chords like ⌘N stay native. */\n @Prop({ attribute: 'shortcuts-enabled' }) shortcutsEnabled: boolean = true;\n\n /** Controlled mobile surface shown above the persistent bottom bar. */\n @Prop({ attribute: 'mobile-destination' })\n mobileDestination: MobileDestination = 'area';\n\n /** Controlled full-stage Mobile Sheet Nav state. */\n @Prop({ attribute: 'mobile-sheet-nav-open' })\n mobileSheetNavOpen: boolean = false;\n\n /** Emitted after crossing the fixed 768px or 1200px shell boundaries. */\n @Event() dsResponsiveModeChange!: EventEmitter<{ mode: ShellResponsiveMode }>;\n\n /** Managed primary-navigation intent; the application router performs navigation. */\n @Event() dsNavSelect!: EventEmitter<string>;\n\n /** Managed desktop BarNav or mobile section-selection intent. */\n @Event() dsTabChange!: EventEmitter<string>;\n\n /** Managed nested PanelNav child-route intent. */\n @Event() dsNavChildSelect!: EventEmitter<PanelNavChildSelectDetail>;\n\n /** Managed page-subsection intent from BarTitle or MobileHeader. */\n @Event() dsSubsectionChange!: EventEmitter<string>;\n\n /** Managed page-level Back intent. */\n @Event() dsPageBack!: EventEmitter<MouseEvent>;\n\n /** Managed breadcrumb intent from BarTitle. */\n @Event({ cancelable: true })\n dsBreadcrumbSelect!: EventEmitter<BreadcrumbSelectDetail>;\n\n /** Managed page action intent from BarTitle. */\n @Event() dsPageAction!: EventEmitter<string>;\n\n /** Managed tools selection state. */\n @Event() dsToolChange!: EventEmitter<{ id: PanelToolsToolId; selected: boolean }>;\n\n /** Managed tool-header Back intent. */\n @Event() dsHeaderBack!: EventEmitter<{ tool: PanelToolsToolId }>;\n\n /** Managed tool-header action intent. */\n @Event() dsHeaderAction!: EventEmitter<PanelToolsHeaderActionDetail>;\n\n /** Managed desktop/tablet rail accessory intent. */\n @Event() dsRailAccessoryAction!: EventEmitter<PanelToolsRailAccessoryActionDetail>;\n\n /** Managed drawer/fullscreen presentation state. */\n @Event() dsPresentationChange!: EventEmitter<{ presentation: 'drawer' | 'fullscreen' }>;\n\n /** Managed mobile Dashboard/Settings browsing intent. */\n @Event() dsBrowseContextChange!: EventEmitter<NavChromeStyle>;\n\n /** Managed account-footer action intent. */\n @Event() dsNavFooterAction!: EventEmitter<void>;\n\n /** Managed user/account trigger intent. */\n @Event() dsNavUserAction!: EventEmitter<PanelNavUserActionDetail>;\n\n @Element() el!: HTMLElement;\n @State() private toolsFullscreen = false;\n @State() private resolvedMode: ShellResponsiveMode = 'desktop';\n @State() private wideViewport = false;\n @State() private managedToolsOpen = false;\n @State() private managedActiveTool: PanelToolsToolId | '' = '';\n @State() private managedToolPresentation: 'drawer' | 'fullscreen' = 'drawer';\n @State() private managedMobileDestination: MobileDestination = 'area';\n @State() private managedMobilePrimaryDestination: 'area' | 'help' = 'area';\n @State() private managedMobileSheetNavOpen = false;\n @State() private managedInboxTool: ShellInboxToolId | '' = '';\n @State() private managedBrowseContext: NavChromeStyle = 'dashboard';\n @State() private mobileActionMenuOpen = false;\n @State() private mobileActionSurfaceOpen = false;\n @State() private mobileActionMenuInitialFocusVisible = false;\n\n private readonly instanceId = nextShellAppId++;\n private readonly mobileActionMenuTriggerId = `shell-app-mobile-action-trigger-${this.instanceId}`;\n private readonly mobileActionMenuId = `shell-app-mobile-action-menu-${this.instanceId}`;\n private mobileActionTriggerEl: FocusableButton | null = null;\n private resizeObserver: ResizeObserver | null = null;\n private hasLoaded = false;\n private readonly connectionTasks = new ConnectionTasks(() => this.el.isConnected);\n private foregroundRefreshTimer: number | null = null;\n private readonly panelNavTransition = new ChromeTransitionDepth();\n private readonly chromeSyncCoalescer = createRafCoalescer(() => this.syncChrome());\n private panelWidthTokens: PanelNavWidthTokens = { expandedPx: 0, collapsedPx: 0 };\n private cachedViewportWidth = 0;\n private cachedViewportHeight = 0;\n private onWindowResize = () => {\n this.updateResponsiveMode();\n if (this.panelNavTransition.isActive) return;\n this.scheduleChromeSync();\n };\n private onVisualViewportChange = () => {\n if (this.panelNavTransition.isActive) return;\n this.scheduleChromeSync();\n };\n\n private get managed(): boolean {\n return this.composition === 'managed';\n }\n\n private get effectiveMobileDestination(): MobileDestination {\n return this.managed ? this.managedMobileDestination : this.mobileDestination;\n }\n\n private get effectiveMobileSheetNavOpen(): boolean {\n return this.managed ? this.managedMobileSheetNavOpen : this.mobileSheetNavOpen;\n }\n\n private get resolvedNavigationGroups() {\n if (this.navigation.groups?.length) return this.navigation.groups;\n return this.managedBrowseContext === 'settings'\n ? (this.navigation.settingsGroups ?? [])\n : (this.navigation.dashboardGroups ?? []);\n }\n\n private get resolvedToolItems() {\n return this.tools.items ?? PANEL_TOOLS_DEFAULT_ITEMS;\n }\n\n private get resolvedPageTabs(): BarNavTab[] {\n const children = this.currentArea.children;\n if (!children?.length) return this.pageChrome.tabs ?? [];\n return children.map(child => ({\n id: child.id,\n label: child.label,\n dot: child.dot,\n isInactive: child.isInactive,\n }));\n }\n\n private get availableInboxTools(): ShellInboxToolId[] {\n return this.resolvedToolItems\n .filter(item => !item.isInactive && itemUsesShellInbox(item))\n .map(item => item.id as ShellInboxToolId);\n }\n\n componentWillLoad() {\n if (typeof window !== 'undefined') {\n this.resolvedMode = resolveShellResponsiveMode(window.innerWidth);\n this.wideViewport = window.innerWidth >= SHELL_WIDE_DESKTOP_BREAKPOINT;\n }\n this.managedBrowseContext = this.navigation.browseContext ?? this.navStyle;\n this.managedMobileDestination = this.mobileDestination;\n this.managedMobileSheetNavOpen = this.mobileSheetNavOpen;\n this.managedInboxTool = resolveAvailableInboxTool('', this.availableInboxTools);\n }\n\n connectedCallback() {\n if (this.hasLoaded) this.connectResources();\n }\n\n componentDidLoad() {\n this.hasLoaded = true;\n this.connectResources();\n }\n\n private connectResources() {\n if (!this.el.isConnected) return;\n this.updateResponsiveMode();\n this.syncSlottedNavStyle();\n this.syncSlottedMobileState();\n this.connectMetricsObserver();\n this.connectViewportListeners();\n this.connectPageLifecycleListeners();\n this.el.addEventListener(CHROME_TRANSITION_START, this.onChromeTransitionStart);\n this.el.addEventListener(CHROME_TRANSITION_END, this.onChromeTransitionEnd);\n this.connectionTasks.frame(() => {\n this.cachePanelWidthTokens();\n this.scheduleChromeSync();\n });\n const tools = this.el.querySelector('ds-shell-tools, ds-panel-tools');\n this.toolsFullscreen = tools?.getAttribute('presentation') === 'fullscreen';\n }\n\n @Listen('dsPresentationChange')\n handleToolsPresentation(event: CustomEvent<{ presentation?: string }>) {\n this.toolsFullscreen = event.detail?.presentation === 'fullscreen';\n }\n\n disconnectedCallback() {\n this.connectionTasks.cancel();\n this.resizeObserver?.disconnect();\n this.resizeObserver = null;\n this.disconnectViewportListeners();\n this.disconnectPageLifecycleListeners();\n this.cancelForegroundRefresh();\n this.chromeSyncCoalescer.cancel();\n this.panelNavTransition.reset();\n this.el.removeEventListener(CHROME_TRANSITION_START, this.onChromeTransitionStart);\n this.el.removeEventListener(CHROME_TRANSITION_END, this.onChromeTransitionEnd);\n }\n\n @Watch('navStyle')\n @Watch('gradientPreset')\n @Watch('paperTexture')\n @Watch('sectionNavigation')\n onShellPropsChange() {\n this.syncSlottedNavStyle();\n this.scheduleChromeSync();\n }\n\n @Watch('navigation')\n handleNavigationChange(next: ShellNavigationConfig, previous: ShellNavigationConfig) {\n this.managedBrowseContext = this.navigation.browseContext ?? this.navStyle;\n if (next.currentUrl !== previous.currentUrl || next.activeId !== previous.activeId) {\n this.managedMobilePrimaryDestination = 'area';\n }\n }\n\n @Watch('pageChrome')\n handlePageChromeChange() {\n if (this.mobileActionMenuSections.length === 0) {\n this.mobileActionMenuOpen = false;\n this.mobileActionSurfaceOpen = false;\n }\n }\n\n @Watch('tools')\n handleToolsChange() {\n if (!this.resolvedToolItems.some(item => item.id === 'help' && !item.isInactive)) {\n this.managedMobilePrimaryDestination = 'area';\n }\n if (\n this.managedActiveTool &&\n !this.resolvedToolItems.some(item => item.id === this.managedActiveTool)\n ) {\n this.managedToolsOpen = false;\n this.managedActiveTool = '';\n this.managedMobileDestination = 'area';\n }\n this.managedInboxTool = resolveAvailableInboxTool(\n this.managedInboxTool,\n this.availableInboxTools\n );\n }\n\n @Watch('mobileDestination')\n @Watch('mobileSheetNavOpen')\n onMobileStateChange(_next: unknown, previous: unknown) {\n if (this.managed) {\n this.managedMobileDestination = this.mobileDestination;\n this.managedMobileSheetNavOpen = this.mobileSheetNavOpen;\n }\n this.syncSlottedMobileState();\n if (previous === true && !this.effectiveMobileSheetNavOpen && this.resolvedMode === 'mobile') {\n this.connectionTasks.frame(() => {\n const bar = this.el.querySelector('ds-mobile-bar-nav') as\n | (HTMLElement & {\n focusDestination?: (destination: MobileDestination | 'sheet-nav') => Promise<void>;\n })\n | null;\n const activeElement = document.activeElement;\n if (activeElement instanceof HTMLElement && bar?.contains(activeElement)) return;\n void bar?.focusDestination?.('sheet-nav');\n });\n }\n }\n\n private updateResponsiveMode() {\n if (!this.el.isConnected || typeof window === 'undefined') return;\n const next = resolveShellResponsiveMode(window.innerWidth);\n this.wideViewport = window.innerWidth >= SHELL_WIDE_DESKTOP_BREAKPOINT;\n if (next === this.resolvedMode) return;\n this.resolvedMode = next;\n this.syncSlottedMobileState();\n this.scheduleChromeSync();\n if (this.hasLoaded) this.dsResponsiveModeChange.emit({ mode: next });\n }\n\n private onChromeTransitionStart = (event: Event) => {\n const source = readChromeTransitionSource(event);\n if (source !== 'panel-nav') return;\n\n this.panelNavTransition.enter();\n const viewport = readShellViewportDimensions();\n this.cachedViewportWidth = viewport.width;\n this.cachedViewportHeight = viewport.height;\n this.syncChrome();\n };\n\n private onChromeTransitionEnd = (event: Event) => {\n const source = readChromeTransitionSource(event);\n if (source !== 'panel-nav') return;\n\n this.panelNavTransition.exit();\n if (!this.panelNavTransition.isActive) {\n this.scheduleChromeSync();\n }\n };\n\n /** Coalesce ResizeObserver bursts to one layout read per frame. */\n private scheduleChromeSync() {\n if (!this.el.isConnected) return;\n this.chromeSyncCoalescer.schedule();\n }\n\n private syncSlottedNavStyle() {\n const panel = this.el.querySelector('ds-panel-nav') as\n | (HTMLElement & { navStyle: NavChromeStyle })\n | null;\n const bar = this.el.querySelector('ds-bar-nav') as\n | (HTMLElement & { navStyle: NavChromeStyle })\n | null;\n if (panel) {\n panel.setAttribute('nav-style', this.navStyle);\n panel.navStyle = this.navStyle;\n }\n if (bar) {\n bar.setAttribute('nav-style', this.navStyle);\n bar.navStyle = this.navStyle;\n }\n }\n\n private syncSlottedMobileState() {\n const tools = this.el.querySelector('ds-shell-tools') as\n | (HTMLElement & { responsiveMode: ShellResponsiveMode })\n | null;\n const bar = this.el.querySelector('ds-mobile-bar-nav') as\n | (HTMLElement & {\n activeDestination: MobileDestination;\n sheetNavExpanded: boolean;\n })\n | null;\n const sheetNav = this.el.querySelector('ds-mobile-sheet-nav') as\n | (HTMLElement & { open: boolean })\n | null;\n\n if (tools) {\n tools.setAttribute('responsive-mode', this.resolvedMode);\n tools.responsiveMode = this.resolvedMode;\n }\n if (bar) {\n bar.activeDestination = this.effectiveMobileDestination;\n bar.sheetNavExpanded = this.effectiveMobileSheetNavOpen;\n }\n if (sheetNav) {\n sheetNav.open = this.effectiveMobileSheetNavOpen;\n }\n }\n\n private connectMetricsObserver() {\n this.resizeObserver?.disconnect();\n this.resizeObserver = new ResizeObserver(() => {\n if (this.panelNavTransition.isActive) return;\n this.scheduleChromeSync();\n });\n this.resizeObserver.observe(this.el);\n const panelWrap = this.el.querySelector('.shell-app__panel');\n if (panelWrap) this.resizeObserver.observe(panelWrap);\n }\n\n private connectViewportListeners() {\n if (typeof window === 'undefined') return;\n\n window.addEventListener('resize', this.onWindowResize, { passive: true });\n\n const visual = window.visualViewport;\n if (visual) {\n visual.addEventListener('resize', this.onVisualViewportChange, { passive: true });\n visual.addEventListener('scroll', this.onVisualViewportChange, { passive: true });\n }\n }\n\n private disconnectViewportListeners() {\n if (typeof window === 'undefined') return;\n\n window.removeEventListener('resize', this.onWindowResize);\n\n const visual = window.visualViewport;\n if (visual) {\n visual.removeEventListener('resize', this.onVisualViewportChange);\n visual.removeEventListener('scroll', this.onVisualViewportChange);\n }\n }\n\n private connectPageLifecycleListeners() {\n if (typeof window === 'undefined' || typeof document === 'undefined') return;\n document.addEventListener('visibilitychange', this.onDocumentVisibilityChange);\n window.addEventListener('pageshow', this.onPageShow);\n }\n\n private disconnectPageLifecycleListeners() {\n if (typeof window === 'undefined' || typeof document === 'undefined') return;\n document.removeEventListener('visibilitychange', this.onDocumentVisibilityChange);\n window.removeEventListener('pageshow', this.onPageShow);\n }\n\n private onDocumentVisibilityChange = () => {\n if (!document.hidden) this.refreshAfterForegroundRestore();\n };\n\n private onPageShow = (event: PageTransitionEvent) => {\n if (event.persisted) this.refreshAfterForegroundRestore();\n };\n\n private cancelForegroundRefresh() {\n if (this.foregroundRefreshTimer !== null) {\n window.clearTimeout(this.foregroundRefreshTimer);\n this.foregroundRefreshTimer = null;\n }\n this.el.classList.remove('shell-app--foreground-refresh');\n }\n\n private refreshAfterForegroundRestore() {\n if (!this.el.isConnected) return;\n this.updateResponsiveMode();\n this.syncSlottedMobileState();\n this.scheduleChromeSync();\n\n if (this.resolvedMode !== 'mobile') return;\n\n /*\n * Mobile WebKit can restore a live, correctly sized DOM without repainting\n * its suspended shell layer. Re-promote the complete mobile stage for one\n * painted frame; routed and tool owners remain mounted and keep their state.\n */\n this.cancelForegroundRefresh();\n this.el.classList.add('shell-app--foreground-refresh');\n void this.el.offsetHeight;\n // A short timer spans a real compositor commit; resumed WebKit may coalesce\n // nested animation frames before presenting either one.\n this.foregroundRefreshTimer = window.setTimeout(() => {\n this.foregroundRefreshTimer = null;\n this.el.classList.remove('shell-app--foreground-refresh');\n // WebKit otherwise may keep presenting the retired composite layer.\n void this.el.querySelector<HTMLElement>('.shell-app__main')?.offsetHeight;\n }, ShellApp.FOREGROUND_REFRESH_MS);\n }\n\n private cachePanelWidthTokens() {\n const navRoot = this.el.querySelector('ds-panel-nav .panel-nav') as HTMLElement | null;\n if (!navRoot) return;\n this.panelWidthTokens = readPanelNavWidthTokens(navRoot);\n }\n\n private applyGradientVars(target: HTMLElement, vars: Record<string, string | null>) {\n const style = target.style;\n for (const [name, value] of Object.entries(vars)) {\n if (value === null) style.removeProperty(name);\n else style.setProperty(name, value);\n }\n }\n\n private clearGradientPaintVars(targets: HTMLElement[]) {\n const keys = [SHELL_GRADIENT_IMAGE_VAR, SHELL_GRADIENT_SIZE_VAR, SHELL_GRADIENT_OPACITY_VAR];\n for (const target of targets) {\n for (const key of keys) target.style.removeProperty(key);\n }\n }\n\n /** Panel/bar wash offsets for scroll fades and badge rings. */\n private syncChromeLayoutVars(\n panel: HTMLElement | null,\n bar: HTMLElement | null,\n panelPosition: string,\n barPosition: string\n ) {\n if (panel) {\n panel.style.setProperty(SHELL_GRADIENT_POSITION_PANEL_VAR, panelPosition);\n }\n if (bar) {\n bar.style.setProperty(SHELL_GRADIENT_POSITION_BAR_VAR, barPosition);\n }\n }\n\n private resolvePanelWidthPx(panelNav: HTMLElement | null): number {\n const navRoot = panelNav?.querySelector('.panel-nav') as HTMLElement | null;\n\n if (this.panelNavTransition.isActive && this.panelWidthTokens.expandedPx > 0) {\n const collapsed = isPanelNavCollapsed(panelNav, navRoot);\n return panelWidthPxFromTokens(this.panelWidthTokens, collapsed);\n }\n\n const panelWrap = this.el.querySelector('.shell-app__panel') as HTMLElement | null;\n const measured = panelWrap?.getBoundingClientRect().width ?? 0;\n if (measured > 0) return measured;\n\n if (this.panelWidthTokens.expandedPx > 0) {\n const collapsed = isPanelNavCollapsed(panelNav, navRoot);\n return panelWidthPxFromTokens(this.panelWidthTokens, collapsed);\n }\n\n return 0;\n }\n\n /** Fixed-attachment wash/grid size — always the viewport, never the shell element box. */\n private resolveViewportDimensions(): { width: number; height: number } {\n if (this.panelNavTransition.isActive && this.cachedViewportWidth > 0) {\n return {\n width: this.cachedViewportWidth,\n height: this.cachedViewportHeight,\n };\n }\n\n return readShellViewportDimensions();\n }\n\n private chromeLayerActive(): boolean {\n return this.gradientLayerActive() || this.paperTextureLayerActive();\n }\n\n private gradientLayerActive(): boolean {\n return (\n this.resolvedMode !== 'mobile' && normalizeShellGradientPreset(this.gradientPreset) !== 'none'\n );\n }\n\n private paperTextureLayerActive(): boolean {\n return this.resolvedMode !== 'mobile' && this.paperTexture != null;\n }\n\n @Listen('keydown', { target: 'window', capture: true })\n handleWindowKeyDown(e: KeyboardEvent) {\n if (!this.shortcutsEnabled) return;\n if (isEditableShortcutTarget(e.target)) return;\n\n const action = resolveShellShortcut(e);\n if (!action) return;\n\n e.preventDefault();\n\n const panel = this.el.querySelector('ds-panel-nav') as HTMLDsPanelNavElement | null;\n const tools = this.el.querySelector('ds-shell-tools, ds-panel-tools') as\n | (HTMLElement & {\n closeDrawer?: () => Promise<void>;\n activateTool?: (id: PanelToolsToolId) => Promise<void>;\n })\n | null;\n\n if (action === 'toggle-panel-nav') {\n void panel?.toggleCollapsed();\n return;\n }\n\n if (action === 'close-panel-tools') {\n void tools?.closeDrawer?.();\n return;\n }\n\n if (action.startsWith('open-tool:') && tools) {\n const toolId = action.slice('open-tool:'.length) as PanelToolsToolId;\n void tools.activateTool?.(toolId);\n }\n }\n\n /** Open or toggle a managed global tool from application-owned UI. */\n @Method()\n async activateTool(id: PanelToolsToolId) {\n const tools = this.el.querySelector('ds-shell-tools, ds-panel-tools') as\n | (HTMLElement & { activateTool?: (tool: PanelToolsToolId) => Promise<void> })\n | null;\n await tools?.activateTool?.(id);\n }\n\n /** Close whichever managed global surface is currently visible. */\n @Method()\n async closeGlobalSurface() {\n if (this.managedMobileSheetNavOpen) {\n this.managedMobileSheetNavOpen = false;\n return;\n }\n const tools = this.el.querySelector('ds-shell-tools, ds-panel-tools') as\n | (HTMLElement & { closeDrawer?: () => Promise<void> })\n | null;\n await tools?.closeDrawer?.();\n }\n\n /** Request drawer or fullscreen presentation for the active managed tool. */\n @Method()\n async setToolPresentation(presentation: 'drawer' | 'fullscreen') {\n this.managedToolPresentation = presentation;\n this.toolsFullscreen = presentation === 'fullscreen';\n }\n\n private handleManagedNavSelect = (event: CustomEvent<string>) => {\n event.stopPropagation();\n this.dsNavSelect.emit(event.detail);\n };\n\n private handleManagedNavChildSelect = (event: CustomEvent<PanelNavChildSelectDetail>) => {\n event.stopPropagation();\n this.dsNavChildSelect.emit(event.detail);\n };\n\n private handleManagedTabChange = (event: CustomEvent<string>) => {\n event.stopPropagation();\n this.dsTabChange.emit(event.detail);\n };\n\n private handleManagedPageBack = (event: CustomEvent<MouseEvent>) => {\n event.stopPropagation();\n this.dsPageBack.emit(event.detail);\n };\n\n private handleManagedBreadcrumbSelect = (event: CustomEvent<BreadcrumbSelectDetail>) => {\n event.stopPropagation();\n const forwarded = this.dsBreadcrumbSelect.emit(event.detail);\n if (forwarded.defaultPrevented) event.preventDefault();\n };\n\n private handleManagedSubsectionChange = (event: CustomEvent<string>) => {\n event.stopPropagation();\n this.dsSubsectionChange.emit(event.detail);\n };\n\n private handleManagedPageAction = (event: CustomEvent<string>) => {\n event.stopPropagation();\n this.dsPageAction.emit(event.detail);\n };\n\n private handleManagedToolChange = (\n event: CustomEvent<{ id: PanelToolsToolId; selected: boolean }>\n ) => {\n event.stopPropagation();\n const { id, selected } = event.detail;\n this.managedActiveTool = id;\n this.managedToolsOpen = selected;\n const item = this.resolvedToolItems.find(candidate => candidate.id === id);\n if (item && itemUsesShellInbox(item)) {\n this.managedInboxTool = id;\n }\n this.managedMobileDestination = selected\n ? (item?.mobileDestination ?? shellMobileDestinationForTool(true, id))\n : 'area';\n this.managedMobileSheetNavOpen = false;\n this.dsToolChange.emit(event.detail);\n };\n\n private handleManagedPresentationChange = (\n event: CustomEvent<{ presentation: 'drawer' | 'fullscreen' }>\n ) => {\n event.stopPropagation();\n this.managedToolPresentation = event.detail.presentation;\n this.toolsFullscreen = event.detail.presentation === 'fullscreen';\n this.dsPresentationChange.emit(event.detail);\n };\n\n private handleManagedHeaderBack = (event: CustomEvent<{ tool: PanelToolsToolId }>) => {\n event.stopPropagation();\n this.dsHeaderBack.emit(event.detail);\n };\n\n private handleManagedHeaderAction = (event: CustomEvent<PanelToolsHeaderActionDetail>) => {\n event.stopPropagation();\n this.dsHeaderAction.emit(event.detail);\n };\n\n private handleManagedRailAccessoryAction = (\n event: CustomEvent<PanelToolsRailAccessoryActionDetail>\n ) => {\n event.stopPropagation();\n this.dsRailAccessoryAction.emit(event.detail);\n };\n\n private handleManagedSheetToggle = (event: CustomEvent<boolean>) => {\n event.stopPropagation();\n this.managedMobileSheetNavOpen = event.detail;\n };\n\n private activateManagedMobileTool(destination: Exclude<MobileDestination, 'area'>): boolean {\n const canonicalId =\n destination === 'inbox'\n ? resolveAvailableInboxTool(this.managedInboxTool, this.availableInboxTools)\n : destination;\n if (!canonicalId) return false;\n\n const item = this.resolvedToolItems.find(\n candidate =>\n candidate.id === canonicalId ||\n (destination !== 'inbox' && candidate.mobileDestination === destination)\n );\n if (!item || item.isInactive) return false;\n\n const id = item.id;\n this.managedActiveTool = id;\n this.managedToolsOpen = true;\n if (itemUsesShellInbox(item)) this.managedInboxTool = id;\n if (destination === 'help') this.managedMobilePrimaryDestination = 'help';\n this.dsToolChange.emit({ id: id as PanelToolsToolId, selected: true });\n return true;\n }\n\n private handleManagedMobileDestination = (event: CustomEvent<MobileBarNavDestinationDetail>) => {\n event.stopPropagation();\n const destination = event.detail.destination;\n this.managedMobileSheetNavOpen = false;\n this.managedMobileDestination = destination;\n\n if (destination === 'area') {\n this.managedToolsOpen = false;\n return;\n }\n\n this.activateManagedMobileTool(destination);\n };\n\n private handleManagedAreaSelect = (event: CustomEvent<string>) => {\n event.stopPropagation();\n if (event.detail === 'help') {\n this.managedMobileSheetNavOpen = false;\n this.managedMobileDestination = 'help';\n if (this.activateManagedMobileTool('help')) return;\n }\n\n this.managedMobileSheetNavOpen = false;\n this.managedMobileDestination = 'area';\n this.managedMobilePrimaryDestination = 'area';\n this.managedToolsOpen = false;\n this.dsNavSelect.emit(event.detail);\n };\n\n private handleManagedMobileChildSelect = (event: CustomEvent<PanelNavChildSelectDetail>) => {\n event.stopPropagation();\n this.managedMobileSheetNavOpen = false;\n this.managedMobileDestination = 'area';\n this.managedMobilePrimaryDestination = 'area';\n this.managedToolsOpen = false;\n this.dsNavChildSelect.emit(event.detail);\n };\n\n private handleManagedBrowseContext = (event: CustomEvent<NavChromeStyle>) => {\n event.stopPropagation();\n this.managedBrowseContext = event.detail;\n this.dsBrowseContextChange.emit(event.detail);\n };\n\n private handleManagedSheetClose = (event: CustomEvent<void>) => {\n event.stopPropagation();\n this.managedMobileSheetNavOpen = false;\n };\n\n private handleManagedNavFooterAction = (event: CustomEvent<void>) => {\n event.stopPropagation();\n this.dsNavFooterAction.emit();\n };\n\n private handleManagedNavUserAction = (event: CustomEvent<PanelNavUserActionDetail>) => {\n event.stopPropagation();\n this.dsNavUserAction.emit(event.detail);\n };\n\n private get currentArea(): PanelNavItem {\n const groups = [\n ...(this.navigation.dashboardGroups ?? []),\n ...(this.navigation.settingsGroups ?? []),\n ...this.resolvedNavigationGroups,\n ];\n const items = groups.flatMap(group => group.items);\n const activeId =\n this.navigation.activeId ||\n deriveActiveIdFromUrl(this.navigation.currentUrl ?? this.pageChrome.currentUrl ?? '', items);\n return (\n items.find(item => item.id === activeId) ??\n items[0] ?? {\n id: 'area',\n icon: 'MapPage',\n label: this.pageChrome.heading || 'Area',\n }\n );\n }\n\n private toolDot(id: PanelToolsToolId): boolean {\n return this.resolvedToolItems.find(item => item.id === id)?.dot ?? false;\n }\n\n private syncChrome() {\n if (!this.el.isConnected) return;\n const panelNav = this.el.querySelector('ds-panel-nav') as HTMLElement | null;\n const bar = this.el.querySelector(\n 'ds-bar-nav, ds-bar-page-title, ds-bar-title[placement=\"shell-bar\"]'\n ) as HTMLElement | null;\n const targets = [this.el, panelNav, bar].filter((el): el is HTMLElement => el !== null);\n\n const clearLayoutVars = () => {\n if (panelNav) panelNav.style.removeProperty(SHELL_GRADIENT_POSITION_PANEL_VAR);\n if (bar) bar.style.removeProperty(SHELL_GRADIENT_POSITION_BAR_VAR);\n };\n\n const preset = normalizeShellGradientPreset(this.gradientPreset);\n if (this.resolvedMode === 'mobile') {\n this.clearGradientPaintVars(targets);\n clearLayoutVars();\n return;\n }\n\n const viewport = this.resolveViewportDimensions();\n if (preset === 'none') {\n this.clearGradientPaintVars(targets);\n clearLayoutVars();\n this.syncPaperTextureLayout(viewport);\n return;\n }\n\n const panelWidth = this.resolvePanelWidthPx(panelNav);\n\n const panelPosition = shellGradientPositionPanel();\n const barPosition = shellGradientPositionBar(panelWidth);\n\n this.syncChromeLayoutVars(panelNav, bar, panelPosition, barPosition);\n\n const image = buildShellRadialGradient(preset);\n\n const size = shellGradientSize({\n width: viewport.width,\n height: viewport.height,\n });\n const opacity = shellGradientPresetOpacity(preset);\n\n for (const target of targets) {\n if (target === this.el) {\n this.applyGradientVars(target, {\n [SHELL_GRADIENT_IMAGE_VAR]: image,\n [SHELL_GRADIENT_SIZE_VAR]: size,\n [SHELL_GRADIENT_POSITION_PANEL_VAR]: panelPosition,\n [SHELL_GRADIENT_POSITION_BAR_VAR]: barPosition,\n [SHELL_GRADIENT_OPACITY_VAR]: opacity,\n });\n continue;\n }\n\n const isBar = target === bar;\n this.applyGradientVars(target, {\n [SHELL_GRADIENT_IMAGE_VAR]: image,\n [SHELL_GRADIENT_SIZE_VAR]: size,\n [SHELL_GRADIENT_OPACITY_VAR]: opacity,\n [SHELL_GRADIENT_POSITION_PANEL_VAR]: isBar ? null : panelPosition,\n [SHELL_GRADIENT_POSITION_BAR_VAR]: isBar ? barPosition : null,\n });\n }\n\n this.syncPaperTextureLayout(viewport);\n }\n\n private syncPaperTextureLayout(viewport: { width: number; height: number }) {\n const chrome = this.el.querySelector<HTMLElement>('.shell-app__chrome');\n const paperTexture = this.el.querySelector<HTMLElement>(\n '.shell-app__chrome > ds-paper-texture'\n );\n if (!chrome || !paperTexture) return;\n\n const chromeRect = chrome.getBoundingClientRect();\n paperTexture.style.setProperty('width', `${Math.round(viewport.width)}px`);\n paperTexture.style.setProperty('height', `${Math.round(viewport.height)}px`);\n paperTexture.style.setProperty('left', `${-Math.round(chromeRect.left)}px`);\n paperTexture.style.setProperty('top', `${-Math.round(chromeRect.top)}px`);\n }\n\n private renderPaperTexture() {\n if (!this.paperTextureLayerActive()) return null;\n return <ds-paper-texture class=\"shell-app__paper-texture\" config={this.paperTexture} />;\n }\n\n private renderManagedPanelNav() {\n const navigation = this.navigation;\n return (\n <ds-panel-nav\n navStyle={this.navStyle}\n presentation={this.sectionNavigation === 'panel' ? 'nested' : 'flat'}\n groups={this.resolvedNavigationGroups}\n breakpoint={SHELL_DESKTOP_BREAKPOINT}\n routerMode={navigation.routerMode ?? 'event'}\n activeId={navigation.activeId ?? ''}\n activeChildId={this.pageChrome.value ?? ''}\n currentUrl={navigation.currentUrl ?? this.pageChrome.currentUrl ?? ''}\n storageKey={navigation.storageKey ?? ''}\n userName={navigation.userName ?? ''}\n userInitial={navigation.userInitial ?? ''}\n accountMenuExpanded={navigation.accountMenuExpanded ?? false}\n accountMenuSurfaceOpen={navigation.accountMenuSurfaceOpen}\n dashboardLabel={navigation.dashboardLabel ?? 'Dashboard'}\n settingsLabel={navigation.settingsLabel ?? 'Settings'}\n accountLabel={navigation.accountLabel ?? 'Account'}\n dashboardNavigationLabel={navigation.dashboardNavigationLabel ?? 'Dashboard navigation'}\n settingsNavigationLabel={navigation.settingsNavigationLabel ?? 'Settings navigation'}\n navigationItemsLabel={navigation.navigationLabel ?? 'Navigation items'}\n onDsNavSelect={this.handleManagedNavSelect}\n onDsNavChildSelect={this.handleManagedNavChildSelect}\n onDsNavFooterAction={this.handleManagedNavFooterAction}\n onDsNavUserAction={this.handleManagedNavUserAction}\n />\n );\n }\n\n private renderManagedBarNav() {\n return (\n <ds-bar-nav\n navStyle={this.navStyle}\n tabs={this.resolvedPageTabs}\n value={this.pageChrome.value ?? ''}\n heading={this.pageChrome.routeHeading ?? this.pageChrome.heading}\n basePath={this.pageChrome.basePath ?? ''}\n currentUrl={this.pageChrome.currentUrl ?? this.navigation.currentUrl ?? ''}\n onDsTabChange={this.handleManagedTabChange}\n />\n );\n }\n\n private renderManagedTopBar() {\n return this.sectionNavigation === 'panel'\n ? this.renderManagedBarPageTitle()\n : this.renderManagedBarNav();\n }\n\n private renderManagedToolSlots() {\n const ids = [...new Set(this.resolvedToolItems.map(item => item.id))].sort();\n return ids.flatMap(id => [\n <div key={`tool:${id}`} class=\"shell-app__tool-slot-proxy\" slot={id}>\n <slot name={id} />\n </div>,\n <div key={`tool-view:${id}`} class=\"shell-app__tool-slot-proxy\" slot={`${id}-view`}>\n <slot name={`${id}-view`} />\n </div>,\n ]);\n }\n\n private renderManagedTools() {\n const tools = this.tools;\n return (\n <ds-shell-tools\n responsiveMode={this.resolvedMode}\n open={this.managedToolsOpen}\n activeTool={this.managedActiveTool}\n presentation={this.managedToolPresentation}\n fullscreenHeaderMode={tools.fullscreenHeaderMode ?? 'shared'}\n items={this.resolvedToolItems}\n accessories={tools.accessories ?? []}\n headers={tools.headers ?? {}}\n storageKey={tools.storageKey ?? ''}\n toolsLabel={tools.toolsLabel ?? 'Tools'}\n toolShortcutsLabel={tools.toolShortcutsLabel ?? 'Tool shortcuts'}\n inboxLabel={tools.inboxLabel ?? 'Inbox'}\n inboxNavigationLabel={tools.inboxNavigationLabel ?? 'Inbox sections'}\n onDsToolChange={this.handleManagedToolChange}\n onDsPresentationChange={this.handleManagedPresentationChange}\n onDsHeaderBack={this.handleManagedHeaderBack}\n onDsHeaderAction={this.handleManagedHeaderAction}\n onDsRailAccessoryAction={this.handleManagedRailAccessoryAction}\n >\n {this.renderManagedToolSlots()}\n </ds-shell-tools>\n );\n }\n\n private renderManagedMobileSheetNav() {\n const navigation = this.navigation;\n return (\n <ds-mobile-sheet-nav\n open={this.managedMobileSheetNavOpen}\n presentation={this.sectionNavigation === 'panel' ? 'nested' : 'flat'}\n browseContext={this.managedBrowseContext}\n dashboardGroups={navigation.dashboardGroups ?? navigation.groups ?? []}\n settingsGroups={navigation.settingsGroups ?? []}\n currentUrl={navigation.currentUrl ?? this.pageChrome.currentUrl ?? ''}\n routeSelectionActive={this.managedMobileDestination === 'area'}\n navigationLabel={navigation.navigationLabel ?? 'Application navigation'}\n dashboardLabel={navigation.dashboardLabel ?? 'Dashboard'}\n settingsLabel={navigation.settingsLabel ?? 'Settings'}\n accountLabel={navigation.accountLabel ?? 'Account'}\n showAccount={navigation.showMobileAccount ?? true}\n onDsAreaSelect={this.handleManagedAreaSelect}\n onDsNavChildSelect={this.handleManagedMobileChildSelect}\n onDsBrowseContextChange={this.handleManagedBrowseContext}\n onDsClose={this.handleManagedSheetClose}\n />\n );\n }\n\n private renderManagedMobileBarNav() {\n const item = (id: PanelToolsToolId) =>\n this.resolvedToolItems.find(candidate => candidate.id === id);\n const label = (id: PanelToolsToolId, fallback: string) =>\n item(id)?.ariaLabel ?? item(id)?.label ?? fallback;\n const directActivity = this.resolvedToolItems.some(\n candidate => !candidate.isInactive && candidate.mobileDestination === 'activity'\n );\n\n return (\n <ds-mobile-bar-nav\n hidden={this.tools.mobileBarHidden ?? false}\n activeDestination={this.managedMobileDestination}\n primaryDestination={this.managedMobilePrimaryDestination}\n currentArea={this.currentArea}\n sheetNavExpanded={this.managedMobileSheetNavOpen}\n searchLabel={label('search', 'Search')}\n activityMode={directActivity ? 'direct' : 'inbox'}\n activityLabel={label('activity', 'Activity')}\n inboxLabel={this.tools.inboxLabel ?? 'Inbox'}\n messagesLabel={label('messages', 'Messages')}\n agentsLabel={label('agents', 'Agents')}\n helpLabel={label('help', 'Help & Support')}\n searchDot={this.toolDot('search')}\n activityDot={this.toolDot('activity')}\n inboxDot={this.toolDot('stacks') || this.toolDot('activity')}\n messagesDot={this.toolDot('messages')}\n agentsDot={this.toolDot('agents')}\n onDsSheetNavToggle={this.handleManagedSheetToggle}\n onDsDestinationChange={this.handleManagedMobileDestination}\n />\n );\n }\n\n private renderManagedMobileLeading() {\n if (!this.pageChrome.showBack) {\n return <slot name=\"page-header-leading\" slot=\"leading\" />;\n }\n return [\n <ds-tooltip\n slot=\"leading\"\n label={this.pageChrome.backAriaLabel ?? 'Back'}\n side=\"bottom\"\n size=\"sm\"\n >\n <ds-button-unfilled\n variant=\"icon\"\n icon={this.pageChrome.backIcon ?? 'ChevronLeft'}\n size=\"md\"\n aria-label={this.pageChrome.backAriaLabel ?? 'Back'}\n activeFill={false}\n hasBorder={false}\n onDsClick={(event: CustomEvent<MouseEvent>) => this.dsPageBack.emit(event.detail)}\n />\n </ds-tooltip>,\n <slot name=\"page-header-leading\" slot=\"leading\" />,\n ];\n }\n\n private renderManagedMobileActions() {\n const legacyActions = this.pageChrome.mobileActions ?? [];\n const visibleActions = visibleBarTitleActions(this.resolvedHeaderActions, 'mobile');\n return [\n ...legacyActions.map((action: PanelToolsHeaderAction) => (\n <ds-tooltip\n slot=\"trailing\"\n key={action.id}\n label={action.ariaLabel}\n side=\"bottom\"\n size=\"sm\"\n >\n <ds-button-unfilled\n id={action.triggerId || undefined}\n variant=\"icon\"\n icon={action.icon}\n size=\"md\"\n aria-label={action.ariaLabel}\n haspopup={action.haspopup}\n controls={action.controls}\n expanded={action.expanded}\n surfaceOpen={action.surfaceOpen}\n pressed={action.pressed}\n isInactive={action.isInactive}\n activeFill={false}\n hasBorder={false}\n onDsClick={() => this.dsPageAction.emit(action.id)}\n />\n </ds-tooltip>\n )),\n ...visibleActions.map(action => {\n if (action.type !== 'icon') return null;\n const button =\n (action.appearance ?? 'unfilled') === 'filled' ? (\n <ds-button-filled\n variant=\"icon\"\n icon={action.icon}\n aria-label={action.ariaLabel}\n size=\"md\"\n intent={action.intent ?? 'brand'}\n contrast={action.contrast ?? 'bold'}\n isInactive={action.isInactive}\n isLoading={action.isLoading}\n onDsClick={() => this.dsPageAction.emit(action.id)}\n />\n ) : (\n <ds-button-unfilled\n variant=\"icon\"\n icon={action.icon}\n aria-label={action.ariaLabel}\n size=\"md\"\n isInactive={action.isInactive}\n isLoading={action.isLoading}\n activeFill={false}\n hasBorder={false}\n onDsClick={() => this.dsPageAction.emit(action.id)}\n />\n );\n return (\n <ds-tooltip slot=\"trailing\" key={action.id} label={action.label} side=\"bottom\" size=\"sm\">\n {button}\n </ds-tooltip>\n );\n }),\n ...(this.mobileActionMenuSections.length > 0\n ? [\n <ds-tooltip slot=\"trailing\" label=\"Page options\" side=\"bottom\" size=\"sm\">\n <ds-button-unfilled\n ref={el => {\n this.mobileActionTriggerEl = el ?? null;\n }}\n id={this.mobileActionMenuTriggerId}\n variant=\"icon\"\n icon=\"Ellipses\"\n aria-label={this.pageChrome.actionsAriaLabel ?? 'More page actions'}\n size=\"md\"\n activeFill={false}\n hasBorder={false}\n haspopup=\"menu\"\n controls={this.mobileActionMenuId}\n expanded={this.mobileActionMenuOpen}\n surfaceOpen={this.mobileActionSurfaceOpen}\n onDsClick={(event: CustomEvent<MouseEvent>) => {\n this.mobileActionMenuInitialFocusVisible = event.detail.detail === 0;\n this.mobileActionMenuOpen = !this.mobileActionMenuOpen;\n if (this.mobileActionMenuOpen) this.mobileActionSurfaceOpen = true;\n }}\n />\n </ds-tooltip>,\n ]\n : []),\n <slot name=\"page-header-trailing\" slot=\"trailing\" />,\n ];\n }\n\n private get resolvedHeaderActions(): BarTitleActionConfigItem[] {\n return resolveBarTitleActionItems(\n this.pageChrome.actionItems,\n this.pageChrome.primaryAction ?? null,\n this.pageChrome.actions ?? []\n );\n }\n\n private get mobileActionMenuSections() {\n return overflowBarTitleActionSections(this.resolvedHeaderActions, 'mobile');\n }\n\n private closeMobileActionMenu = () => {\n this.mobileActionMenuOpen = false;\n };\n\n private handleMobileActionSelect = (event: CustomEvent<MenuItemData>) => {\n const id = String(event.detail.value ?? '');\n const action = findBarTitleAction(this.resolvedHeaderActions, id);\n if (!action || action.isInactive || ('isLoading' in action && action.isLoading)) return;\n this.mobileActionMenuOpen = false;\n this.dsPageAction.emit(id);\n this.connectionTasks.frame(() => void this.mobileActionTriggerEl?.setFocus?.());\n };\n\n private renderManagedMobileActionMenu() {\n if (this.mobileActionMenuSections.length === 0) return null;\n return (\n <ds-menu\n id={this.mobileActionMenuId}\n anchorId={this.mobileActionMenuTriggerId}\n align=\"end\"\n menuLabel={this.pageChrome.actionsAriaLabel ?? 'More page actions'}\n open={this.mobileActionMenuOpen}\n initialFocusVisible={this.mobileActionMenuInitialFocusVisible}\n sections={this.mobileActionMenuSections}\n onDsClose={this.closeMobileActionMenu}\n onDsAfterClose={() => {\n if (!this.mobileActionMenuOpen) this.mobileActionSurfaceOpen = false;\n }}\n onDsSelect={this.handleMobileActionSelect}\n />\n );\n }\n\n private get barTitleSections(): BarTitleSectionItem[] {\n return (this.pageChrome.subsections ?? []).map(item =>\n 'type' in item\n ? { type: 'divider' }\n : {\n id: item.id,\n label: item.label,\n isInactive: item.isInactive,\n }\n );\n }\n\n private renderManagedBarPageTitle() {\n const page = this.pageChrome;\n return (\n <ds-bar-page-title\n data-shell-bar=\"\"\n ref={el => {\n if (el) {\n (el as HTMLElement & { actionItems?: BarTitleActionConfigItem[] }).actionItems =\n page.actionItems;\n }\n }}\n heading={page.heading ?? ''}\n showBack={page.showBack ?? false}\n backAriaLabel={page.backAriaLabel ?? 'Back'}\n sections={this.barTitleSections}\n value={page.subvalue ?? ''}\n sectionsAriaLabel={page.subsectionsAriaLabel ?? 'Change page subsection'}\n primaryAction={page.primaryAction ?? null}\n actions={page.actions ?? []}\n actionsAriaLabel={page.actionsAriaLabel ?? 'More page actions'}\n showDivider={page.showHeaderDivider ?? true}\n showCompactDivider={page.showCompactHeaderDivider}\n onDsBack={this.handleManagedPageBack}\n onDsSectionChange={this.handleManagedSubsectionChange}\n onDsAction={this.handleManagedPageAction}\n />\n );\n }\n\n private renderManagedBarTitle(placement: BarTitlePlacement) {\n const page = this.pageChrome;\n return (\n <ds-bar-title\n ref={el => {\n if (el) {\n (el as HTMLElement & { actionItems?: BarTitleActionConfigItem[] }).actionItems =\n page.actionItems;\n }\n }}\n slot={placement === 'page' ? 'header' : undefined}\n placement={placement}\n variant={placement === 'shell-bar' ? 'compact' : 'expanded'}\n heading={page.heading ?? ''}\n description={page.description ?? ''}\n showBack={page.showBack ?? false}\n backAriaLabel={page.backAriaLabel ?? 'Back'}\n backLabel={page.backLabel ?? 'Back'}\n breadcrumbs={page.breadcrumbs ?? []}\n breadcrumbAriaLabel={page.breadcrumbAriaLabel ?? 'Breadcrumb'}\n sections={this.barTitleSections}\n value={page.subvalue ?? ''}\n sectionsAriaLabel={page.subsectionsAriaLabel ?? 'Change page subsection'}\n primaryAction={page.primaryAction ?? null}\n actions={page.actions ?? []}\n actionsAriaLabel={page.actionsAriaLabel ?? 'More page actions'}\n showDivider={page.showHeaderDivider ?? true}\n showCompactDivider={page.showCompactHeaderDivider}\n onDsBack={this.handleManagedPageBack}\n onDsBreadcrumbSelect={this.handleManagedBreadcrumbSelect}\n onDsSectionChange={this.handleManagedSubsectionChange}\n onDsAction={this.handleManagedPageAction}\n />\n );\n }\n\n private renderManagedPage() {\n const page = this.pageChrome;\n const nestedNavigation = this.sectionNavigation === 'panel';\n const mobileSections = page.showBack || nestedNavigation ? [] : this.resolvedPageTabs;\n return [\n <ds-shell-page\n responsiveMode={this.resolvedMode}\n headerCapacity={resolveManagedShellPageCapacity(this.resolvedMode)}\n headerPresentation={page.headerPresentation ?? 'auto'}\n desktopHeaderPlacement={this.sectionNavigation === 'panel' ? 'shell-bar' : 'page'}\n contentInset={page.contentInset ?? 'default'}\n contentInsetBlockStart={page.contentInsetBlockStart ?? 'default'}\n contentInsetBlockStartSize={page.contentInsetBlockStartSize}\n compactContentInsetBlockStartSize={page.compactContentInsetBlockStartSize}\n scrollCompaction={page.scrollCompaction ?? true}\n contentSurface={page.contentSurface ?? 'primary'}\n >\n {this.sectionNavigation === 'bar' ? this.renderManagedBarTitle('page') : null}\n <ds-mobile-header\n slot=\"mobile-header\"\n heading={page.heading ?? ''}\n headingLevel={page.headingLevel ?? 'h1'}\n sections={mobileSections}\n value={page.showBack ? '' : (page.value ?? '')}\n sectionsAriaLabel={page.sectionsAriaLabel ?? 'Change page section'}\n subsections={page.subsections ?? []}\n subsectionsPlacement={page.showBack || nestedNavigation ? 'combined' : 'below'}\n subvalue={page.subvalue ?? ''}\n subsectionsAriaLabel={page.subsectionsAriaLabel ?? 'Change page subsection'}\n tone={page.tone ?? 'default'}\n onDsSectionChange={this.handleManagedTabChange}\n onDsSubsectionChange={this.handleManagedSubsectionChange}\n >\n {this.renderManagedMobileLeading()}\n {this.renderManagedMobileActions()}\n </ds-mobile-header>\n <slot />\n </ds-shell-page>,\n this.renderManagedMobileActionMenu(),\n ];\n }\n\n private renderSlottedShell(\n shellCls: Record<string, boolean>,\n mobile: boolean,\n mobileToolActive: boolean,\n mobileStageBlocked: boolean,\n fullscreen: boolean\n ) {\n return (\n <Host\n class={shellCls}\n responsive-mode={this.resolvedMode}\n data-wide-viewport={String(this.wideViewport)}\n >\n <div class=\"shell-app__banner\">\n <slot name=\"banner\" />\n </div>\n <div class=\"shell-app__row\">\n <div class=\"shell-app__chrome\" aria-hidden=\"true\">\n {this.renderPaperTexture()}\n </div>\n <div\n class=\"shell-app__panel\"\n aria-hidden={fullscreen ? 'true' : undefined}\n inert={fullscreen ? true : undefined}\n >\n <slot name=\"panel\" />\n </div>\n <div class=\"shell-app__main\">\n <div\n class=\"shell-app__bar\"\n aria-hidden={fullscreen ? 'true' : undefined}\n inert={fullscreen ? true : undefined}\n >\n <slot name=\"bar\" />\n </div>\n <div\n class=\"shell-app__tools\"\n data-ds-overlay-boundary={fullscreen ? undefined : true}\n aria-hidden={\n mobile && (!mobileToolActive || this.mobileSheetNavOpen) ? 'true' : undefined\n }\n inert={mobile && (!mobileToolActive || this.mobileSheetNavOpen) ? true : undefined}\n hidden={mobile && !mobileToolActive}\n >\n <slot name=\"tools\" />\n </div>\n <div class=\"shell-app__content-frame\">\n <div\n class=\"shell-app__content\"\n data-ds-overlay-boundary\n aria-hidden={fullscreen || mobileStageBlocked ? 'true' : undefined}\n inert={fullscreen || mobileStageBlocked ? true : undefined}\n tabIndex={0}\n >\n <slot />\n </div>\n </div>\n <div\n class=\"shell-app__mobile-sheet-nav\"\n hidden={!mobile || !this.mobileSheetNavOpen}\n aria-hidden={mobile && this.mobileSheetNavOpen ? undefined : 'true'}\n inert={mobile && this.mobileSheetNavOpen ? undefined : true}\n >\n <slot name=\"mobile-sheet-nav\" />\n </div>\n <div class=\"shell-app__mobile-bar-nav\" hidden={!mobile}>\n <slot name=\"mobile-bar-nav\" />\n </div>\n </div>\n </div>\n </Host>\n );\n }\n\n render() {\n const chromeActive = this.chromeLayerActive();\n const gradientActive = this.gradientLayerActive();\n const paperTextureActive = this.paperTextureLayerActive();\n const mobile = this.resolvedMode === 'mobile';\n const mobileToolActive = mobile && this.effectiveMobileDestination !== 'area';\n const mobileStageBlocked = mobile && (mobileToolActive || this.effectiveMobileSheetNavOpen);\n const fullscreen = !mobile && this.toolsFullscreen;\n const shellCls: Record<string, boolean> = {\n 'shell-app': true,\n 'shell-app--chrome': chromeActive,\n 'shell-app--gradient': gradientActive,\n 'shell-app--paper-texture': paperTextureActive,\n [`shell-app--${this.navStyle}`]: true,\n [`shell-app--${this.resolvedMode}`]: true,\n 'shell-app--tools-fullscreen': fullscreen,\n 'shell-app--mobile-tool-active': mobileToolActive,\n 'shell-app--mobile-sheet-nav-open': mobile && this.effectiveMobileSheetNavOpen,\n 'shell-app--managed': this.managed,\n 'shell-app--slotted': !this.managed,\n };\n\n if (!this.managed) {\n return this.renderSlottedShell(\n shellCls,\n mobile,\n mobileToolActive,\n mobileStageBlocked,\n fullscreen\n );\n }\n\n return (\n <Host\n class={shellCls}\n responsive-mode={this.resolvedMode}\n data-wide-viewport={String(this.wideViewport)}\n >\n <div class=\"shell-app__banner\">\n <slot name=\"banner\" />\n </div>\n <div class=\"shell-app__row\">\n <div class=\"shell-app__chrome\" aria-hidden=\"true\">\n {this.renderPaperTexture()}\n </div>\n <div\n class=\"shell-app__panel\"\n aria-hidden={fullscreen ? 'true' : undefined}\n inert={fullscreen ? true : undefined}\n >\n {this.renderManagedPanelNav()}\n </div>\n <div class=\"shell-app__main\">\n <div\n class=\"shell-app__bar\"\n aria-hidden={fullscreen ? 'true' : undefined}\n inert={fullscreen ? true : undefined}\n >\n {this.renderManagedTopBar()}\n </div>\n <div\n class=\"shell-app__tools\"\n data-ds-overlay-boundary={fullscreen ? undefined : true}\n aria-hidden={\n mobile && (!mobileToolActive || this.effectiveMobileSheetNavOpen)\n ? 'true'\n : undefined\n }\n inert={\n mobile && (!mobileToolActive || this.effectiveMobileSheetNavOpen) ? true : undefined\n }\n hidden={mobile && !mobileToolActive}\n >\n {this.renderManagedTools()}\n </div>\n <div class=\"shell-app__content-frame\">\n <div\n class=\"shell-app__content\"\n data-ds-overlay-boundary\n aria-hidden={fullscreen || mobileStageBlocked ? 'true' : undefined}\n inert={fullscreen || mobileStageBlocked ? true : undefined}\n tabIndex={0}\n >\n {this.renderManagedPage()}\n </div>\n </div>\n <div\n class=\"shell-app__mobile-sheet-nav\"\n hidden={!mobile || !this.effectiveMobileSheetNavOpen}\n aria-hidden={mobile && this.effectiveMobileSheetNavOpen ? undefined : 'true'}\n inert={mobile && this.effectiveMobileSheetNavOpen ? undefined : true}\n >\n {this.renderManagedMobileSheetNav()}\n </div>\n <div class=\"shell-app__mobile-bar-nav\" hidden={!mobile}>\n {this.renderManagedMobileBarNav()}\n </div>\n </div>\n </div>\n </Host>\n );\n }\n}\n",
|
|
789
791
|
"type": "registry:ui"
|
|
790
792
|
},
|
|
791
793
|
{
|
|
@@ -44,7 +44,9 @@ export { DsDataSavedViews } from "./ds-data-saved-views.js";
|
|
|
44
44
|
export { DsDataSearch } from "./ds-data-search.js";
|
|
45
45
|
export { DsDataSort } from "./ds-data-sort.js";
|
|
46
46
|
export { DsDataToolbar } from "./ds-data-toolbar.js";
|
|
47
|
+
export { DsDisclosure } from "./ds-disclosure.js";
|
|
47
48
|
export { DsDivider } from "./ds-divider.js";
|
|
49
|
+
export { DsDrawer } from "./ds-drawer.js";
|
|
48
50
|
export { DsEmptyState } from "./ds-empty-state.js";
|
|
49
51
|
export { DsField } from "./ds-field.js";
|
|
50
52
|
export { DsFilterMenu } from "./ds-filter-menu.js";
|
|
@@ -77,6 +79,7 @@ export { DsPanelToolHeader } from "./ds-panel-tool-header.js";
|
|
|
77
79
|
export { DsPanelToolSearch } from "./ds-panel-tool-search.js";
|
|
78
80
|
export { DsPanelTools } from "./ds-panel-tools.js";
|
|
79
81
|
export { DsPaperTexture } from "./ds-paper-texture.js";
|
|
82
|
+
export { DsPopover } from "./ds-popover.js";
|
|
80
83
|
export { DsRadioTile } from "./ds-radio-tile.js";
|
|
81
84
|
export { DsRadio } from "./ds-radio.js";
|
|
82
85
|
export { DsScore } from "./ds-score.js";
|
package/dist/react/components.js
CHANGED
|
@@ -45,7 +45,9 @@ export { DsDataSavedViews } from "./ds-data-saved-views.js";
|
|
|
45
45
|
export { DsDataSearch } from "./ds-data-search.js";
|
|
46
46
|
export { DsDataSort } from "./ds-data-sort.js";
|
|
47
47
|
export { DsDataToolbar } from "./ds-data-toolbar.js";
|
|
48
|
+
export { DsDisclosure } from "./ds-disclosure.js";
|
|
48
49
|
export { DsDivider } from "./ds-divider.js";
|
|
50
|
+
export { DsDrawer } from "./ds-drawer.js";
|
|
49
51
|
export { DsEmptyState } from "./ds-empty-state.js";
|
|
50
52
|
export { DsField } from "./ds-field.js";
|
|
51
53
|
export { DsFilterMenu } from "./ds-filter-menu.js";
|
|
@@ -78,6 +80,7 @@ export { DsPanelToolHeader } from "./ds-panel-tool-header.js";
|
|
|
78
80
|
export { DsPanelToolSearch } from "./ds-panel-tool-search.js";
|
|
79
81
|
export { DsPanelTools } from "./ds-panel-tools.js";
|
|
80
82
|
export { DsPaperTexture } from "./ds-paper-texture.js";
|
|
83
|
+
export { DsPopover } from "./ds-popover.js";
|
|
81
84
|
export { DsRadioTile } from "./ds-radio-tile.js";
|
|
82
85
|
export { DsRadio } from "./ds-radio.js";
|
|
83
86
|
export { DsScore } from "./ds-score.js";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file was automatically generated by the Stencil React Output Target.
|
|
3
|
+
* Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
|
|
4
|
+
*/
|
|
5
|
+
import type { EventName, StencilReactComponent } from './react-runtime.js';
|
|
6
|
+
import { type DsDisclosureCustomEvent } from "@ds-mo/ui";
|
|
7
|
+
import type { Components } from "@ds-mo/ui/dist/components";
|
|
8
|
+
import { DsDisclosure as DsDisclosureElement } from "@ds-mo/ui/dist/components/ds-disclosure.js";
|
|
9
|
+
export type DsDisclosureEvents = {
|
|
10
|
+
onDsToggle: EventName<DsDisclosureCustomEvent<boolean>>;
|
|
11
|
+
};
|
|
12
|
+
export declare const DsDisclosure: StencilReactComponent<DsDisclosureElement, DsDisclosureEvents, Components.DsDisclosure, 'label'>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { createComponent } from './react-runtime.js';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { DsDisclosure as DsDisclosureElement, defineCustomElement as defineDsDisclosure } from "@ds-mo/ui/dist/components/ds-disclosure.js";
|
|
5
|
+
export const DsDisclosure = /*@__PURE__*/ createComponent({
|
|
6
|
+
tagName: 'ds-disclosure',
|
|
7
|
+
elementClass: DsDisclosureElement,
|
|
8
|
+
// @ts-ignore - ignore potential React type mismatches between the Stencil Output Target and your project.
|
|
9
|
+
react: React,
|
|
10
|
+
events: { onDsToggle: 'dsToggle' },
|
|
11
|
+
defineCustomElement: defineDsDisclosure
|
|
12
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file was automatically generated by the Stencil React Output Target.
|
|
3
|
+
* Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
|
|
4
|
+
*/
|
|
5
|
+
import type { EventName, StencilReactComponent } from './react-runtime.js';
|
|
6
|
+
import { type DrawerAfterCloseDetail, type DrawerCloseDetail, type DsDrawerCustomEvent } from "@ds-mo/ui";
|
|
7
|
+
import type { Components } from "@ds-mo/ui/dist/components";
|
|
8
|
+
import { DsDrawer as DsDrawerElement } from "@ds-mo/ui/dist/components/ds-drawer.js";
|
|
9
|
+
export type DsDrawerEvents = {
|
|
10
|
+
onDsClose: EventName<DsDrawerCustomEvent<DrawerCloseDetail>>;
|
|
11
|
+
onDsAfterClose: EventName<DsDrawerCustomEvent<DrawerAfterCloseDetail>>;
|
|
12
|
+
onDsBack: EventName<DsDrawerCustomEvent<MouseEvent>>;
|
|
13
|
+
onDsPrevious: EventName<DsDrawerCustomEvent<void>>;
|
|
14
|
+
onDsNext: EventName<DsDrawerCustomEvent<void>>;
|
|
15
|
+
};
|
|
16
|
+
export declare const DsDrawer: StencilReactComponent<DsDrawerElement, DsDrawerEvents, Components.DsDrawer, 'heading'>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { createComponent } from './react-runtime.js';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { DsDrawer as DsDrawerElement, defineCustomElement as defineDsDrawer } from "@ds-mo/ui/dist/components/ds-drawer.js";
|
|
5
|
+
export const DsDrawer = /*@__PURE__*/ createComponent({
|
|
6
|
+
tagName: 'ds-drawer',
|
|
7
|
+
elementClass: DsDrawerElement,
|
|
8
|
+
// @ts-ignore - ignore potential React type mismatches between the Stencil Output Target and your project.
|
|
9
|
+
react: React,
|
|
10
|
+
events: {
|
|
11
|
+
onDsClose: 'dsClose',
|
|
12
|
+
onDsAfterClose: 'dsAfterClose',
|
|
13
|
+
onDsBack: 'dsBack',
|
|
14
|
+
onDsPrevious: 'dsPrevious',
|
|
15
|
+
onDsNext: 'dsNext'
|
|
16
|
+
},
|
|
17
|
+
defineCustomElement: defineDsDrawer
|
|
18
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file was automatically generated by the Stencil React Output Target.
|
|
3
|
+
* Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
|
|
4
|
+
*/
|
|
5
|
+
import type { EventName, StencilReactComponent } from './react-runtime.js';
|
|
6
|
+
import { type DsPopoverCustomEvent, type PopoverCloseDetail } from "@ds-mo/ui";
|
|
7
|
+
import type { Components } from "@ds-mo/ui/dist/components";
|
|
8
|
+
import { DsPopover as DsPopoverElement } from "@ds-mo/ui/dist/components/ds-popover.js";
|
|
9
|
+
export type DsPopoverEvents = {
|
|
10
|
+
onDsClose: EventName<DsPopoverCustomEvent<PopoverCloseDetail>>;
|
|
11
|
+
onDsAfterClose: EventName<DsPopoverCustomEvent<void>>;
|
|
12
|
+
};
|
|
13
|
+
export declare const DsPopover: StencilReactComponent<DsPopoverElement, DsPopoverEvents, Components.DsPopover>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { createComponent } from './react-runtime.js';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { DsPopover as DsPopoverElement, defineCustomElement as defineDsPopover } from "@ds-mo/ui/dist/components/ds-popover.js";
|
|
5
|
+
export const DsPopover = /*@__PURE__*/ createComponent({
|
|
6
|
+
tagName: 'ds-popover',
|
|
7
|
+
elementClass: DsPopoverElement,
|
|
8
|
+
// @ts-ignore - ignore potential React type mismatches between the Stencil Output Target and your project.
|
|
9
|
+
react: React,
|
|
10
|
+
events: {
|
|
11
|
+
onDsClose: 'dsClose',
|
|
12
|
+
onDsAfterClose: 'dsAfterClose'
|
|
13
|
+
},
|
|
14
|
+
defineCustomElement: defineDsPopover
|
|
15
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { EventEmitter } from '../../stencil-public-runtime';
|
|
2
|
+
import type { ButtonSize } from '../../utils/button-types';
|
|
3
|
+
export type DisclosureHeadingLevel = 2 | 3 | 4 | 5 | 6;
|
|
4
|
+
export type DisclosureSize = Extract<ButtonSize, 'sm' | 'md'>;
|
|
5
|
+
export declare class Disclosure {
|
|
6
|
+
el: HTMLElement;
|
|
7
|
+
/** Whether the content is shown. The component mutates this when the trigger is activated. */
|
|
8
|
+
open: boolean;
|
|
9
|
+
/** Visible trigger label. */
|
|
10
|
+
label: string;
|
|
11
|
+
/** Trigger density, matching ButtonUnfilled. */
|
|
12
|
+
size: DisclosureSize;
|
|
13
|
+
/** Wraps the trigger in a heading when the label titles a document section. */
|
|
14
|
+
headingLevel: DisclosureHeadingLevel | undefined;
|
|
15
|
+
isInactive: boolean;
|
|
16
|
+
/** Emitted after the user toggles the content; detail is the new open state. */
|
|
17
|
+
dsToggle: EventEmitter<boolean>;
|
|
18
|
+
private readonly panelId;
|
|
19
|
+
private handleTriggerClick;
|
|
20
|
+
render(): any;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=Disclosure.d.ts.map
|