@ds-mo/ui 12.2.0 → 12.4.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/README.md +8 -2
- package/dist/.build-stamp +1 -1
- package/dist/agent-patterns.json +94 -16
- package/dist/agent.json +1092 -53
- package/dist/angular/ds-bar-title.d.ts +7 -1
- package/dist/angular/ds-bar-title.js +7 -4
- package/dist/angular/ds-breadcrumb.d.ts +19 -0
- package/dist/angular/ds-breadcrumb.js +42 -0
- package/dist/angular/ds-card-data-viz-bar.d.ts +18 -0
- package/dist/angular/ds-card-data-viz-bar.js +42 -0
- package/dist/angular/ds-card-data-viz-line.d.ts +18 -0
- package/dist/angular/ds-card-data-viz-line.js +42 -0
- package/dist/angular/ds-chart-bar-stacked.d.ts +12 -0
- package/dist/angular/ds-chart-bar-stacked.js +38 -0
- package/dist/angular/ds-chart-legend.d.ts +1 -1
- package/dist/angular/ds-chart-legend.js +3 -3
- package/dist/angular/ds-shell-app.d.ts +13 -2
- package/dist/angular/ds-shell-app.js +9 -5
- package/dist/angular/ds-shell-mobile-bar.d.ts +18 -0
- package/dist/angular/ds-shell-mobile-bar.js +46 -0
- package/dist/angular/ds-shell-mobile-nav.d.ts +23 -0
- package/dist/angular/ds-shell-mobile-nav.js +51 -0
- package/dist/angular/ds-shell-mobile-section-nav.d.ts +18 -0
- package/dist/angular/ds-shell-mobile-section-nav.js +42 -0
- package/dist/angular/ds-shell-page.d.ts +1 -1
- package/dist/angular/ds-shell-page.js +3 -3
- package/dist/angular/ds-shell-tools.d.ts +42 -0
- package/dist/angular/ds-shell-tools.js +52 -0
- package/dist/angular/index.d.ts +1 -1
- package/dist/angular/index.js +8 -0
- package/dist/angular/proxies.d.ts +8 -0
- package/dist/angular/proxies.js +8 -0
- package/dist/components/ds-agent-activity.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-bar-nav.js +1 -1
- package/dist/components/ds-bar-title.js +1 -1
- package/dist/components/ds-bar-title.js.map +1 -1
- package/dist/components/ds-bar-workflow.js +1 -1
- package/dist/components/ds-breadcrumb.d.ts +11 -0
- package/dist/components/ds-breadcrumb.js +2 -0
- package/dist/components/ds-breadcrumb.js.map +1 -0
- package/dist/components/ds-button-filled.js +1 -1
- package/dist/components/ds-button-unfilled.js +1 -1
- package/dist/components/ds-card-data-viz-bar.d.ts +11 -0
- package/dist/components/ds-card-data-viz-bar.js +2 -0
- package/dist/components/ds-card-data-viz-bar.js.map +1 -0
- package/dist/components/ds-card-data-viz-donut.js +1 -1
- package/dist/components/ds-card-data-viz-donut.js.map +1 -1
- package/dist/components/ds-card-data-viz-line.d.ts +11 -0
- package/dist/components/ds-card-data-viz-line.js +2 -0
- package/dist/components/ds-card-data-viz-line.js.map +1 -0
- package/dist/components/ds-card-setting.js +1 -1
- package/dist/components/ds-card-setting.js.map +1 -1
- package/dist/components/ds-card-shell-data-viz.js +1 -1
- package/dist/components/ds-chart-bar-stacked.d.ts +11 -0
- package/dist/components/ds-chart-bar-stacked.js +2 -0
- package/dist/components/ds-chart-bar-stacked.js.map +1 -0
- package/dist/components/ds-chart-bar.js +1 -1
- package/dist/components/ds-chart-bar.js.map +1 -1
- package/dist/components/ds-chart-donut.js +1 -1
- package/dist/components/ds-chart-legend.js +1 -1
- package/dist/components/ds-chart-legend.js.map +1 -1
- package/dist/components/ds-chart-line.js +1 -1
- package/dist/components/ds-chart-line.js.map +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-item.js.map +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-divider.js +1 -1
- package/dist/components/ds-empty-state.js +1 -1
- package/dist/components/ds-field.js +1 -1
- package/dist/components/ds-input.js +1 -1
- package/dist/components/ds-markdown.js +1 -1
- package/dist/components/ds-menu.js +1 -1
- package/dist/components/ds-message-bubble.js +1 -1
- package/dist/components/ds-message-bubble.js.map +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-scroller.js.map +1 -1
- package/dist/components/ds-message.js +1 -1
- package/dist/components/ds-modal.js +1 -1
- package/dist/components/ds-modal.js.map +1 -1
- package/dist/components/ds-panel-nav.js +1 -1
- package/dist/components/ds-panel-nav.js.map +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-panel-tools.js.map +1 -1
- package/dist/components/ds-radio.js +1 -1
- package/dist/components/ds-scroll-overlay.js +1 -1
- package/dist/components/ds-scroll-overlay.js.map +1 -1
- package/dist/components/ds-select-multi.js +1 -1
- package/dist/components/ds-select-multi.js.map +1 -1
- package/dist/components/ds-select.js +1 -1
- package/dist/components/ds-select.js.map +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-gradient-picker.js +1 -1
- package/dist/components/ds-shell-gradient-picker.js.map +1 -1
- package/dist/components/ds-shell-gradient-swatch.js +1 -1
- package/dist/components/ds-shell-mobile-bar.d.ts +11 -0
- package/dist/components/ds-shell-mobile-bar.js +2 -0
- package/dist/components/ds-shell-mobile-bar.js.map +1 -0
- package/dist/components/ds-shell-mobile-nav.d.ts +11 -0
- package/dist/components/ds-shell-mobile-nav.js +2 -0
- package/dist/components/ds-shell-mobile-nav.js.map +1 -0
- package/dist/components/ds-shell-mobile-section-nav.d.ts +11 -0
- package/dist/components/ds-shell-mobile-section-nav.js +2 -0
- package/dist/components/ds-shell-mobile-section-nav.js.map +1 -0
- package/dist/components/ds-shell-page.js +1 -1
- package/dist/components/ds-shell-page.js.map +1 -1
- package/dist/components/ds-shell-tools.d.ts +11 -0
- package/dist/components/ds-shell-tools.js +2 -0
- package/dist/components/ds-shell-tools.js.map +1 -0
- package/dist/components/ds-skeleton.js +1 -1
- package/dist/components/ds-slider.js +1 -1
- package/dist/components/ds-slider.js.map +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-tab-group.js.map +1 -1
- package/dist/components/ds-tag.js +1 -1
- package/dist/components/ds-text.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-data-viz.js +1 -1
- package/dist/components/ds-tooltip.js +1 -1
- package/dist/components/ds-typing-indicator.js +1 -1
- package/dist/components/ds-typing-indicator.js.map +1 -1
- package/dist/components/{p-DcGpdIlT.js → p-9dQQSgwT.js} +2 -2
- package/dist/components/p-B3rbhRAk.js +2 -0
- package/dist/components/p-B3rbhRAk.js.map +1 -0
- package/dist/components/p-B6Vuayt6.js +2 -0
- package/dist/components/p-B6Vuayt6.js.map +1 -0
- package/dist/components/{p-BWUTr_XN.js → p-BJMRBHKW.js} +2 -2
- package/dist/components/p-BJMRBHKW.js.map +1 -0
- package/dist/components/p-BPS9VExm.js +2 -0
- package/dist/components/{p-CCHqsF_H.js.map → p-BPS9VExm.js.map} +1 -1
- package/dist/components/p-BVV_rlIP.js +2 -0
- package/dist/components/p-BVV_rlIP.js.map +1 -0
- package/dist/components/{p-GMDGgtY8.js → p-BhxwpOH8.js} +2 -2
- package/dist/components/{p-2W-ZjZCR.js → p-Biz6xo-z.js} +2 -2
- package/dist/components/p-BrusnEYP.js +2 -0
- package/dist/components/p-BrusnEYP.js.map +1 -0
- package/dist/components/{p-CG5EX4NW.js → p-BtEjlcB9.js} +2 -2
- package/dist/components/{p-C0_8K4y7.js → p-BwGnhInj.js} +2 -2
- package/dist/components/p-BwGnhInj.js.map +1 -0
- package/dist/components/{p-D6jK2Hyi.js → p-C7bZ-jiU.js} +2 -2
- package/dist/components/p-CH3w-P2H.js +2 -0
- package/dist/components/p-CH3w-P2H.js.map +1 -0
- package/dist/components/{p-DOCKtrS4.js → p-CH6cNXXF.js} +2 -2
- package/dist/components/{p-BE_CDJeL.js → p-CY19gMiz.js} +2 -2
- package/dist/components/p-C_0Q8Cnp.js +2 -0
- package/dist/components/{p-Dg3xLna0.js.map → p-C_0Q8Cnp.js.map} +1 -1
- package/dist/components/p-C_ewSXTT.js +2 -0
- package/dist/components/p-C_ewSXTT.js.map +1 -0
- package/dist/components/{p-CHkI3oIS.js → p-Ce52yVXF.js} +2 -2
- package/dist/components/p-CgHTNfC1.js +2 -0
- package/dist/components/p-CgHTNfC1.js.map +1 -0
- package/dist/components/p-Co8hD3P-.js +2 -0
- package/dist/components/{p-Cxpl-rzP.js.map → p-Co8hD3P-.js.map} +1 -1
- package/dist/components/{p-xY3TjBF5.js → p-CoYFJD-w.js} +2 -2
- package/dist/components/{p-DBi1bmOL.js → p-CtD24EyI.js} +2 -2
- package/dist/components/{p-BRLDJmkh.js → p-D-xttdpn.js} +2 -2
- package/dist/components/{p-CNffejay.js → p-DSa0Hnd3.js} +2 -2
- package/dist/components/p-DXewjzkM.js +2 -0
- package/dist/components/p-DXewjzkM.js.map +1 -0
- package/dist/components/p-DXnFhmZF.js +2 -0
- package/dist/components/{p-BpA9uYKX.js.map → p-DXnFhmZF.js.map} +1 -1
- package/dist/components/{p-DyGB97Oy.js → p-DYqReipj.js} +2 -2
- package/dist/components/{p-ky1msbJ8.js → p-DjPZxzIG.js} +2 -2
- package/dist/components/{p-ky1msbJ8.js.map → p-DjPZxzIG.js.map} +1 -1
- package/dist/components/p-NUNsefU1.js +2 -0
- package/dist/components/{p-8OOCLGph.js.map → p-NUNsefU1.js.map} +1 -1
- package/dist/components/p-PZ6R_nGI.js +2 -0
- package/dist/components/p-PZ6R_nGI.js.map +1 -0
- package/dist/components/{p-B8CubPgQ.js → p-g40DTeV8.js} +2 -2
- package/dist/components/p-jCWtxthA.js +2 -0
- package/dist/components/{p-XgIQ3oI_.js.map → p-jCWtxthA.js.map} +1 -1
- package/dist/components/p-yhj1lIPx.js +2 -0
- package/dist/components/p-yhj1lIPx.js.map +1 -0
- package/dist/docs/components.json +8860 -6021
- package/dist/lib/components/BarNav/bar-nav-types.d.ts +2 -0
- package/dist/lib/components/PanelNav/panel-nav-types.d.ts +29 -0
- package/dist/lib/components/PanelTools/panel-tools-types.d.ts +40 -0
- package/dist/lib/components/TabGroup/tab-item-utils.d.ts +31 -0
- package/dist/lib/shell/index.d.ts +5 -0
- package/dist/lib/shell/index.js +36 -0
- package/dist/lib/shell/index.js.map +3 -3
- package/dist/lib/shell/shell-responsive.d.ts +13 -0
- package/dist/lib/utils/index.js +3 -1
- package/dist/lib/utils/index.js.map +2 -2
- package/dist/mcp/cli.js +142 -18
- package/dist/mcp-data/registry/agent-source-list.json +1 -1
- package/dist/mcp-data/registry/bar-title.json +68 -14
- package/dist/mcp-data/registry/breadcrumb.json +205 -0
- package/dist/mcp-data/registry/card-data-viz-bar.json +212 -0
- package/dist/mcp-data/registry/card-data-viz-line.json +206 -0
- package/dist/mcp-data/registry/card-shell-data-viz.json +7 -0
- package/dist/mcp-data/registry/chart-bar-stacked.json +225 -0
- package/dist/mcp-data/registry/chart-bar.json +9 -2
- package/dist/mcp-data/registry/chart-legend.json +29 -3
- package/dist/mcp-data/registry/chart-line.json +4 -2
- package/dist/mcp-data/registry/conversation-list-item.json +3 -3
- package/dist/mcp-data/registry/message-scroller.json +4 -2
- package/dist/mcp-data/registry/message.json +3 -2
- package/dist/mcp-data/registry/modal.json +1 -1
- package/dist/mcp-data/registry/registry.json +1107 -44
- package/dist/mcp-data/registry/shell-app.json +71 -11
- package/dist/mcp-data/registry/shell-mobile-bar.json +314 -0
- package/dist/mcp-data/registry/shell-mobile-nav.json +348 -0
- package/dist/mcp-data/registry/shell-mobile-section-nav.json +267 -0
- package/dist/mcp-data/registry/shell-page.json +31 -13
- package/dist/mcp-data/registry/shell-tools.json +450 -0
- package/dist/mcp-data/registry/text.json +1 -1
- package/dist/mcp-data/registry/typing-indicator.json +7 -7
- package/dist/react/components.d.ts +8 -0
- package/dist/react/components.js +8 -0
- package/dist/react/ds-bar-title.d.ts +2 -1
- package/dist/react/ds-bar-title.js +1 -0
- package/dist/react/ds-breadcrumb.d.ts +12 -0
- package/dist/react/ds-breadcrumb.js +12 -0
- package/dist/react/ds-card-data-viz-bar.d.ts +12 -0
- package/dist/react/ds-card-data-viz-bar.js +12 -0
- package/dist/react/ds-card-data-viz-line.d.ts +12 -0
- package/dist/react/ds-card-data-viz-line.js +12 -0
- package/dist/react/ds-chart-bar-stacked.d.ts +9 -0
- package/dist/react/ds-chart-bar-stacked.js +12 -0
- package/dist/react/ds-shell-app.d.ts +7 -2
- package/dist/react/ds-shell-app.js +1 -1
- package/dist/react/ds-shell-mobile-bar.d.ts +13 -0
- package/dist/react/ds-shell-mobile-bar.js +15 -0
- package/dist/react/ds-shell-mobile-nav.d.ts +15 -0
- package/dist/react/ds-shell-mobile-nav.js +17 -0
- package/dist/react/ds-shell-mobile-section-nav.d.ts +12 -0
- package/dist/react/ds-shell-mobile-section-nav.js +12 -0
- package/dist/react/ds-shell-tools.d.ts +25 -0
- package/dist/react/ds-shell-tools.js +17 -0
- package/dist/styles/prose.css +1 -0
- package/dist/types/components/BarTitle/BarTitle.d.ts +13 -1
- package/dist/types/components/Breadcrumb/Breadcrumb.d.ts +31 -0
- package/dist/types/components/Breadcrumb/breadcrumb-types.d.ts +17 -0
- package/dist/types/components/CardDataVizBar/CardDataVizBar.d.ts +24 -0
- package/dist/types/components/CardDataVizBar/index.d.ts +2 -0
- package/dist/types/components/CardDataVizLine/CardDataVizLine.d.ts +24 -0
- package/dist/types/components/CardDataVizLine/index.d.ts +2 -0
- package/dist/types/components/ChartBarStacked/ChartBarStacked.d.ts +15 -0
- package/dist/types/components/ChartBarStacked/index.d.ts +3 -0
- package/dist/types/components/ChartLegend/ChartLegend.d.ts +3 -0
- package/dist/types/components/ShellApp/ShellApp.d.ts +16 -0
- package/dist/types/components/ShellMobileBar/ShellMobileBar.d.ts +25 -0
- package/dist/types/components/ShellMobileBar/index.d.ts +4 -0
- package/dist/types/components/ShellMobileBar/shell-mobile-bar-types.d.ts +5 -0
- package/dist/types/components/ShellMobileNav/ShellMobileNav.d.ts +32 -0
- package/dist/types/components/ShellMobileNav/index.d.ts +2 -0
- package/dist/types/components/ShellMobileNav/shell-mobile-nav-types.d.ts +6 -0
- package/dist/types/components/ShellMobileSectionNav/ShellMobileSectionNav.d.ts +30 -0
- package/dist/types/components/ShellMobileSectionNav/index.d.ts +2 -0
- package/dist/types/components/ShellPage/ShellPage.d.ts +19 -7
- package/dist/types/components/ShellPage/shell-page-responsive.d.ts +0 -8
- package/dist/types/components/ShellTools/ShellTools.d.ts +61 -0
- package/dist/types/components/ShellTools/index.d.ts +3 -0
- package/dist/types/components/TypingIndicator/TypingIndicator.d.ts +1 -1
- package/dist/types/components.d.ts +953 -19
- package/dist/types/shell/index.d.ts +5 -0
- package/dist/types/shell/shell-responsive.d.ts +14 -0
- package/package.json +1 -1
- package/dist/components/p-8OOCLGph.js +0 -2
- package/dist/components/p-BWUTr_XN.js.map +0 -1
- package/dist/components/p-BpA9uYKX.js +0 -2
- package/dist/components/p-C0_8K4y7.js.map +0 -1
- package/dist/components/p-CA3kLO7K.js +0 -2
- package/dist/components/p-CA3kLO7K.js.map +0 -1
- package/dist/components/p-CCHqsF_H.js +0 -2
- package/dist/components/p-Cxpl-rzP.js +0 -2
- package/dist/components/p-CzBnvtcz.js +0 -2
- package/dist/components/p-CzBnvtcz.js.map +0 -1
- package/dist/components/p-Dg3xLna0.js +0 -2
- package/dist/components/p-XgIQ3oI_.js +0 -2
- /package/dist/components/{p-DcGpdIlT.js.map → p-9dQQSgwT.js.map} +0 -0
- /package/dist/components/{p-GMDGgtY8.js.map → p-BhxwpOH8.js.map} +0 -0
- /package/dist/components/{p-2W-ZjZCR.js.map → p-Biz6xo-z.js.map} +0 -0
- /package/dist/components/{p-CG5EX4NW.js.map → p-BtEjlcB9.js.map} +0 -0
- /package/dist/components/{p-D6jK2Hyi.js.map → p-C7bZ-jiU.js.map} +0 -0
- /package/dist/components/{p-DOCKtrS4.js.map → p-CH6cNXXF.js.map} +0 -0
- /package/dist/components/{p-BE_CDJeL.js.map → p-CY19gMiz.js.map} +0 -0
- /package/dist/components/{p-CHkI3oIS.js.map → p-Ce52yVXF.js.map} +0 -0
- /package/dist/components/{p-xY3TjBF5.js.map → p-CoYFJD-w.js.map} +0 -0
- /package/dist/components/{p-DBi1bmOL.js.map → p-CtD24EyI.js.map} +0 -0
- /package/dist/components/{p-BRLDJmkh.js.map → p-D-xttdpn.js.map} +0 -0
- /package/dist/components/{p-CNffejay.js.map → p-DSa0Hnd3.js.map} +0 -0
- /package/dist/components/{p-DyGB97Oy.js.map → p-DYqReipj.js.map} +0 -0
- /package/dist/components/{p-B8CubPgQ.js.map → p-g40DTeV8.js.map} +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
3
|
"name": "shell-page",
|
|
4
4
|
"title": "ShellPage",
|
|
5
|
-
"description": "The standard routed-page boundary inside ShellApp, owning sticky BarTitle or BarWorkflow placement,
|
|
5
|
+
"description": "The standard routed-page boundary inside ShellApp, owning sticky BarTitle or BarWorkflow placement, application-supplied BarTitle capacity, stable scroll compaction, and optional page-content inset without creating a nested scroll container.",
|
|
6
6
|
"type": "registry:ui",
|
|
7
7
|
"meta": {
|
|
8
8
|
"library": "@ds-mo/ui",
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
"intent": {
|
|
14
14
|
"audience": "general",
|
|
15
15
|
"status": "stable",
|
|
16
|
-
"summary": "The standard routed-page boundary inside ShellApp, owning sticky BarTitle or BarWorkflow placement,
|
|
16
|
+
"summary": "The standard routed-page boundary inside ShellApp, owning sticky BarTitle or BarWorkflow placement, application-supplied BarTitle capacity, stable scroll compaction, and optional page-content inset without creating a nested scroll container.",
|
|
17
17
|
"useWhen": [
|
|
18
18
|
"Rendering a standard routed page inside ShellApp's default content region.",
|
|
19
|
-
"BarTitle should expand at a roomy page top, compact after scroll, or use constrained action layout when the
|
|
19
|
+
"BarTitle should expand at a roomy page top, compact after scroll, or use constrained action layout when the application shell reports reduced capacity.",
|
|
20
20
|
"BarWorkflow needs sticky page-level placement while remaining compact.",
|
|
21
21
|
"Pages need consistent content gutters with an explicit full-bleed option."
|
|
22
22
|
],
|
|
@@ -53,16 +53,18 @@
|
|
|
53
53
|
"Responsive variant changes preserve the same heading, controls, accessible names, and keyboard focus targets."
|
|
54
54
|
],
|
|
55
55
|
"states": [
|
|
56
|
-
"
|
|
56
|
+
"headerCapacity=roomy, compact, or constrained is supplied before paint by the owning application shell and describes the page-header space available after viewport and shell-chrome policy are applied.",
|
|
57
|
+
"headerPresentation=auto selects the matching header variant from headerCapacity and scroll position.",
|
|
57
58
|
"expanded, compact, and constrained are explicit overrides for exceptional layouts and deterministic examples.",
|
|
58
59
|
"contentInset=default supplies standard page gutters; none permits maps, canvases, and other full-bleed content.",
|
|
59
60
|
"The required header slot accepts one BarTitle or BarWorkflow. The default slot owns router output and all page product content."
|
|
60
61
|
],
|
|
61
62
|
"responsiveBehavior": [
|
|
62
|
-
"
|
|
63
|
-
"Roomy
|
|
64
|
-
"A
|
|
65
|
-
"
|
|
63
|
+
"The owning application shell resolves viewport and persistent-chrome state into the explicit headerCapacity prop; ShellPage never guesses the cause of reduced width from its own rendered size.",
|
|
64
|
+
"Roomy capacity expands at the top and compacts after scroll; compact capacity stays compact; constrained capacity uses the constrained action layout.",
|
|
65
|
+
"A ShellPage-managed BarTitle remains concealed until headerCapacity is available and its rendered variant matches the resolved presentation, preventing a default-variant flash during route activation.",
|
|
66
|
+
"Update headerCapacity synchronously with shell layout state so opening tools or crossing application breakpoints cannot briefly render the wrong presentation.",
|
|
67
|
+
"On roomy pages, the expanded header scrolls upward until its title and action row reaches the compact row position, then snaps variants without moving that shared visual anchor.",
|
|
66
68
|
"Scroll compaction preserves the expanded page flow offset so content never jumps beneath the sticky header.",
|
|
67
69
|
"ShellPage relies on ShellApp's content scroller and never introduces nested scrolling.",
|
|
68
70
|
"BarWorkflow remains compact and is not assigned BarTitle's capacity or scroll variants."
|
|
@@ -107,6 +109,14 @@
|
|
|
107
109
|
"mutable": false,
|
|
108
110
|
"description": "Standard page gutters, or no inset for full-bleed page content."
|
|
109
111
|
},
|
|
112
|
+
"headerCapacity": {
|
|
113
|
+
"type": "ShellPageCapacity",
|
|
114
|
+
"resolvedType": "\"compact\" | \"constrained\" | \"roomy\" | undefined",
|
|
115
|
+
"attribute": "header-capacity",
|
|
116
|
+
"required": false,
|
|
117
|
+
"mutable": false,
|
|
118
|
+
"description": "Available page-header capacity supplied by the owning application shell."
|
|
119
|
+
},
|
|
110
120
|
"headerPresentation": {
|
|
111
121
|
"type": "ShellPageHeaderPresentation",
|
|
112
122
|
"resolvedType": "\"auto\" | \"compact\" | \"constrained\" | \"expanded\"",
|
|
@@ -114,7 +124,7 @@
|
|
|
114
124
|
"default": "'auto'",
|
|
115
125
|
"required": false,
|
|
116
126
|
"mutable": false,
|
|
117
|
-
"description": "Automatic
|
|
127
|
+
"description": "Automatic capacity/scroll behavior, or an explicit header variant override."
|
|
118
128
|
}
|
|
119
129
|
},
|
|
120
130
|
"events": [],
|
|
@@ -131,6 +141,14 @@
|
|
|
131
141
|
"mutable": false,
|
|
132
142
|
"description": "Standard page gutters, or no inset for full-bleed page content."
|
|
133
143
|
},
|
|
144
|
+
"headerCapacity": {
|
|
145
|
+
"type": "ShellPageCapacity",
|
|
146
|
+
"resolvedType": "\"compact\" | \"constrained\" | \"roomy\" | undefined",
|
|
147
|
+
"attribute": "header-capacity",
|
|
148
|
+
"required": false,
|
|
149
|
+
"mutable": false,
|
|
150
|
+
"description": "Available page-header capacity supplied by the owning application shell."
|
|
151
|
+
},
|
|
134
152
|
"headerPresentation": {
|
|
135
153
|
"type": "ShellPageHeaderPresentation",
|
|
136
154
|
"resolvedType": "\"auto\" | \"compact\" | \"constrained\" | \"expanded\"",
|
|
@@ -138,7 +156,7 @@
|
|
|
138
156
|
"default": "'auto'",
|
|
139
157
|
"required": false,
|
|
140
158
|
"mutable": false,
|
|
141
|
-
"description": "Automatic
|
|
159
|
+
"description": "Automatic capacity/scroll behavior, or an explicit header variant override."
|
|
142
160
|
}
|
|
143
161
|
},
|
|
144
162
|
"events": [],
|
|
@@ -181,17 +199,17 @@
|
|
|
181
199
|
"files": [
|
|
182
200
|
{
|
|
183
201
|
"path": "src/wc/components/ShellPage/ShellPage.css",
|
|
184
|
-
"content": ":host {\n display: block;\n position: relative;\n min-width: 0;\n min-height: 100%;\n box-sizing: border-box;\n background: var(--color-background-primary);\n}\n\n.shell-page__scroll-sentinel {\n position: absolute;\n inset-block-start: 0;\n inset-inline-start: 0;\n width: var(--dimension-stroke-width-012);\n height: var(--dimension-stroke-width-012);\n pointer-events: none;\n}\n\n.shell-page__sticky-header {\n position: sticky;\n inset-block-start: 0;\n z-index: var(--dimension-z-index-raised);\n min-width: 0;\n background: var(--color-background-primary);\n overflow: visible;\n overflow-anchor: none;\n}\n\n.shell-page__sticky-header ::slotted(ds-bar-title),\n.shell-page__sticky-header ::slotted(ds-bar-workflow) {\n display: block;\n min-width: 0;\n}\n\n.shell-page__flow-spacer {\n height: var(--ds-shell-page-flow-spacer, 0);\n background: var(--color-background-primary);\n}\n\n.shell-page__content {\n min-width: 0;\n box-sizing: border-box;\n background: var(--color-background-primary);\n}\n\n:host(.shell-page-host--inset-default) .shell-page__content {\n padding: var(--dimension-space-400) var(--dimension-space-400) var(--dimension-space-800);\n}\n\n:host(.shell-page-host--inset-none) .shell-page__content {\n padding: 0;\n}\n",
|
|
202
|
+
"content": ":host {\n display: block;\n position: relative;\n min-width: 0;\n min-height: 100%;\n box-sizing: border-box;\n background: var(--color-background-primary);\n}\n\n.shell-page__scroll-sentinel {\n position: absolute;\n inset-block-start: var(--ds-shell-page-header-travel, 0);\n inset-inline-start: 0;\n width: var(--dimension-stroke-width-012);\n height: var(--dimension-stroke-width-012);\n pointer-events: none;\n}\n\n.shell-page__sticky-header {\n position: sticky;\n inset-block-start: var(--ds-shell-page-sticky-offset, 0);\n z-index: var(--dimension-z-index-raised);\n min-width: 0;\n background: var(--color-background-primary);\n overflow: visible;\n overflow-anchor: none;\n}\n\n.shell-page__sticky-header ::slotted(ds-bar-title),\n.shell-page__sticky-header ::slotted(ds-bar-workflow) {\n display: block;\n min-width: 0;\n}\n\n.shell-page__flow-spacer {\n height: var(--ds-shell-page-flow-spacer, 0);\n background: var(--color-background-primary);\n}\n\n.shell-page__content {\n min-width: 0;\n box-sizing: border-box;\n background: var(--color-background-primary);\n}\n\n:host(.shell-page-host--inset-default) .shell-page__content {\n padding: var(--dimension-space-400) var(--dimension-space-400) var(--dimension-space-800);\n}\n\n:host(.shell-page-host--inset-none) .shell-page__content {\n padding: 0;\n}\n",
|
|
185
203
|
"type": "registry:ui"
|
|
186
204
|
},
|
|
187
205
|
{
|
|
188
206
|
"path": "src/wc/components/ShellPage/ShellPage.tsx",
|
|
189
|
-
"content": "import { Component, Element, h, Host, Prop, State } from '@stencil/core';\nimport { createRafCoalescer } from '../../shell/chrome-transition';\nimport { resolveCssLengthPx } from '../../utils/resolve-css-length-px';\nimport type { BarTitleVariant } from '../BarTitle/bar-title-types';\nimport { resolveShellPageCapacity, resolveShellPageHeaderVariant } from './shell-page-responsive';\nimport type {\n ShellPageCapacity,\n ShellPageContentInset,\n ShellPageHeaderPresentation,\n} from './shell-page-types';\n\ntype BarTitleElement = HTMLElement & { variant: BarTitleVariant };\n\n@Component({\n tag: 'ds-shell-page',\n styleUrl: 'ShellPage.css',\n scoped: true,\n})\nexport class ShellPage {\n private static readonly MAX_HEADER_REVEAL_FRAMES = 12;\n\n @Element() el!: HTMLElement;\n\n /** Automatic container/scroll behavior, or an explicit header variant override. */\n @Prop() headerPresentation: ShellPageHeaderPresentation = 'auto';\n\n /** Standard page gutters, or no inset for full-bleed page content. */\n @Prop() contentInset: ShellPageContentInset = 'default';\n\n @State() private capacity: ShellPageCapacity | null = null;\n @State() private pageTopVisible = true;\n\n private sentinelEl: HTMLElement | null = null;\n private spacerEl: HTMLElement | null = null;\n private headerEl: BarTitleElement | null = null;\n private expandedHeaderHeight = 0;\n private resizeObserver: ResizeObserver | null = null;\n private headerResizeObserver: ResizeObserver | null = null;\n private headerRevealFrame: number | null = null;\n private scrollRoot: HTMLElement | Window | null = null;\n private readonly layoutCoalescer = createRafCoalescer(() => this.syncCapacity());\n\n componentDidLoad() {\n // Resolve the already-laid-out page width before adopting a slotted header.\n // Waiting for the first ResizeObserver/RAF lets BarTitle paint its default\n // expanded variant for one frame when persistent shell chrome narrows a\n // newly navigated page.\n this.syncCapacity();\n this.observeHeader(this.findHeader());\n this.connectObservers();\n this.connectScrollRoot();\n this.layoutCoalescer.schedule();\n }\n\n componentDidRender() {\n this.syncHeaderVariant();\n }\n\n disconnectedCallback() {\n this.resizeObserver?.disconnect();\n this.resizeObserver = null;\n this.headerResizeObserver?.disconnect();\n this.headerResizeObserver = null;\n this.cancelHeaderReveal();\n this.scrollRoot?.removeEventListener('scroll', this.handleScroll);\n this.scrollRoot = null;\n this.layoutCoalescer.cancel();\n }\n\n private get effectiveVariant(): BarTitleVariant {\n return resolveShellPageHeaderVariant(\n this.headerPresentation,\n this.capacity ?? 'roomy',\n this.pageTopVisible\n );\n }\n\n private get isScrollCompacted(): boolean {\n return this.headerPresentation === 'auto' && this.capacity === 'roomy' && !this.pageTopVisible;\n }\n\n private connectObservers() {\n if (typeof ResizeObserver !== 'undefined') {\n this.resizeObserver = new ResizeObserver(() => this.layoutCoalescer.schedule());\n this.resizeObserver.observe(this.el);\n }\n }\n\n private composedParent(element: Element): Element | null {\n return (element as HTMLElement).assignedSlot?.parentElement ?? element.parentElement;\n }\n\n private findScrollRoot(): HTMLElement | Window {\n let ancestor = this.composedParent(this.el);\n while (ancestor && ancestor !== document.documentElement) {\n const overflow = getComputedStyle(ancestor).overflowY;\n if (overflow === 'auto' || overflow === 'scroll') return ancestor as HTMLElement;\n ancestor = this.composedParent(ancestor);\n }\n return window;\n }\n\n private connectScrollRoot() {\n this.scrollRoot?.removeEventListener('scroll', this.handleScroll);\n this.scrollRoot = this.findScrollRoot();\n this.scrollRoot.addEventListener('scroll', this.handleScroll, { passive: true });\n this.handleScroll();\n }\n\n private handleScroll = () => {\n if (!this.sentinelEl || !this.scrollRoot) return;\n const rootTop =\n this.scrollRoot === window ? 0 : (this.scrollRoot as HTMLElement).getBoundingClientRect().top;\n this.pageTopVisible = this.sentinelEl.getBoundingClientRect().top >= rootTop;\n };\n\n private findHeader(): BarTitleElement | null {\n return this.el.querySelector<BarTitleElement>('ds-bar-title');\n }\n\n private syncCapacity() {\n const inlineSize = this.el.getBoundingClientRect().width;\n if (inlineSize <= 0) return;\n\n const compactAt = resolveCssLengthPx('--dimension-panel-width-2xl', 0);\n const constrainedAt = resolveCssLengthPx('--dimension-panel-width-lg', 0);\n const hysteresis = resolveCssLengthPx('--dimension-space-100', 0);\n if (compactAt <= 0 || constrainedAt <= 0) return;\n\n const next = resolveShellPageCapacity({\n inlineSize,\n compactAt,\n constrainedAt,\n hysteresis,\n previous: this.capacity,\n });\n if (next !== this.capacity) this.capacity = next;\n else this.syncHeaderVariant();\n }\n\n private get compactHeaderHeight(): number {\n return resolveCssLengthPx('--dimension-size-600', 0);\n }\n\n private setSpacerHeight(height: number) {\n if (!this.spacerEl) return;\n this.spacerEl.style.setProperty('--ds-shell-page-flow-spacer', `${Math.max(0, height)}px`);\n }\n\n private variantClass(variant: BarTitleVariant): string {\n return `bar-title-host--${variant}`;\n }\n\n private cancelHeaderReveal() {\n if (this.headerRevealFrame !== null) cancelAnimationFrame(this.headerRevealFrame);\n this.headerRevealFrame = null;\n this.headerEl?.removeAttribute('data-shell-page-syncing');\n }\n\n private revealHeaderWhenSynced(header: BarTitleElement, variant: BarTitleVariant) {\n // Stencil re-renders within a frame or two of a prop change, but this cap\n // guarantees the header can never stay hidden indefinitely if that\n // assumption ever breaks.\n let framesRemaining = ShellPage.MAX_HEADER_REVEAL_FRAMES;\n const reveal = () => {\n if (this.headerEl !== header) {\n header.removeAttribute('data-shell-page-syncing');\n return;\n }\n framesRemaining -= 1;\n if (framesRemaining <= 0 || header.classList.contains(this.variantClass(variant))) {\n header.removeAttribute('data-shell-page-syncing');\n this.headerRevealFrame = null;\n return;\n }\n this.headerRevealFrame = requestAnimationFrame(reveal);\n };\n this.headerRevealFrame = requestAnimationFrame(reveal);\n }\n\n private syncHeaderVariant(concealUntilSynced = false) {\n const header = this.headerEl;\n if (!header) return;\n\n const next = this.effectiveVariant;\n if (header.variant === 'expanded') {\n this.expandedHeaderHeight = header.getBoundingClientRect().height;\n }\n\n this.setSpacerHeight(\n this.isScrollCompacted ? this.expandedHeaderHeight - this.compactHeaderHeight : 0\n );\n\n const renderedVariantIsStale = !header.classList.contains(this.variantClass(next));\n if (concealUntilSynced && renderedVariantIsStale) {\n header.setAttribute('data-shell-page-syncing', '');\n }\n if (header.variant !== next) header.variant = next;\n if (concealUntilSynced && renderedVariantIsStale) {\n this.revealHeaderWhenSynced(header, next);\n }\n }\n\n private observeHeader(header: BarTitleElement | null) {\n this.cancelHeaderReveal();\n this.headerResizeObserver?.disconnect();\n this.headerResizeObserver = null;\n this.headerEl = header;\n this.expandedHeaderHeight = 0;\n this.setSpacerHeight(0);\n\n if (!header) return;\n if (typeof ResizeObserver !== 'undefined') {\n this.headerResizeObserver = new ResizeObserver(() => {\n if (header.variant === 'expanded') {\n this.expandedHeaderHeight = header.getBoundingClientRect().height;\n }\n });\n this.headerResizeObserver.observe(header);\n }\n this.syncHeaderVariant(true);\n }\n\n private handleHeaderSlotChange = (event: Event) => {\n const slot = event.target as HTMLSlotElement;\n const header = slot\n .assignedElements({ flatten: true })\n .find(element => element.tagName.toLowerCase() === 'ds-bar-title');\n this.observeHeader((header as BarTitleElement | undefined) ?? null);\n };\n\n render() {\n return (\n <Host\n role=\"main\"\n class={{\n 'shell-page-host--inset-default': this.contentInset === 'default',\n 'shell-page-host--inset-none': this.contentInset === 'none',\n [`shell-page-host--header-${this.effectiveVariant}`]: true,\n }}\n >\n <div\n ref={el => {\n this.sentinelEl = el ?? null;\n }}\n class=\"shell-page__scroll-sentinel\"\n aria-hidden=\"true\"\n />\n <div class=\"shell-page__sticky-header\">\n <slot name=\"header\" onSlotchange={this.handleHeaderSlotChange} />\n </div>\n <div\n ref={el => {\n this.spacerEl = el ?? null;\n }}\n class=\"shell-page__flow-spacer\"\n aria-hidden=\"true\"\n />\n <div class=\"shell-page__content\">\n <slot />\n </div>\n </Host>\n );\n }\n}\n",
|
|
207
|
+
"content": "import { Component, Element, h, Host, Prop, State, Watch } from '@stencil/core';\nimport { resolveCssLengthPx } from '../../utils/resolve-css-length-px';\nimport type { BarTitleVariant } from '../BarTitle/bar-title-types';\nimport { resolveShellPageHeaderVariant } from './shell-page-responsive';\nimport type {\n ShellPageCapacity,\n ShellPageContentInset,\n ShellPageHeaderPresentation,\n} from './shell-page-types';\n\ntype BarTitleElement = HTMLElement & { variant: BarTitleVariant };\n\n@Component({\n tag: 'ds-shell-page',\n styleUrl: 'ShellPage.css',\n scoped: true,\n})\nexport class ShellPage {\n private static readonly MAX_HEADER_REVEAL_FRAMES = 12;\n private static readonly MAX_HEADER_GEOMETRY_FRAMES = 12;\n\n @Element() el!: HTMLElement;\n\n /** Automatic capacity/scroll behavior, or an explicit header variant override. */\n @Prop() headerPresentation: ShellPageHeaderPresentation = 'auto';\n\n /** Available page-header capacity supplied by the owning application shell. */\n @Prop() headerCapacity?: ShellPageCapacity;\n\n /** Standard page gutters, or no inset for full-bleed page content. */\n @Prop() contentInset: ShellPageContentInset = 'default';\n\n @State() private pageTopVisible = true;\n\n private sentinelEl: HTMLElement | null = null;\n private spacerEl: HTMLElement | null = null;\n private stickyHeaderEl: HTMLElement | null = null;\n private headerEl: BarTitleElement | null = null;\n private expandedHeaderHeight = 0;\n private headerTravel = 0;\n private expandedGeometryFrozen = false;\n private headerResizeObserver: ResizeObserver | null = null;\n private headerMutationObserver: MutationObserver | null = null;\n private headerRevealFrame: number | null = null;\n private headerGeometryFrame: number | null = null;\n private scrollRoot: HTMLElement | Window | null = null;\n\n @Watch('headerCapacity')\n @Watch('headerPresentation')\n handleHeaderContractChange() {\n this.syncHeaderVariant(true);\n }\n\n componentDidLoad() {\n this.observeHeader(this.findHeader());\n this.connectScrollRoot();\n }\n\n componentDidRender() {\n this.syncHeaderVariant();\n }\n\n disconnectedCallback() {\n this.headerResizeObserver?.disconnect();\n this.headerResizeObserver = null;\n this.headerMutationObserver?.disconnect();\n this.headerMutationObserver = null;\n this.cancelHeaderReveal();\n this.cancelHeaderGeometrySync();\n this.scrollRoot?.removeEventListener('scroll', this.handleScroll);\n this.scrollRoot = null;\n }\n\n private get effectiveVariant(): BarTitleVariant {\n return resolveShellPageHeaderVariant(\n this.headerPresentation,\n this.headerCapacity ?? 'roomy',\n this.pageTopVisible\n );\n }\n\n private get headerContractResolved(): boolean {\n return this.headerPresentation !== 'auto' || this.headerCapacity !== undefined;\n }\n\n private get isScrollCompacted(): boolean {\n return (\n this.headerPresentation === 'auto' && this.headerCapacity === 'roomy' && !this.pageTopVisible\n );\n }\n\n private composedParent(element: Element): Element | null {\n return (element as HTMLElement).assignedSlot?.parentElement ?? element.parentElement;\n }\n\n private findScrollRoot(): HTMLElement | Window {\n let ancestor = this.composedParent(this.el);\n while (ancestor && ancestor !== document.documentElement) {\n const overflow = getComputedStyle(ancestor).overflowY;\n if (overflow === 'auto' || overflow === 'scroll' || overflow === 'overlay') {\n return ancestor as HTMLElement;\n }\n ancestor = this.composedParent(ancestor);\n }\n return window;\n }\n\n private connectScrollRoot() {\n const next = this.findScrollRoot();\n if (next === this.scrollRoot) return;\n this.scrollRoot?.removeEventListener('scroll', this.handleScroll);\n this.scrollRoot = next;\n this.scrollRoot.addEventListener('scroll', this.handleScroll, { passive: true });\n this.handleScroll();\n }\n\n private handleScroll = () => {\n if (!this.sentinelEl || !this.scrollRoot) return;\n const rootTop =\n this.scrollRoot === window ? 0 : (this.scrollRoot as HTMLElement).getBoundingClientRect().top;\n const nextPageTopVisible = this.sentinelEl.getBoundingClientRect().top >= rootTop;\n if (\n !nextPageTopVisible &&\n this.pageTopVisible &&\n this.headerEl?.classList.contains(this.variantClass('expanded'))\n ) {\n this.captureExpandedHeaderGeometry(this.headerEl);\n this.expandedGeometryFrozen = true;\n }\n if (\n nextPageTopVisible &&\n this.expandedGeometryFrozen &&\n this.el.getBoundingClientRect().top >= rootTop\n ) {\n this.expandedGeometryFrozen = false;\n if (this.headerEl?.classList.contains(this.variantClass('expanded'))) {\n this.captureExpandedHeaderGeometry(this.headerEl);\n }\n }\n this.pageTopVisible = nextPageTopVisible;\n };\n\n private findHeader(): BarTitleElement | null {\n return this.el.querySelector<BarTitleElement>('ds-bar-title');\n }\n\n private get compactHeaderHeight(): number {\n return resolveCssLengthPx('--dimension-size-600', 0);\n }\n\n private setHeaderTravel(distance: number) {\n const next = Math.max(0, distance);\n if (Math.abs(next - this.headerTravel) < 0.5) return;\n this.headerTravel = next;\n this.el.style.setProperty('--ds-shell-page-header-travel', `${next}px`);\n this.handleScroll();\n }\n\n private measureHeaderTravel(header: BarTitleElement) {\n const anchor = header.querySelector<HTMLElement>('[data-shell-page-header-anchor]');\n const compactHeight = this.compactHeaderHeight;\n if (!anchor || compactHeight <= 0) return;\n\n const headerRect = header.getBoundingClientRect();\n const anchorRect = anchor.getBoundingClientRect();\n if (headerRect.height <= 0 || anchorRect.height <= 0) return;\n\n const expandedAnchorTop = anchorRect.top - headerRect.top;\n const compactAnchorTop = Math.max(0, (compactHeight - anchorRect.height) / 2);\n this.setHeaderTravel(expandedAnchorTop - compactAnchorTop);\n }\n\n private captureExpandedHeaderGeometry(header: BarTitleElement) {\n this.expandedHeaderHeight = header.getBoundingClientRect().height;\n this.measureHeaderTravel(header);\n }\n\n private setSpacerHeight(height: number) {\n if (!this.spacerEl) return;\n this.spacerEl.style.setProperty('--ds-shell-page-flow-spacer', `${Math.max(0, height)}px`);\n }\n\n private variantClass(variant: BarTitleVariant): string {\n return `bar-title-host--${variant}`;\n }\n\n private setStickyHeaderOffset(renderedVariant: BarTitleVariant) {\n this.stickyHeaderEl?.style.setProperty(\n '--ds-shell-page-sticky-offset',\n renderedVariant === 'expanded' &&\n this.headerPresentation === 'auto' &&\n this.headerCapacity === 'roomy'\n ? `${-this.headerTravel}px`\n : '0px'\n );\n }\n\n private syncRenderedHeaderGeometry(header: BarTitleElement) {\n const renderedVariant = this.effectiveVariant;\n if (!header.classList.contains(this.variantClass(renderedVariant))) return;\n\n // Update both flow-preservation pieces before reading layout. Otherwise a\n // reverse compact → expanded render can briefly contain the tall header and\n // the compact spacer together, giving browser scroll anchoring a transient\n // height change to compensate.\n this.setSpacerHeight(\n this.isScrollCompacted ? this.expandedHeaderHeight - this.compactHeaderHeight : 0\n );\n this.setStickyHeaderOffset(renderedVariant);\n\n if (renderedVariant === 'expanded' && !this.expandedGeometryFrozen) {\n this.captureExpandedHeaderGeometry(header);\n this.setStickyHeaderOffset(renderedVariant);\n }\n }\n\n private cancelHeaderGeometrySync() {\n if (this.headerGeometryFrame !== null) cancelAnimationFrame(this.headerGeometryFrame);\n this.headerGeometryFrame = null;\n }\n\n private scheduleHeaderGeometrySync(header: BarTitleElement) {\n this.cancelHeaderGeometrySync();\n let framesRemaining = ShellPage.MAX_HEADER_GEOMETRY_FRAMES;\n let framesElapsed = 0;\n const sync = () => {\n if (this.headerEl !== header) return;\n framesRemaining -= 1;\n framesElapsed += 1;\n // In WebKit a child's componentDidLoad can precede its assignment into\n // the parent's rendered slot. Revalidate after ShellApp has had a frame\n // to expose the actual content scroller.\n this.connectScrollRoot();\n this.syncRenderedHeaderGeometry(header);\n const geometryReady =\n this.effectiveVariant !== 'expanded' ||\n (this.expandedHeaderHeight > 0 && this.headerTravel > 0);\n if ((geometryReady && framesElapsed >= 2) || framesRemaining <= 0) {\n this.headerGeometryFrame = null;\n return;\n }\n this.headerGeometryFrame = requestAnimationFrame(sync);\n };\n this.headerGeometryFrame = requestAnimationFrame(sync);\n }\n\n private cancelHeaderReveal() {\n if (this.headerRevealFrame !== null) cancelAnimationFrame(this.headerRevealFrame);\n this.headerRevealFrame = null;\n this.headerEl?.removeAttribute('data-shell-page-syncing');\n }\n\n private revealHeaderWhenSynced(header: BarTitleElement, variant: BarTitleVariant) {\n // Stencil normally re-renders within a frame or two of a prop change. Stop\n // polling at the cap, but fail closed: a stale variant must never paint.\n let framesRemaining = ShellPage.MAX_HEADER_REVEAL_FRAMES;\n const reveal = () => {\n if (this.headerEl !== header) {\n header.removeAttribute('data-shell-page-syncing');\n return;\n }\n framesRemaining -= 1;\n if (header.classList.contains(this.variantClass(variant))) {\n header.removeAttribute('data-shell-page-syncing');\n this.headerRevealFrame = null;\n return;\n }\n if (framesRemaining <= 0) {\n this.headerRevealFrame = null;\n return;\n }\n this.headerRevealFrame = requestAnimationFrame(reveal);\n };\n this.headerRevealFrame = requestAnimationFrame(reveal);\n }\n\n private syncHeaderVariant(concealUntilSynced = false) {\n const header = this.headerEl;\n if (!header) return;\n\n if (!this.headerContractResolved) {\n this.cancelHeaderReveal();\n header.setAttribute('data-shell-page-syncing', '');\n return;\n }\n\n const next = this.effectiveVariant;\n const renderedVariantIsStale = !header.classList.contains(this.variantClass(next));\n if (!renderedVariantIsStale) this.syncRenderedHeaderGeometry(header);\n if (concealUntilSynced) {\n this.cancelHeaderReveal();\n header.setAttribute('data-shell-page-syncing', '');\n }\n if (header.variant !== next) header.variant = next;\n if (concealUntilSynced) {\n this.revealHeaderWhenSynced(header, next);\n }\n }\n\n private observeHeader(header: BarTitleElement | null) {\n this.cancelHeaderReveal();\n this.cancelHeaderGeometrySync();\n this.headerResizeObserver?.disconnect();\n this.headerResizeObserver = null;\n this.headerMutationObserver?.disconnect();\n this.headerMutationObserver = null;\n this.headerEl = header;\n this.expandedHeaderHeight = 0;\n this.expandedGeometryFrozen = false;\n this.setHeaderTravel(0);\n this.setSpacerHeight(0);\n this.stickyHeaderEl?.style.setProperty('--ds-shell-page-sticky-offset', '0px');\n\n if (!header) return;\n if (header.classList.contains(this.variantClass('expanded'))) {\n this.captureExpandedHeaderGeometry(header);\n }\n if (typeof ResizeObserver !== 'undefined') {\n this.headerResizeObserver = new ResizeObserver(() => {\n this.syncRenderedHeaderGeometry(header);\n });\n this.headerResizeObserver.observe(header);\n }\n if (typeof MutationObserver !== 'undefined') {\n this.headerMutationObserver = new MutationObserver(() => {\n this.syncRenderedHeaderGeometry(header);\n });\n this.headerMutationObserver.observe(header, {\n attributes: true,\n attributeFilter: ['class'],\n });\n }\n this.syncHeaderVariant(true);\n this.scheduleHeaderGeometrySync(header);\n }\n\n private handleHeaderSlotChange = (event: Event) => {\n const slot = event.target as HTMLSlotElement;\n const header = slot\n .assignedElements()\n .find(element => element.tagName.toLowerCase() === 'ds-bar-title');\n this.observeHeader((header as BarTitleElement | undefined) ?? null);\n };\n\n render() {\n return (\n <Host\n role=\"main\"\n class={{\n 'shell-page-host--inset-default': this.contentInset === 'default',\n 'shell-page-host--inset-none': this.contentInset === 'none',\n [`shell-page-host--header-${this.effectiveVariant}`]: true,\n }}\n >\n <div\n ref={el => {\n this.sentinelEl = el ?? null;\n }}\n class=\"shell-page__scroll-sentinel\"\n aria-hidden=\"true\"\n />\n <div\n ref={el => {\n this.stickyHeaderEl = el ?? null;\n }}\n class=\"shell-page__sticky-header\"\n >\n <slot name=\"header\" onSlotchange={this.handleHeaderSlotChange} />\n </div>\n <div\n ref={el => {\n this.spacerEl = el ?? null;\n }}\n class=\"shell-page__flow-spacer\"\n aria-hidden=\"true\"\n />\n <div class=\"shell-page__content\">\n <slot />\n </div>\n </Host>\n );\n }\n}\n",
|
|
190
208
|
"type": "registry:ui"
|
|
191
209
|
},
|
|
192
210
|
{
|
|
193
211
|
"path": "src/wc/components/ShellPage/shell-page-responsive.ts",
|
|
194
|
-
"content": "import type { ShellPageCapacity, ShellPageHeaderPresentation } from './shell-page-types';\nimport type { BarTitleVariant } from '../BarTitle/bar-title-types';\n\nexport
|
|
212
|
+
"content": "import type { ShellPageCapacity, ShellPageHeaderPresentation } from './shell-page-types';\nimport type { BarTitleVariant } from '../BarTitle/bar-title-types';\n\nexport function resolveShellPageHeaderVariant(\n presentation: ShellPageHeaderPresentation,\n capacity: ShellPageCapacity,\n pageTopVisible: boolean\n): BarTitleVariant {\n if (presentation !== 'auto') return presentation;\n if (capacity === 'constrained') return 'constrained';\n if (capacity === 'compact' || !pageTopVisible) return 'compact';\n return 'expanded';\n}\n",
|
|
195
213
|
"type": "registry:ui"
|
|
196
214
|
},
|
|
197
215
|
{
|
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "shell-tools",
|
|
4
|
+
"title": "ShellTools",
|
|
5
|
+
"description": "Responsive persistent host for shell tools, adapting PanelTools rail and drawer chrome to full-stage mobile Search, Agents, and Inbox surfaces.",
|
|
6
|
+
"type": "registry:ui",
|
|
7
|
+
"meta": {
|
|
8
|
+
"library": "@ds-mo/ui",
|
|
9
|
+
"distribution": "npm",
|
|
10
|
+
"storybook": "https://zainadeel.github.io/compomo/?path=/story/shell-tools",
|
|
11
|
+
"source": "src/wc/components/ShellTools/ShellTools.tsx",
|
|
12
|
+
"intentStatus": "complete",
|
|
13
|
+
"intent": {
|
|
14
|
+
"audience": "specialized",
|
|
15
|
+
"status": "experimental",
|
|
16
|
+
"summary": "Responsive persistent host for shell tools, adapting PanelTools rail and drawer chrome to full-stage mobile Search, Agents, and Inbox surfaces.",
|
|
17
|
+
"useWhen": [
|
|
18
|
+
"A ShellApp must preserve the same Search, Agents, Messages, Stacks, Activity, and Help product owners across desktop, tablet, and mobile."
|
|
19
|
+
],
|
|
20
|
+
"avoidWhen": [
|
|
21
|
+
"The tools exist only in desktop and tablet chrome; PanelTools remains supported for that case.",
|
|
22
|
+
"The content is a routed primary application area."
|
|
23
|
+
],
|
|
24
|
+
"alternatives": [
|
|
25
|
+
{
|
|
26
|
+
"when": "Only legacy rail and drawer behavior is required.",
|
|
27
|
+
"component": "component:ds-panel-tools",
|
|
28
|
+
"reason": "PanelTools remains the backward-compatible desktop and tablet primitive."
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"commonlyComposedWith": [
|
|
32
|
+
"component:ds-shell-app",
|
|
33
|
+
"component:ds-panel-tool-header",
|
|
34
|
+
"component:ds-shell-mobile-bar"
|
|
35
|
+
],
|
|
36
|
+
"patterns": [
|
|
37
|
+
"pattern:application-shell"
|
|
38
|
+
],
|
|
39
|
+
"accessibility": [
|
|
40
|
+
"Only the active mobile tool view participates in focus or the accessibility tree.",
|
|
41
|
+
"Header Back and application-owned header actions preserve the PanelTools event contract.",
|
|
42
|
+
"Inbox uses TabGroup semantics for Messages, Stacks, and Activity."
|
|
43
|
+
],
|
|
44
|
+
"states": [
|
|
45
|
+
"The application owns open and activeTool and replaces headers when tool depth changes.",
|
|
46
|
+
"Messages, Stacks, and Activity map to one mobile Inbox destination while retaining the actual active tool id.",
|
|
47
|
+
"Switching responsive mode must not recreate application-owned slotted product elements."
|
|
48
|
+
],
|
|
49
|
+
"responsiveBehavior": [
|
|
50
|
+
"Desktop and tablet compose PanelTools-compatible rail and drawer chrome.",
|
|
51
|
+
"Mobile renders the active tool across the shell stage and groups available inbox tools in a segmented control."
|
|
52
|
+
],
|
|
53
|
+
"frameworkCaveats": {
|
|
54
|
+
"customElements": [
|
|
55
|
+
"Assign items and headers as JavaScript properties and place each persistent owner in its named *-view slot."
|
|
56
|
+
],
|
|
57
|
+
"react": [
|
|
58
|
+
"Keep stable keyed product owner elements in the generated wrapper's named slots."
|
|
59
|
+
],
|
|
60
|
+
"angular": [
|
|
61
|
+
"Bind structured items and headers as properties and keep each tool owner mounted under a named *-view slot."
|
|
62
|
+
]
|
|
63
|
+
},
|
|
64
|
+
"references": [
|
|
65
|
+
{
|
|
66
|
+
"label": "Storybook",
|
|
67
|
+
"path": "src/wc/components/ShellTools/ShellTools.stories.ts"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"label": "PanelTools contract",
|
|
71
|
+
"path": "src/wc/components/PanelTools/PanelTools.agent.json"
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
},
|
|
75
|
+
"api": {
|
|
76
|
+
"props": {
|
|
77
|
+
"activeTool": {
|
|
78
|
+
"type": "PanelToolsToolId | ''",
|
|
79
|
+
"resolvedType": "\"\" | \"activity\" | \"agents\" | \"help\" | \"messages\" | \"search\" | \"stacks\"",
|
|
80
|
+
"attribute": "active-tool",
|
|
81
|
+
"default": "''",
|
|
82
|
+
"required": false,
|
|
83
|
+
"mutable": true
|
|
84
|
+
},
|
|
85
|
+
"fullscreenHeaderMode": {
|
|
86
|
+
"type": "'shared' | 'split'",
|
|
87
|
+
"resolvedType": "\"shared\" | \"split\"",
|
|
88
|
+
"attribute": "fullscreen-header-mode",
|
|
89
|
+
"default": "'shared'",
|
|
90
|
+
"required": false,
|
|
91
|
+
"mutable": false
|
|
92
|
+
},
|
|
93
|
+
"headers": {
|
|
94
|
+
"type": "PanelToolsHeaders",
|
|
95
|
+
"resolvedType": "\"activity\" | \"agents\" | \"help\" | \"messages\" | \"search\" | \"stacks\" | PanelToolsHeaderConfig",
|
|
96
|
+
"default": "{}",
|
|
97
|
+
"required": false,
|
|
98
|
+
"mutable": false
|
|
99
|
+
},
|
|
100
|
+
"headersJson": {
|
|
101
|
+
"type": "string",
|
|
102
|
+
"resolvedType": "string",
|
|
103
|
+
"attribute": "headers-json",
|
|
104
|
+
"default": "''",
|
|
105
|
+
"required": false,
|
|
106
|
+
"mutable": false
|
|
107
|
+
},
|
|
108
|
+
"inboxLabel": {
|
|
109
|
+
"type": "string",
|
|
110
|
+
"resolvedType": "string",
|
|
111
|
+
"attribute": "inbox-label",
|
|
112
|
+
"default": "'Inbox'",
|
|
113
|
+
"required": false,
|
|
114
|
+
"mutable": false
|
|
115
|
+
},
|
|
116
|
+
"inboxNavigationLabel": {
|
|
117
|
+
"type": "string",
|
|
118
|
+
"resolvedType": "string",
|
|
119
|
+
"attribute": "inbox-navigation-label",
|
|
120
|
+
"default": "'Inbox sections'",
|
|
121
|
+
"required": false,
|
|
122
|
+
"mutable": false
|
|
123
|
+
},
|
|
124
|
+
"items": {
|
|
125
|
+
"type": "PanelToolsItem[]",
|
|
126
|
+
"resolvedType": "PanelToolsItem[]",
|
|
127
|
+
"default": "[]",
|
|
128
|
+
"required": false,
|
|
129
|
+
"mutable": false
|
|
130
|
+
},
|
|
131
|
+
"itemsJson": {
|
|
132
|
+
"type": "string",
|
|
133
|
+
"resolvedType": "string",
|
|
134
|
+
"attribute": "items-json",
|
|
135
|
+
"default": "''",
|
|
136
|
+
"required": false,
|
|
137
|
+
"mutable": false
|
|
138
|
+
},
|
|
139
|
+
"open": {
|
|
140
|
+
"type": "boolean",
|
|
141
|
+
"resolvedType": "boolean",
|
|
142
|
+
"attribute": "open",
|
|
143
|
+
"default": "false",
|
|
144
|
+
"required": false,
|
|
145
|
+
"mutable": true
|
|
146
|
+
},
|
|
147
|
+
"presentation": {
|
|
148
|
+
"type": "'drawer' | 'fullscreen'",
|
|
149
|
+
"resolvedType": "\"drawer\" | \"fullscreen\"",
|
|
150
|
+
"attribute": "presentation",
|
|
151
|
+
"default": "'drawer'",
|
|
152
|
+
"required": false,
|
|
153
|
+
"mutable": true
|
|
154
|
+
},
|
|
155
|
+
"responsiveMode": {
|
|
156
|
+
"type": "ShellResponsiveMode",
|
|
157
|
+
"resolvedType": "\"desktop\" | \"mobile\" | \"tablet\"",
|
|
158
|
+
"attribute": "responsive-mode",
|
|
159
|
+
"default": "'desktop'",
|
|
160
|
+
"required": false,
|
|
161
|
+
"mutable": false,
|
|
162
|
+
"description": "Resolved by ShellApp; mobile uses a full-stage tool presentation."
|
|
163
|
+
},
|
|
164
|
+
"storageKey": {
|
|
165
|
+
"type": "string",
|
|
166
|
+
"resolvedType": "string",
|
|
167
|
+
"attribute": "storage-key",
|
|
168
|
+
"default": "''",
|
|
169
|
+
"required": false,
|
|
170
|
+
"mutable": false
|
|
171
|
+
},
|
|
172
|
+
"toolShortcutsLabel": {
|
|
173
|
+
"type": "string",
|
|
174
|
+
"resolvedType": "string",
|
|
175
|
+
"attribute": "tool-shortcuts-label",
|
|
176
|
+
"default": "'Tool shortcuts'",
|
|
177
|
+
"required": false,
|
|
178
|
+
"mutable": false
|
|
179
|
+
},
|
|
180
|
+
"toolsLabel": {
|
|
181
|
+
"type": "string",
|
|
182
|
+
"resolvedType": "string",
|
|
183
|
+
"attribute": "tools-label",
|
|
184
|
+
"default": "'Tools'",
|
|
185
|
+
"required": false,
|
|
186
|
+
"mutable": false
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
"events": [
|
|
190
|
+
{
|
|
191
|
+
"name": "dsHeaderAction",
|
|
192
|
+
"detail": "{\n tool: PanelToolsToolId;\n id: string;\n }",
|
|
193
|
+
"bubbles": true,
|
|
194
|
+
"cancelable": true,
|
|
195
|
+
"composed": true
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"name": "dsHeaderBack",
|
|
199
|
+
"detail": "{\n tool: PanelToolsToolId;\n }",
|
|
200
|
+
"bubbles": true,
|
|
201
|
+
"cancelable": true,
|
|
202
|
+
"composed": true
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
"name": "dsPresentationChange",
|
|
206
|
+
"detail": "{\n presentation: 'drawer' | 'fullscreen';\n }",
|
|
207
|
+
"bubbles": true,
|
|
208
|
+
"cancelable": true,
|
|
209
|
+
"composed": true
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"name": "dsToolChange",
|
|
213
|
+
"detail": "{\n id: PanelToolsToolId;\n selected: boolean;\n }",
|
|
214
|
+
"bubbles": true,
|
|
215
|
+
"cancelable": true,
|
|
216
|
+
"composed": true
|
|
217
|
+
}
|
|
218
|
+
],
|
|
219
|
+
"methods": [
|
|
220
|
+
{
|
|
221
|
+
"name": "activateTool",
|
|
222
|
+
"signature": "activateTool(id: PanelToolsToolId) => Promise<void>",
|
|
223
|
+
"description": "Match PanelTools' imperative activation contract in every responsive mode."
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
"name": "closeDrawer",
|
|
227
|
+
"signature": "closeDrawer() => Promise<void>",
|
|
228
|
+
"description": "Close the active global tool surface."
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
"name": "focusHeaderAction",
|
|
232
|
+
"signature": "focusHeaderAction(id: string) => Promise<void>",
|
|
233
|
+
"description": "Focus an application-owned action in the visible shared tool header."
|
|
234
|
+
}
|
|
235
|
+
],
|
|
236
|
+
"slots": []
|
|
237
|
+
},
|
|
238
|
+
"props": {
|
|
239
|
+
"activeTool": {
|
|
240
|
+
"type": "PanelToolsToolId | ''",
|
|
241
|
+
"resolvedType": "\"\" | \"activity\" | \"agents\" | \"help\" | \"messages\" | \"search\" | \"stacks\"",
|
|
242
|
+
"attribute": "active-tool",
|
|
243
|
+
"default": "''",
|
|
244
|
+
"required": false,
|
|
245
|
+
"mutable": true
|
|
246
|
+
},
|
|
247
|
+
"fullscreenHeaderMode": {
|
|
248
|
+
"type": "'shared' | 'split'",
|
|
249
|
+
"resolvedType": "\"shared\" | \"split\"",
|
|
250
|
+
"attribute": "fullscreen-header-mode",
|
|
251
|
+
"default": "'shared'",
|
|
252
|
+
"required": false,
|
|
253
|
+
"mutable": false
|
|
254
|
+
},
|
|
255
|
+
"headers": {
|
|
256
|
+
"type": "PanelToolsHeaders",
|
|
257
|
+
"resolvedType": "\"activity\" | \"agents\" | \"help\" | \"messages\" | \"search\" | \"stacks\" | PanelToolsHeaderConfig",
|
|
258
|
+
"default": "{}",
|
|
259
|
+
"required": false,
|
|
260
|
+
"mutable": false
|
|
261
|
+
},
|
|
262
|
+
"headersJson": {
|
|
263
|
+
"type": "string",
|
|
264
|
+
"resolvedType": "string",
|
|
265
|
+
"attribute": "headers-json",
|
|
266
|
+
"default": "''",
|
|
267
|
+
"required": false,
|
|
268
|
+
"mutable": false
|
|
269
|
+
},
|
|
270
|
+
"inboxLabel": {
|
|
271
|
+
"type": "string",
|
|
272
|
+
"resolvedType": "string",
|
|
273
|
+
"attribute": "inbox-label",
|
|
274
|
+
"default": "'Inbox'",
|
|
275
|
+
"required": false,
|
|
276
|
+
"mutable": false
|
|
277
|
+
},
|
|
278
|
+
"inboxNavigationLabel": {
|
|
279
|
+
"type": "string",
|
|
280
|
+
"resolvedType": "string",
|
|
281
|
+
"attribute": "inbox-navigation-label",
|
|
282
|
+
"default": "'Inbox sections'",
|
|
283
|
+
"required": false,
|
|
284
|
+
"mutable": false
|
|
285
|
+
},
|
|
286
|
+
"items": {
|
|
287
|
+
"type": "PanelToolsItem[]",
|
|
288
|
+
"resolvedType": "PanelToolsItem[]",
|
|
289
|
+
"default": "[]",
|
|
290
|
+
"required": false,
|
|
291
|
+
"mutable": false
|
|
292
|
+
},
|
|
293
|
+
"itemsJson": {
|
|
294
|
+
"type": "string",
|
|
295
|
+
"resolvedType": "string",
|
|
296
|
+
"attribute": "items-json",
|
|
297
|
+
"default": "''",
|
|
298
|
+
"required": false,
|
|
299
|
+
"mutable": false
|
|
300
|
+
},
|
|
301
|
+
"open": {
|
|
302
|
+
"type": "boolean",
|
|
303
|
+
"resolvedType": "boolean",
|
|
304
|
+
"attribute": "open",
|
|
305
|
+
"default": "false",
|
|
306
|
+
"required": false,
|
|
307
|
+
"mutable": true
|
|
308
|
+
},
|
|
309
|
+
"presentation": {
|
|
310
|
+
"type": "'drawer' | 'fullscreen'",
|
|
311
|
+
"resolvedType": "\"drawer\" | \"fullscreen\"",
|
|
312
|
+
"attribute": "presentation",
|
|
313
|
+
"default": "'drawer'",
|
|
314
|
+
"required": false,
|
|
315
|
+
"mutable": true
|
|
316
|
+
},
|
|
317
|
+
"responsiveMode": {
|
|
318
|
+
"type": "ShellResponsiveMode",
|
|
319
|
+
"resolvedType": "\"desktop\" | \"mobile\" | \"tablet\"",
|
|
320
|
+
"attribute": "responsive-mode",
|
|
321
|
+
"default": "'desktop'",
|
|
322
|
+
"required": false,
|
|
323
|
+
"mutable": false,
|
|
324
|
+
"description": "Resolved by ShellApp; mobile uses a full-stage tool presentation."
|
|
325
|
+
},
|
|
326
|
+
"storageKey": {
|
|
327
|
+
"type": "string",
|
|
328
|
+
"resolvedType": "string",
|
|
329
|
+
"attribute": "storage-key",
|
|
330
|
+
"default": "''",
|
|
331
|
+
"required": false,
|
|
332
|
+
"mutable": false
|
|
333
|
+
},
|
|
334
|
+
"toolShortcutsLabel": {
|
|
335
|
+
"type": "string",
|
|
336
|
+
"resolvedType": "string",
|
|
337
|
+
"attribute": "tool-shortcuts-label",
|
|
338
|
+
"default": "'Tool shortcuts'",
|
|
339
|
+
"required": false,
|
|
340
|
+
"mutable": false
|
|
341
|
+
},
|
|
342
|
+
"toolsLabel": {
|
|
343
|
+
"type": "string",
|
|
344
|
+
"resolvedType": "string",
|
|
345
|
+
"attribute": "tools-label",
|
|
346
|
+
"default": "'Tools'",
|
|
347
|
+
"required": false,
|
|
348
|
+
"mutable": false
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
"events": [
|
|
352
|
+
{
|
|
353
|
+
"name": "dsHeaderAction",
|
|
354
|
+
"detail": "{\n tool: PanelToolsToolId;\n id: string;\n }",
|
|
355
|
+
"bubbles": true,
|
|
356
|
+
"cancelable": true,
|
|
357
|
+
"composed": true
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
"name": "dsHeaderBack",
|
|
361
|
+
"detail": "{\n tool: PanelToolsToolId;\n }",
|
|
362
|
+
"bubbles": true,
|
|
363
|
+
"cancelable": true,
|
|
364
|
+
"composed": true
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
"name": "dsPresentationChange",
|
|
368
|
+
"detail": "{\n presentation: 'drawer' | 'fullscreen';\n }",
|
|
369
|
+
"bubbles": true,
|
|
370
|
+
"cancelable": true,
|
|
371
|
+
"composed": true
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
"name": "dsToolChange",
|
|
375
|
+
"detail": "{\n id: PanelToolsToolId;\n selected: boolean;\n }",
|
|
376
|
+
"bubbles": true,
|
|
377
|
+
"cancelable": true,
|
|
378
|
+
"composed": true
|
|
379
|
+
}
|
|
380
|
+
],
|
|
381
|
+
"methods": [
|
|
382
|
+
{
|
|
383
|
+
"name": "activateTool",
|
|
384
|
+
"signature": "activateTool(id: PanelToolsToolId) => Promise<void>",
|
|
385
|
+
"description": "Match PanelTools' imperative activation contract in every responsive mode."
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
"name": "closeDrawer",
|
|
389
|
+
"signature": "closeDrawer() => Promise<void>",
|
|
390
|
+
"description": "Close the active global tool surface."
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
"name": "focusHeaderAction",
|
|
394
|
+
"signature": "focusHeaderAction(id: string) => Promise<void>",
|
|
395
|
+
"description": "Focus an application-owned action in the visible shared tool header."
|
|
396
|
+
}
|
|
397
|
+
],
|
|
398
|
+
"slots": [],
|
|
399
|
+
"exports": {
|
|
400
|
+
"customElement": "ds-shell-tools",
|
|
401
|
+
"react": "DsShellTools",
|
|
402
|
+
"angular": "DsShellTools"
|
|
403
|
+
},
|
|
404
|
+
"consumption": {
|
|
405
|
+
"install": "npm install @ds-mo/ui @ds-mo/tokens @ds-mo/icons",
|
|
406
|
+
"cssSetup": "import '@ds-mo/tokens';\nimport '@ds-mo/tokens/reset';\nimport '@ds-mo/tokens/globals';",
|
|
407
|
+
"customElements": {
|
|
408
|
+
"import": "import '@ds-mo/ui/dist/components/ds-shell-tools.js';",
|
|
409
|
+
"example": "<ds-shell-tools></ds-shell-tools>"
|
|
410
|
+
},
|
|
411
|
+
"react": {
|
|
412
|
+
"import": "import { DsShellTools } from '@ds-mo/ui/react';",
|
|
413
|
+
"example": "<DsShellTools />"
|
|
414
|
+
},
|
|
415
|
+
"angular": {
|
|
416
|
+
"import": "import { DsShellTools } from '@ds-mo/ui/angular/ds-shell-tools';",
|
|
417
|
+
"example": "<ds-shell-tools></ds-shell-tools>"
|
|
418
|
+
},
|
|
419
|
+
"complexPropertyNote": "Assign these non-primitive values as JavaScript properties: headers, items.",
|
|
420
|
+
"peerDependencies": {
|
|
421
|
+
"required": [
|
|
422
|
+
"@ds-mo/tokens ^3.1.1",
|
|
423
|
+
"@ds-mo/icons ^6.4.0"
|
|
424
|
+
],
|
|
425
|
+
"frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
"dependencies": [
|
|
430
|
+
"@ds-mo/ui",
|
|
431
|
+
"@ds-mo/tokens"
|
|
432
|
+
],
|
|
433
|
+
"registryDependencies": [
|
|
434
|
+
"panel-tool-header",
|
|
435
|
+
"panel-tools",
|
|
436
|
+
"tab-group"
|
|
437
|
+
],
|
|
438
|
+
"files": [
|
|
439
|
+
{
|
|
440
|
+
"path": "src/wc/components/ShellTools/ShellTools.css",
|
|
441
|
+
"content": ":host {\n display: block;\n min-width: 0;\n min-height: 0;\n height: 100%;\n background: var(--color-background-primary);\n}\n\n.shell-tools__desktop,\n.shell-tools__desktop ds-panel-tools,\n.shell-tools__mobile {\n display: block;\n min-width: 0;\n min-height: 0;\n height: 100%;\n}\n\n.shell-tools__mobile {\n display: grid;\n grid-template-rows: auto auto minmax(0, 1fr);\n background: var(--color-background-primary);\n}\n\n.shell-tools__inbox-nav {\n padding: var(--dimension-space-100);\n border-bottom: var(--dimension-stroke-width-012) solid var(--color-border-tertiary);\n background: var(--color-background-primary);\n}\n\n.shell-tools__mobile-body,\n.shell-tools__view {\n min-width: 0;\n min-height: 0;\n height: 100%;\n}\n\n.shell-tools__mobile-body {\n overflow: hidden;\n}\n\n.shell-tools__view {\n overflow: auto;\n overscroll-behavior: contain;\n}\n\n.shell-tools__view[hidden] {\n display: none;\n}\n\n.shell-tools__view ::slotted(*) {\n min-height: 0;\n}\n",
|
|
442
|
+
"type": "registry:ui"
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
"path": "src/wc/components/ShellTools/ShellTools.tsx",
|
|
446
|
+
"content": "import {\n Component,\n Element,\n Event,\n EventEmitter,\n h,\n Host,\n Method,\n Prop,\n Watch,\n} from '@stencil/core';\nimport {\n PANEL_TOOLS_LABELS,\n PANEL_TOOLS_TOOL_IDS,\n type PanelToolsHeaderAction,\n type PanelToolsHeaderConfig,\n type PanelToolsHeaders,\n type PanelToolsItem,\n type PanelToolsToolId,\n} from '../PanelTools/panel-tools-types';\nimport {\n parsePanelToolsItems,\n reconcilePanelToolsAvailability,\n resolvePanelToolActivation,\n} from '../PanelTools/panel-tools-utils';\nimport {\n isShellInboxTool,\n resolveAvailableInboxTool,\n type ShellInboxToolId,\n type ShellResponsiveMode,\n} from '../../shell/shell-responsive';\n\nconst MOBILE_VIEW_ORDER: PanelToolsToolId[] = [\n 'search',\n 'agents',\n 'messages',\n 'stacks',\n 'activity',\n 'help',\n];\n\n@Component({\n tag: 'ds-shell-tools',\n styleUrl: 'ShellTools.css',\n scoped: true,\n})\nexport class ShellTools {\n @Element() el!: HTMLElement;\n\n /** Resolved by ShellApp; mobile uses a full-stage tool presentation. */\n @Prop({ attribute: 'responsive-mode', reflect: true })\n responsiveMode: ShellResponsiveMode = 'desktop';\n @Prop({ mutable: true, reflect: true }) open: boolean = false;\n @Prop({ mutable: true, attribute: 'active-tool', reflect: true })\n activeTool: PanelToolsToolId | '' = '';\n @Prop({ mutable: true, reflect: true }) presentation: 'drawer' | 'fullscreen' = 'drawer';\n @Prop({ attribute: 'fullscreen-header-mode', reflect: true })\n fullscreenHeaderMode: 'shared' | 'split' = 'shared';\n @Prop() items: PanelToolsItem[] = [];\n @Prop({ attribute: 'items-json' }) itemsJson: string = '';\n @Prop() headers: PanelToolsHeaders = {};\n @Prop({ attribute: 'headers-json' }) headersJson: string = '';\n @Prop({ attribute: 'storage-key' }) storageKey: string = '';\n @Prop() toolsLabel: string = 'Tools';\n @Prop() toolShortcutsLabel: string = 'Tool shortcuts';\n @Prop() inboxLabel: string = 'Inbox';\n @Prop() inboxNavigationLabel: string = 'Inbox sections';\n\n @Event({ bubbles: true, composed: true }) dsToolChange!: EventEmitter<{\n id: PanelToolsToolId;\n selected: boolean;\n }>;\n @Event({ bubbles: true, composed: true }) dsPresentationChange!: EventEmitter<{\n presentation: 'drawer' | 'fullscreen';\n }>;\n @Event({ bubbles: true, composed: true }) dsHeaderBack!: EventEmitter<{\n tool: PanelToolsToolId;\n }>;\n @Event({ bubbles: true, composed: true }) dsHeaderAction!: EventEmitter<{\n tool: PanelToolsToolId;\n id: string;\n }>;\n\n private panelToolsEl: HTMLDsPanelToolsElement | null = null;\n\n private get resolvedItems(): PanelToolsItem[] {\n return parsePanelToolsItems(this.items, this.itemsJson);\n }\n\n private get availableInboxTools(): ShellInboxToolId[] {\n return this.resolvedItems\n .filter(item => !item.isInactive && isShellInboxTool(item.id))\n .map(item => item.id as ShellInboxToolId);\n }\n\n private get mobileActiveTool(): PanelToolsToolId | '' {\n if (!isShellInboxTool(this.activeTool)) return this.activeTool;\n return resolveAvailableInboxTool(this.activeTool, this.availableInboxTools);\n }\n\n componentWillLoad() {\n this.reconcileAvailability();\n }\n\n @Watch('items')\n @Watch('itemsJson')\n handleItemsChange() {\n this.reconcileAvailability();\n }\n\n private reconcileAvailability() {\n const next = reconcilePanelToolsAvailability(this.resolvedItems, this.open, this.activeTool);\n this.open = next.open;\n this.activeTool = next.activeTool;\n }\n\n private resolvedHeaders(): PanelToolsHeaders {\n if (Object.keys(this.headers).length) return this.headers;\n if (!this.headersJson.trim()) return {};\n try {\n const parsed = JSON.parse(this.headersJson);\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as PanelToolsHeaders)\n : {};\n } catch {\n return {};\n }\n }\n\n private activeHeader(tool: PanelToolsToolId): PanelToolsHeaderConfig {\n return this.resolvedHeaders()[tool] ?? {};\n }\n\n private activateMobileTool(id: PanelToolsToolId) {\n const item = this.resolvedItems.find(entry => entry.id === id);\n if (!item || item.isInactive) return;\n const next = resolvePanelToolActivation(this.open, this.activeTool, id);\n this.open = next.open;\n this.activeTool = next.activeTool;\n this.dsToolChange.emit({ id, selected: next.selected });\n }\n\n private selectInboxTool = (event: CustomEvent<string>) => {\n const id = event.detail;\n if (!isShellInboxTool(id) || id === this.mobileActiveTool) return;\n this.open = true;\n this.activeTool = id;\n this.dsToolChange.emit({ id, selected: true });\n };\n\n private handlePanelToolChange = (\n event: CustomEvent<{ id: PanelToolsToolId; selected: boolean }>\n ) => {\n this.activeTool = event.detail.id;\n this.open = event.detail.selected;\n };\n\n private handlePanelPresentationChange = (\n event: CustomEvent<{ presentation: 'drawer' | 'fullscreen' }>\n ) => {\n this.presentation = event.detail.presentation;\n };\n\n private handleMobileHeaderAction = (\n tool: PanelToolsToolId,\n event: CustomEvent<{ id: string }>\n ) => {\n event.stopPropagation();\n this.dsHeaderAction.emit({ tool, id: event.detail.id });\n };\n\n /** Match PanelTools' imperative activation contract in every responsive mode. */\n @Method()\n async activateTool(id: PanelToolsToolId) {\n if (this.responsiveMode !== 'mobile') {\n await this.panelToolsEl?.activateTool(id);\n return;\n }\n this.activateMobileTool(id);\n }\n\n /** Close the active global tool surface. */\n @Method()\n async closeDrawer() {\n if (this.responsiveMode !== 'mobile') {\n await this.panelToolsEl?.closeDrawer();\n return;\n }\n if (!this.open) return;\n const id = this.activeTool;\n this.open = false;\n if (id) this.dsToolChange.emit({ id, selected: false });\n }\n\n /** Focus an application-owned action in the visible shared tool header. */\n @Method()\n async focusHeaderAction(id: string) {\n if (this.responsiveMode !== 'mobile') {\n await this.panelToolsEl?.focusHeaderAction(id);\n return;\n }\n const action = this.el.querySelector(`[data-header-action-id=\"${CSS.escape(id)}\"]`) as\n | (HTMLElement & { setFocus?: () => Promise<void> })\n | null;\n await action?.setFocus?.();\n }\n\n private renderForwardedSlots() {\n return PANEL_TOOLS_TOOL_IDS.flatMap(id => [\n <slot name={id} slot={id} />,\n <slot name={`${id}-view`} slot={`${id}-view`} />,\n ]);\n }\n\n private renderDesktop() {\n return (\n <div class=\"shell-tools__desktop\">\n <ds-panel-tools\n open={this.open}\n activeTool={this.activeTool}\n presentation={this.presentation}\n fullscreenHeaderMode={this.fullscreenHeaderMode}\n items={this.resolvedItems}\n headers={this.resolvedHeaders()}\n storageKey={this.storageKey}\n toolsLabel={this.toolsLabel}\n toolShortcutsLabel={this.toolShortcutsLabel}\n ref={element => {\n this.panelToolsEl = element as HTMLDsPanelToolsElement | null;\n }}\n onDsToolChange={this.handlePanelToolChange}\n onDsPresentationChange={this.handlePanelPresentationChange}\n >\n {this.renderForwardedSlots()}\n </ds-panel-tools>\n </div>\n );\n }\n\n private renderMobileHeader(tool: PanelToolsToolId) {\n const header = this.activeHeader(tool);\n const configuredTitle = header.title?.trim();\n const title = isShellInboxTool(tool)\n ? header.showBack && configuredTitle\n ? configuredTitle\n : this.inboxLabel\n : configuredTitle || PANEL_TOOLS_LABELS[tool];\n const actions = header.actions ?? [];\n\n return (\n <ds-panel-tool-header\n class=\"shell-tools__mobile-header\"\n heading={title}\n showBack={header.showBack ?? false}\n backIcon={header.backIcon || 'ChevronLeft'}\n backAriaLabel={header.backAriaLabel || 'Back'}\n showMenu={false}\n ref={element => {\n const toolHeader = element as\n | (HTMLDsPanelToolHeaderElement & { actions: PanelToolsHeaderAction[] })\n | undefined;\n if (toolHeader) toolHeader.actions = actions;\n }}\n onDsBack={() => this.dsHeaderBack.emit({ tool })}\n onDsAction={(event: CustomEvent<{ id: string }>) =>\n this.handleMobileHeaderAction(tool, event)}\n />\n );\n }\n\n private renderMobile() {\n const tool = this.mobileActiveTool;\n const inboxTabs = this.availableInboxTools.map(id => ({\n id,\n label: PANEL_TOOLS_LABELS[id],\n }));\n\n return (\n <div\n class=\"shell-tools__mobile\"\n aria-hidden={this.open && tool ? undefined : 'true'}\n inert={this.open && tool ? undefined : true}\n >\n {tool ? this.renderMobileHeader(tool) : null}\n {tool && isShellInboxTool(tool) && inboxTabs.length ? (\n <div class=\"shell-tools__inbox-nav\">\n <ds-tab-group\n tabs={inboxTabs}\n value={tool}\n aria-label={this.inboxNavigationLabel}\n onDsChange={this.selectInboxTool}\n />\n </div>\n ) : null}\n <div class=\"shell-tools__mobile-body\">\n {MOBILE_VIEW_ORDER.map(id => {\n const active = this.open && id === tool;\n return (\n <div\n class={{\n 'shell-tools__view': true,\n 'shell-tools__view--active': active,\n }}\n hidden={!active}\n aria-hidden={active ? undefined : 'true'}\n inert={active ? undefined : true}\n >\n <slot name={`${id}-view`}>\n <slot name={id} />\n </slot>\n </div>\n );\n })}\n </div>\n </div>\n );\n }\n\n render() {\n return (\n <Host\n role={this.presentation === 'fullscreen' ? 'dialog' : 'complementary'}\n aria-modal={this.presentation === 'fullscreen' ? 'true' : undefined}\n aria-label={this.toolsLabel}\n >\n {this.responsiveMode === 'mobile' ? this.renderMobile() : this.renderDesktop()}\n </Host>\n );\n }\n}\n",
|
|
447
|
+
"type": "registry:ui"
|
|
448
|
+
}
|
|
449
|
+
]
|
|
450
|
+
}
|