@ds-mo/ui 13.4.0 → 13.6.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 +8 -1
- package/dist/agent.json +187 -26
- package/dist/angular/ds-banner.d.ts +23 -0
- package/dist/angular/ds-banner.js +45 -0
- package/dist/angular/ds-modal.d.ts +1 -1
- package/dist/angular/ds-modal.js +3 -3
- package/dist/angular/index.d.ts +1 -1
- package/dist/angular/index.js +1 -0
- package/dist/angular/proxies.d.ts +1 -0
- package/dist/angular/proxies.js +1 -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-banner.d.ts +11 -0
- package/dist/components/ds-banner.js +2 -0
- package/dist/components/ds-banner.js.map +1 -0
- package/dist/components/ds-bar-nav.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-unfilled.js +1 -1
- package/dist/components/ds-card-data-viz.js +1 -1
- package/dist/components/ds-card-overview.js +1 -1
- package/dist/components/ds-card-overview.js.map +1 -1
- package/dist/components/ds-card-setting.js +1 -1
- package/dist/components/ds-card-setting.js.map +1 -1
- package/dist/components/ds-chart-bar.js +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-line.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-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-input.js.map +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-actions.js.map +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-modal.js.map +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-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.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-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-tag.js +1 -1
- package/dist/components/ds-text.js +1 -1
- package/dist/components/ds-toast.js +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/{p-BN9FZESH.js → p-1S9AW2MA.js} +2 -2
- package/dist/components/{p-D4bVE7e4.js → p-6B411hQA.js} +2 -2
- package/dist/components/{p-D5PN6zdD.js → p-B-kRByho.js} +2 -2
- package/dist/components/{p-CmUGA_q4.js → p-B4_0Ys4w.js} +2 -2
- package/dist/components/{p-BApNi_xA.js → p-B6RrZQqw.js} +2 -2
- package/dist/components/{p-CQ4vAZ9j.js → p-BBtjABBS.js} +2 -2
- package/dist/components/{p-CmMfRM1s.js → p-BFfHIu6y.js} +2 -2
- package/dist/components/{p-CBiAsFAZ.js → p-BPyymeuR.js} +2 -2
- package/dist/components/{p-Bgh0uG51.js → p-BR_oMaas.js} +2 -2
- package/dist/components/{p-w2kKeG_h.js → p-Bb4f5SuF.js} +2 -2
- package/dist/components/p-Bl6A5AJq.js +2 -0
- package/dist/components/{p-CIgag7g2.js.map → p-Bl6A5AJq.js.map} +1 -1
- package/dist/components/{p-B5snQKiP.js → p-BmNpPyzf.js} +2 -2
- package/dist/components/{p-CWItD0gL.js → p-Bmo4T96l.js} +2 -2
- package/dist/components/{p-DHAuidt1.js → p-Bsfpxto1.js} +2 -2
- package/dist/components/{p-a6cnCKWk.js → p-BtuYufL3.js} +2 -2
- package/dist/components/{p-BxEOlO5k.js → p-CKRJX0Y0.js} +2 -2
- package/dist/components/{p-1moOgMI-.js → p-Ca6s5fLR.js} +2 -2
- package/dist/components/{p-RT1mts9U.js → p-CbjFUpOj.js} +2 -2
- package/dist/components/{p-CqOswTy8.js → p-CuJ3J6OJ.js} +2 -2
- package/dist/components/{p-BQTJ7ePr.js → p-CxgMUvqg.js} +2 -2
- package/dist/components/{p-CWKrq35a.js → p-D0bM3cKj.js} +2 -2
- package/dist/components/{p-UtfErnpX.js → p-D2cjoj2r.js} +2 -2
- package/dist/components/{p-CGwVooB6.js → p-DHF5cLzr.js} +2 -2
- package/dist/components/{p-BgjtMMye.js → p-DVuxzrCC.js} +2 -2
- package/dist/components/p-DX4c3kZ1.js +2 -0
- package/dist/components/p-DX4c3kZ1.js.map +1 -0
- package/dist/components/p-DaqFuVKZ.js +2 -0
- package/dist/components/p-DaqFuVKZ.js.map +1 -0
- package/dist/components/{p-DXGKfXTh.js → p-DfvDNcoq.js} +2 -2
- package/dist/components/p-Di81y9Q2.js +2 -0
- package/dist/components/{p-C_3mZMFK.js → p-Eep5mxtv.js} +2 -2
- package/dist/components/{p-DoNdiEly.js → p-IYy-jODg.js} +2 -2
- package/dist/components/{p-DtV263qO.js → p-iCjUWcp7.js} +2 -2
- package/dist/components/{p-BgASe-BH.js → p-og48Kh2i.js} +2 -2
- package/dist/components/{p-BSocHH2X.js → p-wOOAzVMC.js} +2 -2
- package/dist/docs/components.json +445 -16
- package/dist/mcp-data/registry/agent-activity.json +1 -1
- package/dist/mcp-data/registry/agent-response.json +1 -1
- package/dist/mcp-data/registry/agent-source-list.json +1 -1
- package/dist/mcp-data/registry/agent-tool-call.json +1 -1
- package/dist/mcp-data/registry/attachment-list.json +1 -1
- package/dist/mcp-data/registry/avatar.json +1 -1
- package/dist/mcp-data/registry/badge.json +1 -1
- package/dist/mcp-data/registry/banner.json +333 -0
- package/dist/mcp-data/registry/bar-nav.json +1 -1
- package/dist/mcp-data/registry/bar-title.json +4 -4
- package/dist/mcp-data/registry/bar-workflow.json +1 -1
- package/dist/mcp-data/registry/breadcrumb.json +1 -1
- package/dist/mcp-data/registry/button-filled.json +1 -1
- package/dist/mcp-data/registry/button-unfilled.json +1 -1
- package/dist/mcp-data/registry/card-data-viz.json +1 -1
- package/dist/mcp-data/registry/card-overview.json +33 -35
- package/dist/mcp-data/registry/card-setting.json +1 -1
- package/dist/mcp-data/registry/chart-bar.json +1 -1
- package/dist/mcp-data/registry/chart-donut.json +1 -1
- package/dist/mcp-data/registry/chart-legend.json +1 -1
- package/dist/mcp-data/registry/chart-line.json +1 -1
- package/dist/mcp-data/registry/checkbox.json +1 -1
- package/dist/mcp-data/registry/chip.json +1 -1
- package/dist/mcp-data/registry/code-block.json +1 -1
- package/dist/mcp-data/registry/conversation-list-item.json +1 -1
- package/dist/mcp-data/registry/conversation-list-section.json +1 -1
- package/dist/mcp-data/registry/conversation-list.json +1 -1
- package/dist/mcp-data/registry/divider.json +1 -1
- package/dist/mcp-data/registry/empty-state.json +1 -1
- package/dist/mcp-data/registry/field.json +1 -1
- package/dist/mcp-data/registry/icon.json +1 -1
- package/dist/mcp-data/registry/input.json +6 -6
- package/dist/mcp-data/registry/loader.json +1 -1
- package/dist/mcp-data/registry/markdown.json +1 -1
- package/dist/mcp-data/registry/menu.json +1 -1
- package/dist/mcp-data/registry/message-actions.json +3 -2
- package/dist/mcp-data/registry/message-bubble.json +1 -1
- package/dist/mcp-data/registry/message-composer.json +1 -1
- package/dist/mcp-data/registry/message-scroller.json +1 -1
- package/dist/mcp-data/registry/message.json +1 -1
- package/dist/mcp-data/registry/mobile-bar-nav.json +1 -1
- package/dist/mcp-data/registry/mobile-header.json +1 -1
- package/dist/mcp-data/registry/mobile-section-switcher.json +1 -1
- package/dist/mcp-data/registry/mobile-sheet-nav.json +1 -1
- package/dist/mcp-data/registry/modal.json +26 -6
- package/dist/mcp-data/registry/panel-nav.json +1 -1
- package/dist/mcp-data/registry/panel-sub-nav.json +1 -1
- package/dist/mcp-data/registry/panel-tool-header.json +4 -4
- package/dist/mcp-data/registry/panel-tool-search.json +1 -1
- package/dist/mcp-data/registry/panel-tools.json +4 -3
- package/dist/mcp-data/registry/radio.json +1 -1
- package/dist/mcp-data/registry/registry.json +190 -26
- package/dist/mcp-data/registry/scroll-overlay.json +1 -1
- package/dist/mcp-data/registry/select.json +5 -5
- package/dist/mcp-data/registry/shell-app.json +7 -3
- package/dist/mcp-data/registry/shell-page.json +1 -1
- package/dist/mcp-data/registry/shell-tools.json +1 -1
- package/dist/mcp-data/registry/skeleton.json +1 -1
- package/dist/mcp-data/registry/slider.json +1 -1
- package/dist/mcp-data/registry/swatch-picker.json +1 -1
- package/dist/mcp-data/registry/switch.json +1 -1
- package/dist/mcp-data/registry/tab-group.json +1 -1
- package/dist/mcp-data/registry/tag.json +1 -1
- package/dist/mcp-data/registry/text.json +1 -1
- package/dist/mcp-data/registry/toast.json +1 -1
- package/dist/mcp-data/registry/tooltip-data-viz.json +1 -1
- package/dist/mcp-data/registry/tooltip.json +1 -1
- package/dist/mcp-data/registry/typing-indicator.json +1 -1
- package/dist/react/components.d.ts +1 -0
- package/dist/react/components.js +1 -0
- package/dist/react/ds-banner.d.ts +13 -0
- package/dist/react/ds-banner.js +15 -0
- package/dist/types/components/Banner/Banner.d.ts +50 -0
- package/dist/types/components/CardOverview/CardOverview.d.ts +10 -9
- package/dist/types/components/CardOverview/card-overview-controller.d.ts +13 -1
- package/dist/types/components/CardOverview/card-overview-types.d.ts +8 -2
- package/dist/types/components/CardOverview/index.d.ts +1 -1
- package/dist/types/components/Input/Input.d.ts +1 -1
- package/dist/types/components/Modal/Modal.d.ts +4 -0
- package/dist/types/components/PanelTools/PanelTools.d.ts +7 -0
- package/dist/types/components/Select/Select.d.ts +1 -1
- package/dist/types/components.d.ts +154 -16
- package/package.json +3 -3
- package/dist/components/p-4scN0Mgn.js +0 -2
- package/dist/components/p-B3PdrMP0.js +0 -2
- package/dist/components/p-B3PdrMP0.js.map +0 -1
- package/dist/components/p-CIgag7g2.js +0 -2
- package/dist/components/p-CUSz0JFa.js +0 -2
- package/dist/components/p-CUSz0JFa.js.map +0 -1
- /package/dist/components/{p-BN9FZESH.js.map → p-1S9AW2MA.js.map} +0 -0
- /package/dist/components/{p-D4bVE7e4.js.map → p-6B411hQA.js.map} +0 -0
- /package/dist/components/{p-D5PN6zdD.js.map → p-B-kRByho.js.map} +0 -0
- /package/dist/components/{p-CmUGA_q4.js.map → p-B4_0Ys4w.js.map} +0 -0
- /package/dist/components/{p-BApNi_xA.js.map → p-B6RrZQqw.js.map} +0 -0
- /package/dist/components/{p-CQ4vAZ9j.js.map → p-BBtjABBS.js.map} +0 -0
- /package/dist/components/{p-CmMfRM1s.js.map → p-BFfHIu6y.js.map} +0 -0
- /package/dist/components/{p-CBiAsFAZ.js.map → p-BPyymeuR.js.map} +0 -0
- /package/dist/components/{p-Bgh0uG51.js.map → p-BR_oMaas.js.map} +0 -0
- /package/dist/components/{p-w2kKeG_h.js.map → p-Bb4f5SuF.js.map} +0 -0
- /package/dist/components/{p-B5snQKiP.js.map → p-BmNpPyzf.js.map} +0 -0
- /package/dist/components/{p-CWItD0gL.js.map → p-Bmo4T96l.js.map} +0 -0
- /package/dist/components/{p-DHAuidt1.js.map → p-Bsfpxto1.js.map} +0 -0
- /package/dist/components/{p-a6cnCKWk.js.map → p-BtuYufL3.js.map} +0 -0
- /package/dist/components/{p-BxEOlO5k.js.map → p-CKRJX0Y0.js.map} +0 -0
- /package/dist/components/{p-1moOgMI-.js.map → p-Ca6s5fLR.js.map} +0 -0
- /package/dist/components/{p-RT1mts9U.js.map → p-CbjFUpOj.js.map} +0 -0
- /package/dist/components/{p-CqOswTy8.js.map → p-CuJ3J6OJ.js.map} +0 -0
- /package/dist/components/{p-BQTJ7ePr.js.map → p-CxgMUvqg.js.map} +0 -0
- /package/dist/components/{p-CWKrq35a.js.map → p-D0bM3cKj.js.map} +0 -0
- /package/dist/components/{p-UtfErnpX.js.map → p-D2cjoj2r.js.map} +0 -0
- /package/dist/components/{p-CGwVooB6.js.map → p-DHF5cLzr.js.map} +0 -0
- /package/dist/components/{p-BgjtMMye.js.map → p-DVuxzrCC.js.map} +0 -0
- /package/dist/components/{p-DXGKfXTh.js.map → p-DfvDNcoq.js.map} +0 -0
- /package/dist/components/{p-4scN0Mgn.js.map → p-Di81y9Q2.js.map} +0 -0
- /package/dist/components/{p-C_3mZMFK.js.map → p-Eep5mxtv.js.map} +0 -0
- /package/dist/components/{p-DoNdiEly.js.map → p-IYy-jODg.js.map} +0 -0
- /package/dist/components/{p-DtV263qO.js.map → p-iCjUWcp7.js.map} +0 -0
- /package/dist/components/{p-BgASe-BH.js.map → p-og48Kh2i.js.map} +0 -0
- /package/dist/components/{p-BSocHH2X.js.map → p-wOOAzVMC.js.map} +0 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "banner",
|
|
4
|
+
"title": "Banner",
|
|
5
|
+
"description": "Persistent in-flow application banner with semantic color intent, explicit announcement urgency, horizontal actions, and controlled dismissal motion.",
|
|
6
|
+
"type": "registry:ui",
|
|
7
|
+
"meta": {
|
|
8
|
+
"library": "@ds-mo/ui",
|
|
9
|
+
"distribution": "npm",
|
|
10
|
+
"storybook": "https://zainadeel.github.io/compomo/?path=/story/banner",
|
|
11
|
+
"source": "src/wc/components/Banner/Banner.tsx",
|
|
12
|
+
"intentStatus": "complete",
|
|
13
|
+
"intent": {
|
|
14
|
+
"audience": "general",
|
|
15
|
+
"status": "stable",
|
|
16
|
+
"summary": "Persistent in-flow application banner with semantic color intent, explicit announcement urgency, horizontal actions, and controlled dismissal motion.",
|
|
17
|
+
"useWhen": [
|
|
18
|
+
"A persistent application-wide condition or announcement must remain visible while the user works.",
|
|
19
|
+
"A shell-level message needs optional actions and dismissal while reflowing the application rather than floating over it.",
|
|
20
|
+
"Message urgency and color intent must be selected independently."
|
|
21
|
+
],
|
|
22
|
+
"avoidWhen": [
|
|
23
|
+
"Feedback is transient, queued, promise-driven, or anchored to a control; use Toast.",
|
|
24
|
+
"The message belongs inside a field, card, or bounded content section; use the future inline banner rather than shrinking the application shell.",
|
|
25
|
+
"Several global messages must be prioritized or queued; the application must resolve them to one active banner before rendering."
|
|
26
|
+
],
|
|
27
|
+
"alternatives": [
|
|
28
|
+
{
|
|
29
|
+
"when": "Feedback is transient or follows a completed action.",
|
|
30
|
+
"component": "component:ds-toast",
|
|
31
|
+
"reason": "Toast owns queues, timers, stacking, swipe dismissal, and anchored feedback."
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"when": "A bounded region has no content to show.",
|
|
35
|
+
"component": "component:ds-empty-state",
|
|
36
|
+
"reason": "EmptyState explains an empty result without creating global application urgency."
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"commonlyComposedWith": [
|
|
40
|
+
"component:ds-shell-app",
|
|
41
|
+
"component:ds-button-unfilled"
|
|
42
|
+
],
|
|
43
|
+
"patterns": [
|
|
44
|
+
"pattern:application-shell"
|
|
45
|
+
],
|
|
46
|
+
"accessibility": [
|
|
47
|
+
"Choose announcement=none for content present at page load, polite for non-urgent dynamic changes, and assertive only when immediate interruption is necessary.",
|
|
48
|
+
"Visual intent never chooses live-region urgency; a negative banner can be persistent and non-urgent, while a neutral banner can require prompt attention.",
|
|
49
|
+
"The close control is always available. Localize dismissLabel and keep the banner mounted until dsAfterClose before removing it.",
|
|
50
|
+
"Slotted actions retain their own accessible names, keyboard behavior, and outcome messaging."
|
|
51
|
+
],
|
|
52
|
+
"states": [
|
|
53
|
+
"The application owns open. The always-present close control emits dsClose, and the application sets open=false to begin exit motion.",
|
|
54
|
+
"dsAfterClose fires once after the closing banner is inert, hidden from assistive technology, and no longer occupies layout space.",
|
|
55
|
+
"The opaque surface slides by its complete block size while occupied-shell space expands with the same short-3 duration. Opening uses the shell tool panel's ease-out curve; dismissal reverses with its ease-in curve, keeping the Banner boundary flush with the shell throughout.",
|
|
56
|
+
"Heading is optional and description is required. With orientation=horizontal they share one inline copy flow, so description begins after the heading and continuation lines use the complete text width beneath it. With orientation=vertical, description starts on the next line with a 4px gap.",
|
|
57
|
+
"The actions slot accepts at most two ds-button-unfilled controls. They stay in one horizontal trailing lane, align to the top of the dismiss control when sharing the copy row, and must receive the matching parent-surface background context.",
|
|
58
|
+
"Intent selects neutral, brand, positive, warning, caution, or negative hue. Contrast independently selects faint, medium, strong, or bold fill.",
|
|
59
|
+
"Banner has a real bottom boundary, no radius, no elevation, no icon, no timeout, and no internal queue."
|
|
60
|
+
],
|
|
61
|
+
"responsiveBehavior": [
|
|
62
|
+
"Applications select orientation explicitly. Horizontal keeps copy and actions in one row, with actions top-aligned to dismissal. Vertical puts actions below copy on the 16px text boundary and keeps controls 16px above the bottom boundary.",
|
|
63
|
+
"When placed in ShellApp's banner slot, the banner spans the complete application above navigation, workspace, and tools.",
|
|
64
|
+
"ShellApp reserves the mobile top safe area once above the banner and keeps the banner visible while mobile tools or navigation surfaces replace the routed stage."
|
|
65
|
+
],
|
|
66
|
+
"frameworkCaveats": {
|
|
67
|
+
"customElements": [
|
|
68
|
+
"Listen for dsClose, set open=false, and wait for dsAfterClose before conditionally removing the element.",
|
|
69
|
+
"Place at most one ds-banner in ShellApp's banner slot, choose horizontal or vertical orientation for the available shell capacity, and assign no more than two ds-button-unfilled elements slot=actions."
|
|
70
|
+
],
|
|
71
|
+
"react": [
|
|
72
|
+
"Keep the component mounted while open changes from true to false; remove it in onDsAfterClose when unmounting is required.",
|
|
73
|
+
"Project no more than two ds-button-unfilled actions and pass each the matching background context."
|
|
74
|
+
],
|
|
75
|
+
"angular": [
|
|
76
|
+
"Bind open as controlled application state, handle dsClose to update it, and wait for dsAfterClose before removing an ngIf owner.",
|
|
77
|
+
"Import and project no more than two DsButtonUnfilled actions with slot=actions and their matching background input."
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
"references": [
|
|
81
|
+
{
|
|
82
|
+
"label": "Storybook examples",
|
|
83
|
+
"path": "src/wc/components/Banner/Banner.stories.ts"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"label": "Rendered behavior coverage",
|
|
87
|
+
"path": "tests/e2e/banner.spec.ts"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"label": "Application shell contract",
|
|
91
|
+
"path": "agent/patterns/application-shell/pattern.agent.json"
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
},
|
|
95
|
+
"api": {
|
|
96
|
+
"props": {
|
|
97
|
+
"announcement": {
|
|
98
|
+
"type": "BannerAnnouncement",
|
|
99
|
+
"resolvedType": "\"assertive\" | \"none\" | \"polite\"",
|
|
100
|
+
"attribute": "announcement",
|
|
101
|
+
"default": "'none'",
|
|
102
|
+
"required": false,
|
|
103
|
+
"mutable": false,
|
|
104
|
+
"description": "Live-region urgency, independent of visual intent."
|
|
105
|
+
},
|
|
106
|
+
"contrast": {
|
|
107
|
+
"type": "BannerContrast",
|
|
108
|
+
"resolvedType": "\"bold\" | \"faint\" | \"medium\" | \"strong\"",
|
|
109
|
+
"attribute": "contrast",
|
|
110
|
+
"default": "'faint'",
|
|
111
|
+
"required": false,
|
|
112
|
+
"mutable": false,
|
|
113
|
+
"description": "Surface fill weight."
|
|
114
|
+
},
|
|
115
|
+
"description": {
|
|
116
|
+
"type": "string",
|
|
117
|
+
"resolvedType": "string",
|
|
118
|
+
"attribute": "description",
|
|
119
|
+
"required": true,
|
|
120
|
+
"mutable": false,
|
|
121
|
+
"description": "Required explanatory copy."
|
|
122
|
+
},
|
|
123
|
+
"dismissLabel": {
|
|
124
|
+
"type": "string",
|
|
125
|
+
"resolvedType": "string",
|
|
126
|
+
"attribute": "dismiss-label",
|
|
127
|
+
"default": "'Dismiss banner'",
|
|
128
|
+
"required": false,
|
|
129
|
+
"mutable": false,
|
|
130
|
+
"description": "Localized accessible label for the close control."
|
|
131
|
+
},
|
|
132
|
+
"heading": {
|
|
133
|
+
"type": "string",
|
|
134
|
+
"resolvedType": "string",
|
|
135
|
+
"attribute": "heading",
|
|
136
|
+
"default": "''",
|
|
137
|
+
"required": false,
|
|
138
|
+
"mutable": false,
|
|
139
|
+
"description": "Optional visual heading rendered before the description."
|
|
140
|
+
},
|
|
141
|
+
"intent": {
|
|
142
|
+
"type": "BannerIntent",
|
|
143
|
+
"resolvedType": "\"brand\" | \"caution\" | \"negative\" | \"neutral\" | \"positive\" | \"warning\"",
|
|
144
|
+
"attribute": "intent",
|
|
145
|
+
"default": "'neutral'",
|
|
146
|
+
"required": false,
|
|
147
|
+
"mutable": false,
|
|
148
|
+
"description": "Semantic color family."
|
|
149
|
+
},
|
|
150
|
+
"open": {
|
|
151
|
+
"type": "boolean",
|
|
152
|
+
"resolvedType": "boolean",
|
|
153
|
+
"attribute": "open",
|
|
154
|
+
"default": "true",
|
|
155
|
+
"required": false,
|
|
156
|
+
"mutable": false,
|
|
157
|
+
"description": "Controlled visibility. Keep the element mounted until dsAfterClose when unmounting."
|
|
158
|
+
},
|
|
159
|
+
"orientation": {
|
|
160
|
+
"type": "BannerOrientation",
|
|
161
|
+
"resolvedType": "\"horizontal\" | \"vertical\"",
|
|
162
|
+
"attribute": "orientation",
|
|
163
|
+
"default": "'horizontal'",
|
|
164
|
+
"required": false,
|
|
165
|
+
"mutable": false,
|
|
166
|
+
"description": "Copy and action-lane arrangement, selected by the application."
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
"events": [
|
|
170
|
+
{
|
|
171
|
+
"name": "dsAfterClose",
|
|
172
|
+
"detail": "void",
|
|
173
|
+
"bubbles": true,
|
|
174
|
+
"cancelable": true,
|
|
175
|
+
"composed": true,
|
|
176
|
+
"description": "Emitted once after controlled exit motion and layout collapse complete."
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"name": "dsClose",
|
|
180
|
+
"detail": "MouseEvent",
|
|
181
|
+
"bubbles": true,
|
|
182
|
+
"cancelable": true,
|
|
183
|
+
"composed": true,
|
|
184
|
+
"description": "Emitted when the close control requests dismissal. The application controls open."
|
|
185
|
+
}
|
|
186
|
+
],
|
|
187
|
+
"methods": [],
|
|
188
|
+
"slots": []
|
|
189
|
+
},
|
|
190
|
+
"props": {
|
|
191
|
+
"announcement": {
|
|
192
|
+
"type": "BannerAnnouncement",
|
|
193
|
+
"resolvedType": "\"assertive\" | \"none\" | \"polite\"",
|
|
194
|
+
"attribute": "announcement",
|
|
195
|
+
"default": "'none'",
|
|
196
|
+
"required": false,
|
|
197
|
+
"mutable": false,
|
|
198
|
+
"description": "Live-region urgency, independent of visual intent."
|
|
199
|
+
},
|
|
200
|
+
"contrast": {
|
|
201
|
+
"type": "BannerContrast",
|
|
202
|
+
"resolvedType": "\"bold\" | \"faint\" | \"medium\" | \"strong\"",
|
|
203
|
+
"attribute": "contrast",
|
|
204
|
+
"default": "'faint'",
|
|
205
|
+
"required": false,
|
|
206
|
+
"mutable": false,
|
|
207
|
+
"description": "Surface fill weight."
|
|
208
|
+
},
|
|
209
|
+
"description": {
|
|
210
|
+
"type": "string",
|
|
211
|
+
"resolvedType": "string",
|
|
212
|
+
"attribute": "description",
|
|
213
|
+
"required": true,
|
|
214
|
+
"mutable": false,
|
|
215
|
+
"description": "Required explanatory copy."
|
|
216
|
+
},
|
|
217
|
+
"dismissLabel": {
|
|
218
|
+
"type": "string",
|
|
219
|
+
"resolvedType": "string",
|
|
220
|
+
"attribute": "dismiss-label",
|
|
221
|
+
"default": "'Dismiss banner'",
|
|
222
|
+
"required": false,
|
|
223
|
+
"mutable": false,
|
|
224
|
+
"description": "Localized accessible label for the close control."
|
|
225
|
+
},
|
|
226
|
+
"heading": {
|
|
227
|
+
"type": "string",
|
|
228
|
+
"resolvedType": "string",
|
|
229
|
+
"attribute": "heading",
|
|
230
|
+
"default": "''",
|
|
231
|
+
"required": false,
|
|
232
|
+
"mutable": false,
|
|
233
|
+
"description": "Optional visual heading rendered before the description."
|
|
234
|
+
},
|
|
235
|
+
"intent": {
|
|
236
|
+
"type": "BannerIntent",
|
|
237
|
+
"resolvedType": "\"brand\" | \"caution\" | \"negative\" | \"neutral\" | \"positive\" | \"warning\"",
|
|
238
|
+
"attribute": "intent",
|
|
239
|
+
"default": "'neutral'",
|
|
240
|
+
"required": false,
|
|
241
|
+
"mutable": false,
|
|
242
|
+
"description": "Semantic color family."
|
|
243
|
+
},
|
|
244
|
+
"open": {
|
|
245
|
+
"type": "boolean",
|
|
246
|
+
"resolvedType": "boolean",
|
|
247
|
+
"attribute": "open",
|
|
248
|
+
"default": "true",
|
|
249
|
+
"required": false,
|
|
250
|
+
"mutable": false,
|
|
251
|
+
"description": "Controlled visibility. Keep the element mounted until dsAfterClose when unmounting."
|
|
252
|
+
},
|
|
253
|
+
"orientation": {
|
|
254
|
+
"type": "BannerOrientation",
|
|
255
|
+
"resolvedType": "\"horizontal\" | \"vertical\"",
|
|
256
|
+
"attribute": "orientation",
|
|
257
|
+
"default": "'horizontal'",
|
|
258
|
+
"required": false,
|
|
259
|
+
"mutable": false,
|
|
260
|
+
"description": "Copy and action-lane arrangement, selected by the application."
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
"events": [
|
|
264
|
+
{
|
|
265
|
+
"name": "dsAfterClose",
|
|
266
|
+
"detail": "void",
|
|
267
|
+
"bubbles": true,
|
|
268
|
+
"cancelable": true,
|
|
269
|
+
"composed": true,
|
|
270
|
+
"description": "Emitted once after controlled exit motion and layout collapse complete."
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
"name": "dsClose",
|
|
274
|
+
"detail": "MouseEvent",
|
|
275
|
+
"bubbles": true,
|
|
276
|
+
"cancelable": true,
|
|
277
|
+
"composed": true,
|
|
278
|
+
"description": "Emitted when the close control requests dismissal. The application controls open."
|
|
279
|
+
}
|
|
280
|
+
],
|
|
281
|
+
"methods": [],
|
|
282
|
+
"slots": [],
|
|
283
|
+
"exports": {
|
|
284
|
+
"customElement": "ds-banner",
|
|
285
|
+
"react": "DsBanner",
|
|
286
|
+
"angular": "DsBanner"
|
|
287
|
+
},
|
|
288
|
+
"consumption": {
|
|
289
|
+
"install": "npm install @ds-mo/ui @ds-mo/tokens @ds-mo/icons",
|
|
290
|
+
"cssSetup": "import '@ds-mo/tokens';\nimport '@ds-mo/tokens/reset';\nimport '@ds-mo/tokens/globals';",
|
|
291
|
+
"customElements": {
|
|
292
|
+
"import": "import '@ds-mo/ui/dist/components/ds-banner.js';",
|
|
293
|
+
"example": "<ds-banner></ds-banner>"
|
|
294
|
+
},
|
|
295
|
+
"react": {
|
|
296
|
+
"import": "import { DsBanner } from '@ds-mo/ui/react';",
|
|
297
|
+
"example": "<DsBanner />"
|
|
298
|
+
},
|
|
299
|
+
"angular": {
|
|
300
|
+
"import": "import { DsBanner } from '@ds-mo/ui/angular/ds-banner';",
|
|
301
|
+
"example": "<ds-banner></ds-banner>"
|
|
302
|
+
},
|
|
303
|
+
"peerDependencies": {
|
|
304
|
+
"required": [
|
|
305
|
+
"@ds-mo/tokens ^5.1.1",
|
|
306
|
+
"@ds-mo/icons ^6.4.0"
|
|
307
|
+
],
|
|
308
|
+
"frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
"dependencies": [
|
|
313
|
+
"@ds-mo/ui",
|
|
314
|
+
"@ds-mo/tokens"
|
|
315
|
+
],
|
|
316
|
+
"registryDependencies": [
|
|
317
|
+
"button-unfilled",
|
|
318
|
+
"text",
|
|
319
|
+
"tooltip"
|
|
320
|
+
],
|
|
321
|
+
"files": [
|
|
322
|
+
{
|
|
323
|
+
"path": "src/wc/components/Banner/Banner.css",
|
|
324
|
+
"content": "@import '../../utils/forced-colors.css';\n@import '../../utils/chrome-header.css';\n\n:host {\n display: grid;\n overflow: hidden;\n min-width: 0;\n grid-template-rows: 0fr;\n transition: grid-template-rows var(--effect-animation-duration-short-3)\n var(--_banner-motion-easing);\n\n --ds-banner-background: var(--color-background-faint-neutral);\n --ds-banner-heading: var(--color-foreground-primary);\n --ds-banner-description: var(--color-foreground-secondary);\n --ds-banner-border: var(--color-border-tertiary);\n --_banner-motion-easing: var(--effect-animation-easing-ease-in-out, ease);\n}\n\n:host(.banner--open) {\n grid-template-rows: 1fr;\n --_banner-motion-easing: var(--effect-animation-easing-ease-out, ease-out);\n}\n\n:host(.banner--closing) {\n --_banner-motion-easing: var(--effect-animation-easing-ease-in, ease-in);\n}\n\n:host(.banner--hidden) {\n display: none;\n}\n\n.banner-overflow {\n min-height: 0;\n overflow: visible;\n}\n\n.banner-surface {\n display: flex;\n position: relative;\n width: 100%;\n padding-inline-end: calc(var(--dimension-size-400) + var(--dimension-space-200));\n color: var(--ds-banner-description);\n background-color: var(--ds-banner-background);\n border-bottom: var(--dimension-stroke-width-012) solid var(--ds-banner-border);\n transform: translateY(-100%);\n transition: transform var(--effect-animation-duration-short-3) var(--_banner-motion-easing);\n}\n\n:host(.banner--open) .banner-surface {\n transform: translateY(0);\n}\n\n.banner-copy {\n flex-basis: var(--dimension-card-width-sm);\n}\n\n:host(.banner--orientation-horizontal) .banner-surface {\n flex-wrap: nowrap;\n}\n\n:host(.banner--orientation-vertical) .banner-copy {\n flex-basis: 100%;\n}\n\n.banner-copy-flow {\n box-sizing: border-box;\n min-width: 0;\n padding-inline: var(--ds-control-label-inset, var(--dimension-space-025));\n color: var(--ds-banner-description);\n}\n\n.banner-heading {\n display: inline-block;\n width: auto;\n max-width: 100%;\n margin-inline-end: var(--dimension-space-100);\n vertical-align: baseline;\n color: var(--ds-banner-heading);\n}\n\n.banner-description {\n color: var(--ds-banner-description);\n}\n\n.banner-actions {\n display: flex;\n align-items: center;\n flex: 0 0 auto;\n flex-wrap: nowrap;\n min-width: 0;\n gap: var(--dimension-space-100);\n}\n\n.banner-actions slot {\n display: contents;\n}\n\n.banner-actions ::slotted(:not(ds-button-unfilled)),\n.banner-actions ::slotted(ds-button-unfilled:nth-of-type(n + 3)) {\n display: none;\n}\n\n.banner-actions--empty {\n display: none;\n}\n\n.banner-trailing {\n align-self: flex-start;\n flex-wrap: nowrap;\n}\n\n:host(.banner--orientation-vertical.banner--has-actions) .banner-trailing {\n margin-inline: var(--dimension-space-100) auto;\n padding-block-end: var(--dimension-space-100);\n box-sizing: border-box;\n}\n\n:host(.banner--orientation-vertical) .banner-description {\n display: block;\n}\n\n:host(.banner--orientation-vertical) .banner-heading + .banner-description {\n margin-block-start: var(--dimension-space-050);\n}\n\n.banner-close {\n flex: 0 0 auto;\n}\n\n.banner-dismiss {\n position: absolute;\n inset-block-start: var(--dimension-space-100);\n inset-inline-end: var(--dimension-space-100);\n}\n\n:host(.banner--intent-neutral) {\n --ds-banner-faint-background: var(--color-background-faint-neutral);\n}\n\n:host(.banner--intent-brand) {\n --ds-banner-faint-background: var(--color-background-faint-brand);\n}\n\n:host(.banner--intent-positive) {\n --ds-banner-faint-background: var(--color-background-faint-positive);\n}\n\n:host(.banner--intent-warning) {\n --ds-banner-faint-background: var(--color-background-faint-warning);\n}\n\n:host(.banner--intent-caution) {\n --ds-banner-faint-background: var(--color-background-faint-caution);\n}\n\n:host(.banner--intent-negative) {\n --ds-banner-faint-background: var(--color-background-faint-negative);\n}\n\n:host(.banner--contrast-faint) {\n --ds-banner-background: var(--ds-banner-faint-background);\n --ds-banner-heading: var(--color-foreground-primary);\n --ds-banner-description: var(--color-foreground-secondary);\n --ds-banner-border: var(--color-border-tertiary);\n}\n\n:host(.banner--contrast-medium) {\n --ds-banner-background: var(--color-background-medium-neutral);\n --ds-banner-heading: var(--color-foreground-on-medium-background-primary);\n --ds-banner-description: var(--color-foreground-on-medium-background-secondary);\n --ds-banner-border: var(--color-border-on-medium-background-tertiary);\n}\n\n:host(.banner--intent-brand.banner--contrast-medium) { --ds-banner-background: var(--color-background-medium-brand); }\n:host(.banner--intent-positive.banner--contrast-medium) { --ds-banner-background: var(--color-background-medium-positive); }\n:host(.banner--intent-warning.banner--contrast-medium) { --ds-banner-background: var(--color-background-medium-warning); }\n:host(.banner--intent-caution.banner--contrast-medium) { --ds-banner-background: var(--color-background-medium-caution); }\n:host(.banner--intent-negative.banner--contrast-medium) { --ds-banner-background: var(--color-background-medium-negative); }\n\n:host(.banner--contrast-strong) {\n --ds-banner-background: var(--color-background-strong-neutral);\n --ds-banner-heading: var(--color-foreground-on-strong-background-primary);\n --ds-banner-description: var(--color-foreground-on-strong-background-secondary);\n --ds-banner-border: var(--color-border-on-strong-background-tertiary);\n}\n\n:host(.banner--intent-brand.banner--contrast-strong) { --ds-banner-background: var(--color-background-strong-brand); }\n:host(.banner--intent-positive.banner--contrast-strong) { --ds-banner-background: var(--color-background-strong-positive); }\n:host(.banner--intent-warning.banner--contrast-strong) { --ds-banner-background: var(--color-background-strong-warning); }\n:host(.banner--intent-caution.banner--contrast-strong) { --ds-banner-background: var(--color-background-strong-caution); }\n:host(.banner--intent-negative.banner--contrast-strong) { --ds-banner-background: var(--color-background-strong-negative); }\n\n:host(.banner--contrast-bold) {\n --ds-banner-background: var(--color-background-bold-neutral);\n --ds-banner-heading: var(--color-foreground-on-bold-background-primary);\n --ds-banner-description: var(--color-foreground-on-bold-background-secondary);\n --ds-banner-border: var(--color-border-on-bold-background-tertiary);\n}\n\n:host(.banner--intent-brand.banner--contrast-bold) { --ds-banner-background: var(--color-background-bold-brand); }\n:host(.banner--intent-positive.banner--contrast-bold) { --ds-banner-background: var(--color-background-bold-positive); }\n:host(.banner--intent-warning.banner--contrast-bold) { --ds-banner-background: var(--color-background-bold-warning); }\n:host(.banner--intent-caution.banner--contrast-bold) { --ds-banner-background: var(--color-background-bold-caution); }\n:host(.banner--intent-negative.banner--contrast-bold) { --ds-banner-background: var(--color-background-bold-negative); }\n\n@media (prefers-reduced-motion: reduce) {\n :host,\n .banner-surface {\n transition: none;\n }\n}\n\n@media (forced-colors: active) {\n .banner-surface {\n color: var(--ds-forced-color-content);\n background-color: var(--ds-forced-color-surface);\n border-bottom-color: var(--ds-forced-color-content);\n }\n\n .banner-heading,\n .banner-description {\n color: var(--ds-forced-color-content);\n }\n}\n",
|
|
325
|
+
"type": "registry:ui"
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
"path": "src/wc/components/Banner/Banner.tsx",
|
|
329
|
+
"content": "import {\n Component,\n Element,\n Event,\n EventEmitter,\n h,\n Host,\n Prop,\n State,\n Watch,\n} from '@stencil/core';\nimport { resolveMotionTimeMs, TOKEN_DEFAULTS } from '../../utils';\n\nexport type BannerIntent =\n | 'neutral'\n | 'brand'\n | 'positive'\n | 'warning'\n | 'caution'\n | 'negative';\n\nexport type BannerContrast = 'faint' | 'medium' | 'strong' | 'bold';\nexport type BannerAnnouncement = 'none' | 'polite' | 'assertive';\nexport type BannerOrientation = 'horizontal' | 'vertical';\n\ntype BannerPhase = 'closed' | 'open' | 'closing';\n\n@Component({\n tag: 'ds-banner',\n styleUrl: 'Banner.css',\n scoped: true,\n})\nexport class Banner {\n @Element() el!: HTMLElement;\n\n /** Required explanatory copy. */\n @Prop() description!: string;\n /** Optional visual heading rendered before the description. */\n @Prop() heading: string = '';\n /** Semantic color family. */\n @Prop({ reflect: true }) intent: BannerIntent = 'neutral';\n /** Surface fill weight. */\n @Prop({ reflect: true }) contrast: BannerContrast = 'faint';\n /** Copy and action-lane arrangement, selected by the application. */\n @Prop({ reflect: true }) orientation: BannerOrientation = 'horizontal';\n /** Controlled visibility. Keep the element mounted until dsAfterClose when unmounting. */\n @Prop({ reflect: true }) open: boolean = true;\n /** Localized accessible label for the close control. */\n @Prop() dismissLabel: string = 'Dismiss banner';\n /** Live-region urgency, independent of visual intent. */\n @Prop() announcement: BannerAnnouncement = 'none';\n\n /** Emitted when the close control requests dismissal. The application controls open. */\n @Event() dsClose!: EventEmitter<MouseEvent>;\n /** Emitted once after controlled exit motion and layout collapse complete. */\n @Event() dsAfterClose!: EventEmitter<void>;\n\n @State() private rendered = false;\n @State() private phase: BannerPhase = 'closed';\n @State() private hasActions = false;\n\n private loaded = false;\n private transitionTimer: ReturnType<typeof setTimeout> | null = null;\n private openFrame: number | null = null;\n private closePending = false;\n\n componentWillLoad() {\n this.rendered = this.open;\n }\n\n componentDidLoad() {\n this.loaded = true;\n this.updateActionsPresence();\n if (this.open) this.startOpen();\n }\n\n disconnectedCallback() {\n this.clearTransitionWork();\n }\n\n @Watch('open')\n onOpenChange(isOpen: boolean) {\n if (!this.loaded) return;\n if (isOpen) this.startOpen();\n else this.startClose();\n }\n\n private get closeMotionMs(): number {\n return resolveMotionTimeMs(\n TOKEN_DEFAULTS.motionShort3,\n TOKEN_DEFAULTS.animationDurationShort3,\n );\n }\n\n private clearTransitionWork() {\n if (this.transitionTimer) clearTimeout(this.transitionTimer);\n if (this.openFrame !== null) cancelAnimationFrame(this.openFrame);\n this.transitionTimer = null;\n this.openFrame = null;\n }\n\n private startOpen() {\n this.clearTransitionWork();\n this.closePending = false;\n this.rendered = true;\n this.phase = 'closed';\n this.openFrame = requestAnimationFrame(() => {\n this.openFrame = null;\n if (this.open) this.phase = 'open';\n });\n }\n\n private startClose() {\n this.clearTransitionWork();\n if (!this.rendered || this.phase === 'closed') {\n this.rendered = false;\n return;\n }\n\n this.closePending = true;\n this.phase = 'closing';\n const duration = this.closeMotionMs;\n if (duration <= 0) {\n this.finishClose();\n return;\n }\n this.transitionTimer = setTimeout(() => {\n this.transitionTimer = null;\n this.finishClose();\n }, duration);\n }\n\n private finishClose() {\n if (this.open || !this.closePending) return;\n this.closePending = false;\n this.rendered = false;\n this.phase = 'closed';\n this.dsAfterClose.emit();\n }\n\n private updateActionsPresence(slot?: HTMLSlotElement) {\n const actions = slot\n ? slot.assignedElements().filter(element => element.tagName === 'DS-BUTTON-UNFILLED')\n : Array.from(this.el.querySelectorAll('ds-button-unfilled[slot=\"actions\"]'));\n this.hasActions = actions.length > 0;\n }\n\n private requestClose(event: CustomEvent<MouseEvent>) {\n if (!this.open || this.phase === 'closing') return;\n this.dsClose.emit(event.detail);\n }\n\n private get role(): 'status' | 'alert' | undefined {\n if (this.announcement === 'polite') return 'status';\n if (this.announcement === 'assertive') return 'alert';\n return undefined;\n }\n\n private get ariaLive(): 'polite' | 'assertive' | undefined {\n return this.announcement === 'none' ? undefined : this.announcement;\n }\n\n render() {\n const closing = this.phase === 'closing';\n const hostClass = {\n banner: true,\n 'banner--open': this.phase === 'open',\n 'banner--closing': closing,\n 'banner--hidden': !this.rendered,\n 'banner--has-actions': this.hasActions,\n [`banner--orientation-${this.orientation}`]: true,\n [`banner--intent-${this.intent}`]: true,\n [`banner--contrast-${this.contrast}`]: true,\n };\n\n return (\n <Host class={hostClass}>\n {this.rendered && (\n <div class=\"banner-overflow\">\n <section\n class=\"banner-surface ds-chrome-header ds-chrome-header--wrapping\"\n role={this.role}\n aria-live={this.ariaLive}\n aria-atomic={this.announcement === 'none' ? undefined : 'true'}\n aria-hidden={closing ? 'true' : undefined}\n inert={closing ? true : undefined}\n data-phase={this.phase}\n >\n {/* eslint-disable-next-line local/prefer-direct-ds-text -- The header-copy lane owns the 6px container inset separately from the inline text flow's 2px inset. */}\n <div class=\"banner-copy ds-chrome-header__copy ds-chrome-header__copy--wrapping ds-control--md\">\n <ds-text\n class=\"banner-copy-flow\"\n as=\"div\"\n variant=\"text-body-medium\"\n color=\"inherit\"\n >\n {this.heading && (\n <ds-text\n class=\"banner-heading\"\n as=\"span\"\n variant=\"text-title-small\"\n color=\"inherit\"\n >\n {this.heading}\n </ds-text>\n )}\n <span class=\"banner-description\">{this.description}</span>\n </ds-text>\n </div>\n <div class=\"banner-trailing ds-chrome-header__trailing\">\n <div class={{ 'banner-actions': true, 'banner-actions--empty': !this.hasActions }}>\n <slot\n name=\"actions\"\n onSlotchange={(event: Event) =>\n this.updateActionsPresence(event.currentTarget as HTMLSlotElement)\n }\n />\n </div>\n <div class=\"banner-dismiss\">\n <ds-tooltip label={this.dismissLabel} side=\"bottom\" size=\"sm\">\n <ds-button-unfilled\n class=\"banner-close\"\n variant=\"icon\"\n icon=\"Cross\"\n size=\"md\"\n type=\"button\"\n background={this.contrast}\n aria-label={this.dismissLabel}\n hasBorder={false}\n activeFill={false}\n onDsClick={(event: CustomEvent<MouseEvent>) => this.requestClose(event)}\n />\n </ds-tooltip>\n </div>\n </div>\n </section>\n </div>\n )}\n </Host>\n );\n }\n}\n",
|
|
330
|
+
"type": "registry:ui"
|
|
331
|
+
}
|
|
332
|
+
]
|
|
333
|
+
}
|
|
@@ -276,7 +276,7 @@
|
|
|
276
276
|
"complexPropertyNote": "Assign these non-primitive values as JavaScript properties: tabs.",
|
|
277
277
|
"peerDependencies": {
|
|
278
278
|
"required": [
|
|
279
|
-
"@ds-mo/tokens ^5.1.
|
|
279
|
+
"@ds-mo/tokens ^5.1.1",
|
|
280
280
|
"@ds-mo/icons ^6.4.0"
|
|
281
281
|
],
|
|
282
282
|
"frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"responsiveBehavior": [
|
|
75
75
|
"BarTitle does not observe viewport, shell, scroll, or container state. Its owner selects an explicit variant.",
|
|
76
76
|
"ShellPage is the standard owner for automatic expanded, compact, and constrained selection.",
|
|
77
|
-
"Compact titles use the shared md control inset so top-level headings align with BarNav and inline Back geometry aligns with PanelToolHeader. Long headings and section labels truncate independently while Back and action controls retain complete hit targets and accessible names.",
|
|
77
|
+
"Compact titles use the shared compact-header geometry and md control inset so top-level headings align with BarNav and inline Back geometry aligns with PanelToolHeader. Long headings and section labels truncate independently while Back and action controls retain complete hit targets and accessible names.",
|
|
78
78
|
"A never-collapse primary action remains visible in constrained mode; the title and section label yield space first."
|
|
79
79
|
],
|
|
80
80
|
"frameworkCaveats": {
|
|
@@ -449,7 +449,7 @@
|
|
|
449
449
|
"complexPropertyNote": "Assign these non-primitive values as JavaScript properties: actions, breadcrumbs, primaryAction, sections.",
|
|
450
450
|
"peerDependencies": {
|
|
451
451
|
"required": [
|
|
452
|
-
"@ds-mo/tokens ^5.1.
|
|
452
|
+
"@ds-mo/tokens ^5.1.1",
|
|
453
453
|
"@ds-mo/icons ^6.4.0"
|
|
454
454
|
],
|
|
455
455
|
"frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
|
|
@@ -473,12 +473,12 @@
|
|
|
473
473
|
"files": [
|
|
474
474
|
{
|
|
475
475
|
"path": "src/wc/components/BarTitle/BarTitle.css",
|
|
476
|
-
"content": "@import '../../utils/control-density.css';\n@import '../../utils/chrome-
|
|
476
|
+
"content": "@import '../../utils/control-density.css';\n@import '../../utils/chrome-header.css';\n@import '../../utils/focus-ring.css';\n@import '../../utils/interaction-fill.css';\n\n:host {\n display: block;\n min-width: 0;\n box-sizing: border-box;\n background: var(--color-background-primary);\n}\n\n:host([data-shell-page-syncing]) {\n visibility: hidden;\n}\n\n.bar-title {\n position: relative;\n min-width: 0;\n box-sizing: border-box;\n background: var(--color-background-primary);\n}\n\n.bar-title::after {\n content: '';\n position: absolute;\n inset: auto var(--ds-bar-title-divider-inset, var(--dimension-space-400)) 0;\n height: var(--dimension-stroke-width-012);\n background: var(--color-border-tertiary);\n pointer-events: none;\n}\n\n.bar-title__inner {\n position: relative;\n display: flex;\n flex-direction: column;\n align-items: stretch;\n gap: var(--dimension-space-100);\n padding: var(--dimension-space-400);\n box-sizing: border-box;\n}\n\n.bar-title__row {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: var(--dimension-space-100);\n min-height: var(--dimension-size-400);\n min-width: 0;\n}\n\n:host(.bar-title-host--expanded) .bar-title__row {\n gap: var(--dimension-space-400);\n}\n\n.bar-title__leading {\n display: flex;\n flex: 1 1 auto;\n flex-direction: column;\n gap: var(--dimension-space-100);\n min-width: 0;\n}\n\n.bar-title__title-row {\n display: flex;\n align-items: center;\n gap: var(--dimension-space-100);\n min-width: 0;\n min-height: var(--dimension-size-400);\n}\n\n.bar-title__identity {\n display: flex;\n flex: 0 1 auto;\n align-items: center;\n gap: var(--dimension-space-050);\n min-width: 0;\n}\n\n.bar-title__back,\n.bar-title__section-chevron {\n flex: 0 0 auto;\n}\n\n.bar-title__breadcrumb {\n display: block;\n width: 100%;\n min-width: 0;\n}\n\n.bar-title__heading {\n display: block;\n width: auto;\n min-width: 0;\n user-select: none;\n}\n\n.bar-title__divider {\n width: var(--dimension-stroke-width-012);\n height: var(--dimension-size-250);\n margin-inline-start: var(--dimension-space-100);\n flex: 0 0 var(--dimension-stroke-width-012);\n background: var(--color-border-tertiary);\n}\n\n.bar-title__section-selector {\n display: flex;\n flex: 0 1 auto;\n min-width: 0;\n max-width: var(--dimension-panel-width-sm);\n}\n\n.bar-title__section-trigger {\n --ds-control-radius: var(--dimension-radius-half);\n\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--ds-control-gap);\n height: var(--ds-control-height);\n min-width: 0;\n max-width: 100%;\n padding-inline: var(--ds-control-padding-inline);\n border: none;\n border-radius: var(--ds-control-radius);\n box-sizing: border-box;\n background: transparent;\n color: var(--color-foreground-secondary);\n cursor: default;\n font-family: inherit;\n user-select: none;\n}\n\n.bar-title__section-trigger--expanded {\n color: var(--color-foreground-primary);\n}\n\n.bar-title__section-trigger--expanded::after,\n.bar-title__section-trigger--expanded:hover::after {\n background: var(--ds-interaction-pressed);\n}\n\n.bar-title__section-label {\n min-width: 0;\n padding-inline: var(--ds-control-label-inset);\n}\n\n.bar-title__actions {\n display: flex;\n align-items: center;\n gap: var(--dimension-space-100);\n flex: 0 0 auto;\n}\n\n.bar-title__description {\n display: block;\n min-width: 0;\n max-width: 100%;\n}\n\n:host(.bar-title-host--compact) .bar-title {\n height: var(--dimension-size-600);\n}\n\n:host(.bar-title-host--compact) .bar-title::after {\n inset-inline: 0;\n}\n\n:host(.bar-title-host--compact) .bar-title__inner {\n flex: 1 1 auto;\n height: 100%;\n min-width: 0;\n padding: 0;\n gap: 0;\n}\n\n:host(.bar-title-host--compact) .bar-title__row {\n height: 100%;\n align-items: center;\n}\n\n:host(.bar-title-host--compact) .bar-title__identity {\n height: var(--dimension-size-400);\n}\n\n:host(.bar-title-host--compact) .bar-title__divider {\n margin-inline-start: 0;\n}\n\n:host(.bar-title-host--compact) .bar-title__heading {\n display: inline-flex;\n flex: 1 1 auto;\n align-items: center;\n height: var(--dimension-size-400);\n padding-inline: calc(var(--ds-control-padding-inline) + var(--ds-control-label-inset));\n box-sizing: border-box;\n}\n",
|
|
477
477
|
"type": "registry:ui"
|
|
478
478
|
},
|
|
479
479
|
{
|
|
480
480
|
"path": "src/wc/components/BarTitle/BarTitle.tsx",
|
|
481
|
-
"content": "import {\n Component,\n Element,\n Event,\n EventEmitter,\n h,\n Host,\n Prop,\n State,\n Watch,\n} from '@stencil/core';\nimport type { BreadcrumbItem, BreadcrumbSelectDetail } from '../Breadcrumb/breadcrumb-types';\nimport type { MenuItemData, MenuSection } from '../Menu/menu-types';\nimport {\n isBarTitleDivider,\n type BarTitleAction,\n type BarTitleActionItem,\n type BarTitlePrimaryAction,\n type BarTitleSection,\n type BarTitleSectionItem,\n type BarTitleVariant,\n} from './bar-title-types';\n\nlet nextBarTitleId = 0;\n\ntype FocusableButton = HTMLElement & { setFocus?: () => Promise<void> };\n\n@Component({\n tag: 'ds-bar-title',\n styleUrl: 'BarTitle.css',\n scoped: true,\n})\nexport class BarTitle {\n @Element() el!: HTMLElement;\n\n /** The page's single visible h1. */\n @Prop() heading!: string;\n\n /** Optional supporting copy shown in the expanded presentation. */\n @Prop() description: string = '';\n\n /** Show the leading page-level Back action. */\n @Prop({ attribute: 'show-back' }) showBack: boolean = false;\n\n /** Accessible name for the leading Back action. */\n @Prop() backAriaLabel: string = 'Back';\n\n /** Visible parent-page label used by the expanded breadcrumb when breadcrumbs is empty. */\n @Prop() backLabel: string = 'Back';\n\n /** Optional expanded ancestor path. Compact variants continue to use the Back action. */\n @Prop() breadcrumbs: BreadcrumbItem[] = [];\n\n /** Accessible name for the expanded breadcrumb navigation landmark. */\n @Prop() breadcrumbAriaLabel: string = 'Breadcrumb';\n\n /** Optional page sections exposed through the active-section menu. */\n @Prop() sections: BarTitleSectionItem[] = [];\n\n /** Id of the active page section. */\n @Prop() value: string = '';\n\n /** Accessible name for the page-section menu. */\n @Prop() sectionsAriaLabel: string = 'Change page section';\n\n /** The one highest-emphasis page action. */\n @Prop() primaryAction: BarTitlePrimaryAction | null = null;\n\n /** Secondary page actions shown in the overflow menu. Dividers create groups. */\n @Prop() actions: BarTitleActionItem[] = [];\n\n /** Accessible name for the page-actions menu. */\n @Prop() actionsAriaLabel: string = 'More page actions';\n\n /** Explicit visual/capacity variant. ShellPage owns automatic selection. */\n @Prop() variant: BarTitleVariant = 'expanded';\n\n /** Emitted when the leading Back action is activated. */\n @Event() dsBack!: EventEmitter<MouseEvent>;\n\n /** Emitted when an authored expanded breadcrumb item is activated. */\n @Event({ cancelable: true }) dsBreadcrumbSelect!: EventEmitter<BreadcrumbSelectDetail>;\n\n /** Emitted with the newly selected page-section id. */\n @Event() dsSectionChange!: EventEmitter<string>;\n\n /** Emitted with the activated primary or overflow action id. */\n @Event() dsAction!: EventEmitter<string>;\n\n @State() private sectionMenuOpen = false;\n @State() private actionMenuOpen = false;\n @State() private sectionMenuInitialFocusVisible = false;\n @State() private actionMenuInitialFocusVisible = false;\n\n private readonly instanceId = nextBarTitleId++;\n private readonly sectionMenuTriggerId = `bar-title-section-trigger-${this.instanceId}`;\n private readonly sectionMenuId = `bar-title-section-menu-${this.instanceId}`;\n private readonly actionMenuTriggerId = `bar-title-action-trigger-${this.instanceId}`;\n private readonly actionMenuId = `bar-title-action-menu-${this.instanceId}`;\n private sectionTriggerEl: HTMLButtonElement | null = null;\n private actionTriggerEl: FocusableButton | null = null;\n\n componentWillLoad() {\n if (this.el.closest('ds-shell-page')) {\n this.el.setAttribute('data-shell-page-syncing', '');\n }\n }\n\n @Watch('sections')\n handleSectionsChange() {\n if (!this.hasSectionSelector) this.closeSectionMenu();\n }\n\n @Watch('actions')\n @Watch('primaryAction')\n handleActionsChange() {\n if (!this.showActionMenuTrigger) this.closeActionMenu();\n }\n\n private get compact(): boolean {\n return this.variant !== 'expanded';\n }\n\n private get expandedBreadcrumbItems(): BreadcrumbItem[] {\n if (this.breadcrumbs.length > 0) return this.breadcrumbs;\n if (!this.showBack) return [];\n return [\n {\n id: 'back',\n label: this.backLabel,\n ariaLabel: this.backAriaLabel,\n },\n ];\n }\n\n /** Tooltip copy is fixed and generic; backAriaLabel/actionsAriaLabel stay page-specific. */\n private get backTooltipLabel(): string {\n return 'Go back';\n }\n\n private get primaryCollapsed(): boolean {\n return (\n this.variant === 'constrained' &&\n this.primaryAction !== null &&\n (this.primaryAction.collapse ?? 'auto') === 'auto'\n );\n }\n\n private get selectableSections(): BarTitleSection[] {\n return this.sections.filter((item): item is BarTitleSection => !isBarTitleDivider(item));\n }\n\n private get hasSectionSelector(): boolean {\n return this.selectableSections.length > 1;\n }\n\n private get effectiveValue(): string {\n return this.selectableSections.some(section => section.id === this.value)\n ? this.value\n : (this.selectableSections[0]?.id ?? '');\n }\n\n private get selectedSectionLabel(): string {\n return this.selectableSections.find(section => section.id === this.effectiveValue)?.label ?? '';\n }\n\n private get sectionTriggerAriaLabel(): string {\n return `${this.sectionsAriaLabel}. Current section: ${this.selectedSectionLabel}`;\n }\n\n private groupsFromItems<T extends BarTitleSection | BarTitleAction>(\n source: Array<T | { type: 'divider' }>,\n map: (item: T) => MenuItemData\n ): MenuSection[] {\n const groups: MenuSection[] = [];\n let items: MenuItemData[] = [];\n const commit = () => {\n if (items.length > 0) groups.push({ items });\n items = [];\n };\n\n for (const item of source) {\n if (isBarTitleDivider(item)) {\n commit();\n } else {\n items.push(map(item));\n }\n }\n commit();\n return groups;\n }\n\n private get sectionMenuSections(): MenuSection[] {\n return this.groupsFromItems(this.sections, section => ({\n label: section.label,\n value: section.id,\n isSelected: section.id === this.effectiveValue,\n isInactive: section.isInactive,\n }));\n }\n\n private get selectableActions(): BarTitleAction[] {\n return this.actions.filter((item): item is BarTitleAction => !isBarTitleDivider(item));\n }\n\n private get showActionMenuTrigger(): boolean {\n return (\n this.selectableActions.length > 0 || (this.primaryCollapsed && this.primaryAction !== null)\n );\n }\n\n private actionMenuItem(action: BarTitleAction | BarTitlePrimaryAction): MenuItemData {\n const primary = action as BarTitlePrimaryAction;\n return {\n label: action.label,\n value: action.id,\n isInactive: action.isInactive || primary.isLoading,\n isDestructive: action.isDestructive || primary.intent === 'negative',\n };\n }\n\n private get actionMenuSections(): MenuSection[] {\n const groups = this.groupsFromItems(this.actions, action => this.actionMenuItem(action));\n if (!this.primaryCollapsed || !this.primaryAction) return groups;\n return [{ items: [this.actionMenuItem(this.primaryAction)] }, ...groups];\n }\n\n private toggleSectionMenu = (event: MouseEvent) => {\n this.sectionMenuInitialFocusVisible = event.detail === 0;\n this.actionMenuOpen = false;\n this.sectionMenuOpen = !this.sectionMenuOpen;\n };\n\n private closeSectionMenu = () => {\n this.sectionMenuOpen = false;\n };\n\n private handleSectionSelect = (event: CustomEvent<MenuItemData>) => {\n const id = String(event.detail?.value ?? '');\n const selected = this.selectableSections.find(section => section.id === id);\n if (!selected || selected.isInactive) return;\n this.closeSectionMenu();\n this.dsSectionChange.emit(id);\n requestAnimationFrame(() => this.sectionTriggerEl?.focus());\n };\n\n private toggleActionMenu = (event: MouseEvent) => {\n this.actionMenuInitialFocusVisible = event.detail === 0;\n this.sectionMenuOpen = false;\n this.actionMenuOpen = !this.actionMenuOpen;\n };\n\n private closeActionMenu = () => {\n this.actionMenuOpen = false;\n };\n\n private handleActionSelect = (event: CustomEvent<MenuItemData>) => {\n const id = String(event.detail?.value ?? '');\n const primaryMatches = this.primaryCollapsed && this.primaryAction?.id === id;\n const action = this.selectableActions.find(candidate => candidate.id === id);\n if (!primaryMatches && !action) return;\n if (\n (primaryMatches && (this.primaryAction?.isInactive || this.primaryAction?.isLoading)) ||\n action?.isInactive\n ) {\n return;\n }\n this.closeActionMenu();\n this.dsAction.emit(id);\n requestAnimationFrame(() => void this.actionTriggerEl?.setFocus?.());\n };\n\n private renderSectionSelector() {\n if (!this.hasSectionSelector) return null;\n return [\n <div class=\"bar-title__divider\" aria-hidden=\"true\" />,\n <div class=\"bar-title__section-selector\">\n <button\n ref={el => {\n this.sectionTriggerEl = el ?? null;\n }}\n id={this.sectionMenuTriggerId}\n class={{\n 'bar-title__section-trigger': true,\n 'bar-title__section-trigger--expanded': this.sectionMenuOpen,\n 'ds-control--md': true,\n 'ds-focus-ring-inset': true,\n 'ds-interaction-fill': true,\n }}\n type=\"button\"\n aria-haspopup=\"menu\"\n aria-controls={this.sectionMenuId}\n aria-expanded={String(this.sectionMenuOpen)}\n aria-label={this.sectionTriggerAriaLabel}\n onClick={this.toggleSectionMenu}\n >\n <ds-text\n class=\"bar-title__section-label ds-interaction-fill__content\"\n as=\"span\"\n variant=\"text-body-medium\"\n emphasis\n color=\"primary\"\n lineTruncation={1}\n >\n {this.selectedSectionLabel}\n </ds-text>\n <ds-icon\n class=\"bar-title__section-chevron ds-interaction-fill__content\"\n name=\"ChevronUpDown\"\n size=\"md\"\n color=\"inherit\"\n aria-hidden=\"true\"\n />\n </button>\n </div>,\n ];\n }\n\n private renderActions() {\n const primary = this.primaryAction;\n if (!primary && !this.showActionMenuTrigger) return null;\n\n return (\n <div class=\"bar-title__actions\">\n {primary && !this.primaryCollapsed ? (\n <ds-button-filled\n class=\"bar-title__primary-action\"\n variant={primary.icon ? 'icon-label' : 'label'}\n icon={primary.icon ?? ''}\n label={primary.label}\n intent={primary.intent ?? 'brand'}\n contrast={primary.contrast ?? 'bold'}\n size=\"md\"\n type={primary.type ?? 'button'}\n isInactive={primary.isInactive}\n isLoading={primary.isLoading}\n onDsClick={() => this.dsAction.emit(primary.id)}\n />\n ) : null}\n {this.showActionMenuTrigger ? (\n <ds-tooltip label=\"Page options\" side=\"bottom\" size=\"sm\">\n <ds-button-unfilled\n ref={el => {\n this.actionTriggerEl = el ?? null;\n }}\n id={this.actionMenuTriggerId}\n class=\"bar-title__more-actions\"\n variant=\"icon\"\n icon=\"Ellipses\"\n aria-label={this.actionsAriaLabel}\n size=\"md\"\n activeFill={!this.compact}\n hasBorder={primary !== null && !this.primaryCollapsed}\n haspopup=\"menu\"\n controls={this.actionMenuId}\n expanded={this.actionMenuOpen}\n onDsClick={(event: CustomEvent<MouseEvent>) => this.toggleActionMenu(event.detail)}\n />\n </ds-tooltip>\n ) : null}\n </div>\n );\n }\n\n private handleBreadcrumbSelect = (event: CustomEvent<BreadcrumbSelectDetail>) => {\n if (this.breadcrumbs.length === 0) {\n this.dsBack.emit(event.detail.originalEvent);\n return;\n }\n const forwarded = this.dsBreadcrumbSelect.emit(event.detail);\n if (forwarded.defaultPrevented) event.preventDefault();\n };\n\n private renderBreadcrumb() {\n const items = this.expandedBreadcrumbItems;\n if (items.length === 0) return null;\n return (\n <ds-breadcrumb\n class=\"bar-title__breadcrumb\"\n items={items}\n ariaLabel={this.breadcrumbAriaLabel}\n onDsSelect={this.handleBreadcrumbSelect}\n />\n );\n }\n\n private renderBack() {\n if (!this.showBack) return null;\n return (\n <ds-tooltip label={this.backTooltipLabel} side=\"bottom\" size=\"sm\">\n <ds-button-unfilled\n class=\"bar-title__back\"\n variant=\"icon\"\n icon=\"ChevronLeft\"\n aria-label={this.backAriaLabel}\n size=\"md\"\n activeFill={false}\n hasBorder={false}\n onDsClick={(event: CustomEvent<MouseEvent>) => this.dsBack.emit(event.detail)}\n />\n </ds-tooltip>\n );\n }\n\n render() {\n const compact = this.compact;\n return (\n <Host\n class={{\n 'bar-title-host--compact': compact,\n 'bar-title-host--expanded': this.variant === 'expanded',\n 'bar-title-host--constrained': this.variant === 'constrained',\n 'bar-title-host--has-description': !!this.description,\n 'bar-title-host--has-back': this.showBack,\n 'bar-title-host--has-breadcrumb': !compact && this.expandedBreadcrumbItems.length > 0,\n }}\n >\n <div\n class={{\n 'bar-title': true,\n 'ds-chrome-row': compact,\n 'ds-chrome-space--md': compact,\n }}\n >\n <div class=\"bar-title__inner\">\n {!compact ? this.renderBreadcrumb() : null}\n <div class=\"bar-title__row\">\n <div class=\"bar-title__leading\">\n <div class=\"bar-title__title-row\" data-shell-page-header-anchor>\n <div class=\"bar-title__identity\">\n {compact ? this.renderBack() : null}\n <ds-text\n class=\"bar-title__heading ds-control--md\"\n variant={compact ? 'text-title-small' : 'text-title-medium'}\n emphasis\n color=\"primary\"\n as=\"h1\"\n lineTruncation={1}\n >\n {this.heading}\n </ds-text>\n </div>\n {this.renderSectionSelector()}\n </div>\n {this.description && !compact ? (\n <ds-text\n class=\"bar-title__description\"\n variant=\"text-body-small\"\n color=\"secondary\"\n lineTruncation={2}\n wrap=\"wrap\"\n as=\"p\"\n >\n {this.description}\n </ds-text>\n ) : null}\n </div>\n {this.renderActions()}\n </div>\n </div>\n </div>\n\n {this.hasSectionSelector ? (\n <ds-menu\n id={this.sectionMenuId}\n class=\"bar-title__section-menu\"\n anchorId={this.sectionMenuTriggerId}\n menuLabel={this.sectionsAriaLabel}\n open={this.sectionMenuOpen}\n initialFocusVisible={this.sectionMenuInitialFocusVisible}\n sections={this.sectionMenuSections}\n onDsClose={this.closeSectionMenu}\n onDsSelect={this.handleSectionSelect}\n />\n ) : null}\n\n {this.showActionMenuTrigger ? (\n <ds-menu\n id={this.actionMenuId}\n class=\"bar-title__action-menu\"\n anchorId={this.actionMenuTriggerId}\n align=\"end\"\n menuLabel={this.actionsAriaLabel}\n open={this.actionMenuOpen}\n initialFocusVisible={this.actionMenuInitialFocusVisible}\n sections={this.actionMenuSections}\n onDsClose={this.closeActionMenu}\n onDsSelect={this.handleActionSelect}\n />\n ) : null}\n </Host>\n );\n }\n}\n",
|
|
481
|
+
"content": "import {\n Component,\n Element,\n Event,\n EventEmitter,\n h,\n Host,\n Prop,\n State,\n Watch,\n} from '@stencil/core';\nimport type { BreadcrumbItem, BreadcrumbSelectDetail } from '../Breadcrumb/breadcrumb-types';\nimport type { MenuItemData, MenuSection } from '../Menu/menu-types';\nimport {\n isBarTitleDivider,\n type BarTitleAction,\n type BarTitleActionItem,\n type BarTitlePrimaryAction,\n type BarTitleSection,\n type BarTitleSectionItem,\n type BarTitleVariant,\n} from './bar-title-types';\n\nlet nextBarTitleId = 0;\n\ntype FocusableButton = HTMLElement & { setFocus?: () => Promise<void> };\n\n@Component({\n tag: 'ds-bar-title',\n styleUrl: 'BarTitle.css',\n scoped: true,\n})\nexport class BarTitle {\n @Element() el!: HTMLElement;\n\n /** The page's single visible h1. */\n @Prop() heading!: string;\n\n /** Optional supporting copy shown in the expanded presentation. */\n @Prop() description: string = '';\n\n /** Show the leading page-level Back action. */\n @Prop({ attribute: 'show-back' }) showBack: boolean = false;\n\n /** Accessible name for the leading Back action. */\n @Prop() backAriaLabel: string = 'Back';\n\n /** Visible parent-page label used by the expanded breadcrumb when breadcrumbs is empty. */\n @Prop() backLabel: string = 'Back';\n\n /** Optional expanded ancestor path. Compact variants continue to use the Back action. */\n @Prop() breadcrumbs: BreadcrumbItem[] = [];\n\n /** Accessible name for the expanded breadcrumb navigation landmark. */\n @Prop() breadcrumbAriaLabel: string = 'Breadcrumb';\n\n /** Optional page sections exposed through the active-section menu. */\n @Prop() sections: BarTitleSectionItem[] = [];\n\n /** Id of the active page section. */\n @Prop() value: string = '';\n\n /** Accessible name for the page-section menu. */\n @Prop() sectionsAriaLabel: string = 'Change page section';\n\n /** The one highest-emphasis page action. */\n @Prop() primaryAction: BarTitlePrimaryAction | null = null;\n\n /** Secondary page actions shown in the overflow menu. Dividers create groups. */\n @Prop() actions: BarTitleActionItem[] = [];\n\n /** Accessible name for the page-actions menu. */\n @Prop() actionsAriaLabel: string = 'More page actions';\n\n /** Explicit visual/capacity variant. ShellPage owns automatic selection. */\n @Prop() variant: BarTitleVariant = 'expanded';\n\n /** Emitted when the leading Back action is activated. */\n @Event() dsBack!: EventEmitter<MouseEvent>;\n\n /** Emitted when an authored expanded breadcrumb item is activated. */\n @Event({ cancelable: true }) dsBreadcrumbSelect!: EventEmitter<BreadcrumbSelectDetail>;\n\n /** Emitted with the newly selected page-section id. */\n @Event() dsSectionChange!: EventEmitter<string>;\n\n /** Emitted with the activated primary or overflow action id. */\n @Event() dsAction!: EventEmitter<string>;\n\n @State() private sectionMenuOpen = false;\n @State() private actionMenuOpen = false;\n @State() private sectionMenuInitialFocusVisible = false;\n @State() private actionMenuInitialFocusVisible = false;\n\n private readonly instanceId = nextBarTitleId++;\n private readonly sectionMenuTriggerId = `bar-title-section-trigger-${this.instanceId}`;\n private readonly sectionMenuId = `bar-title-section-menu-${this.instanceId}`;\n private readonly actionMenuTriggerId = `bar-title-action-trigger-${this.instanceId}`;\n private readonly actionMenuId = `bar-title-action-menu-${this.instanceId}`;\n private sectionTriggerEl: HTMLButtonElement | null = null;\n private actionTriggerEl: FocusableButton | null = null;\n\n componentWillLoad() {\n if (this.el.closest('ds-shell-page')) {\n this.el.setAttribute('data-shell-page-syncing', '');\n }\n }\n\n @Watch('sections')\n handleSectionsChange() {\n if (!this.hasSectionSelector) this.closeSectionMenu();\n }\n\n @Watch('actions')\n @Watch('primaryAction')\n handleActionsChange() {\n if (!this.showActionMenuTrigger) this.closeActionMenu();\n }\n\n private get compact(): boolean {\n return this.variant !== 'expanded';\n }\n\n private get expandedBreadcrumbItems(): BreadcrumbItem[] {\n if (this.breadcrumbs.length > 0) return this.breadcrumbs;\n if (!this.showBack) return [];\n return [\n {\n id: 'back',\n label: this.backLabel,\n ariaLabel: this.backAriaLabel,\n },\n ];\n }\n\n /** Tooltip copy is fixed and generic; backAriaLabel/actionsAriaLabel stay page-specific. */\n private get backTooltipLabel(): string {\n return 'Go back';\n }\n\n private get primaryCollapsed(): boolean {\n return (\n this.variant === 'constrained' &&\n this.primaryAction !== null &&\n (this.primaryAction.collapse ?? 'auto') === 'auto'\n );\n }\n\n private get selectableSections(): BarTitleSection[] {\n return this.sections.filter((item): item is BarTitleSection => !isBarTitleDivider(item));\n }\n\n private get hasSectionSelector(): boolean {\n return this.selectableSections.length > 1;\n }\n\n private get effectiveValue(): string {\n return this.selectableSections.some(section => section.id === this.value)\n ? this.value\n : (this.selectableSections[0]?.id ?? '');\n }\n\n private get selectedSectionLabel(): string {\n return this.selectableSections.find(section => section.id === this.effectiveValue)?.label ?? '';\n }\n\n private get sectionTriggerAriaLabel(): string {\n return `${this.sectionsAriaLabel}. Current section: ${this.selectedSectionLabel}`;\n }\n\n private groupsFromItems<T extends BarTitleSection | BarTitleAction>(\n source: Array<T | { type: 'divider' }>,\n map: (item: T) => MenuItemData\n ): MenuSection[] {\n const groups: MenuSection[] = [];\n let items: MenuItemData[] = [];\n const commit = () => {\n if (items.length > 0) groups.push({ items });\n items = [];\n };\n\n for (const item of source) {\n if (isBarTitleDivider(item)) {\n commit();\n } else {\n items.push(map(item));\n }\n }\n commit();\n return groups;\n }\n\n private get sectionMenuSections(): MenuSection[] {\n return this.groupsFromItems(this.sections, section => ({\n label: section.label,\n value: section.id,\n isSelected: section.id === this.effectiveValue,\n isInactive: section.isInactive,\n }));\n }\n\n private get selectableActions(): BarTitleAction[] {\n return this.actions.filter((item): item is BarTitleAction => !isBarTitleDivider(item));\n }\n\n private get showActionMenuTrigger(): boolean {\n return (\n this.selectableActions.length > 0 || (this.primaryCollapsed && this.primaryAction !== null)\n );\n }\n\n private actionMenuItem(action: BarTitleAction | BarTitlePrimaryAction): MenuItemData {\n const primary = action as BarTitlePrimaryAction;\n return {\n label: action.label,\n value: action.id,\n isInactive: action.isInactive || primary.isLoading,\n isDestructive: action.isDestructive || primary.intent === 'negative',\n };\n }\n\n private get actionMenuSections(): MenuSection[] {\n const groups = this.groupsFromItems(this.actions, action => this.actionMenuItem(action));\n if (!this.primaryCollapsed || !this.primaryAction) return groups;\n return [{ items: [this.actionMenuItem(this.primaryAction)] }, ...groups];\n }\n\n private toggleSectionMenu = (event: MouseEvent) => {\n this.sectionMenuInitialFocusVisible = event.detail === 0;\n this.actionMenuOpen = false;\n this.sectionMenuOpen = !this.sectionMenuOpen;\n };\n\n private closeSectionMenu = () => {\n this.sectionMenuOpen = false;\n };\n\n private handleSectionSelect = (event: CustomEvent<MenuItemData>) => {\n const id = String(event.detail?.value ?? '');\n const selected = this.selectableSections.find(section => section.id === id);\n if (!selected || selected.isInactive) return;\n this.closeSectionMenu();\n this.dsSectionChange.emit(id);\n requestAnimationFrame(() => this.sectionTriggerEl?.focus());\n };\n\n private toggleActionMenu = (event: MouseEvent) => {\n this.actionMenuInitialFocusVisible = event.detail === 0;\n this.sectionMenuOpen = false;\n this.actionMenuOpen = !this.actionMenuOpen;\n };\n\n private closeActionMenu = () => {\n this.actionMenuOpen = false;\n };\n\n private handleActionSelect = (event: CustomEvent<MenuItemData>) => {\n const id = String(event.detail?.value ?? '');\n const primaryMatches = this.primaryCollapsed && this.primaryAction?.id === id;\n const action = this.selectableActions.find(candidate => candidate.id === id);\n if (!primaryMatches && !action) return;\n if (\n (primaryMatches && (this.primaryAction?.isInactive || this.primaryAction?.isLoading)) ||\n action?.isInactive\n ) {\n return;\n }\n this.closeActionMenu();\n this.dsAction.emit(id);\n requestAnimationFrame(() => void this.actionTriggerEl?.setFocus?.());\n };\n\n private renderSectionSelector() {\n if (!this.hasSectionSelector) return null;\n return [\n <div class=\"bar-title__divider\" aria-hidden=\"true\" />,\n <div class=\"bar-title__section-selector\">\n <button\n ref={el => {\n this.sectionTriggerEl = el ?? null;\n }}\n id={this.sectionMenuTriggerId}\n class={{\n 'bar-title__section-trigger': true,\n 'bar-title__section-trigger--expanded': this.sectionMenuOpen,\n 'ds-control--md': true,\n 'ds-focus-ring-inset': true,\n 'ds-interaction-fill': true,\n }}\n type=\"button\"\n aria-haspopup=\"menu\"\n aria-controls={this.sectionMenuId}\n aria-expanded={String(this.sectionMenuOpen)}\n aria-label={this.sectionTriggerAriaLabel}\n onClick={this.toggleSectionMenu}\n >\n <ds-text\n class=\"bar-title__section-label ds-interaction-fill__content\"\n as=\"span\"\n variant=\"text-body-medium\"\n emphasis\n color=\"primary\"\n lineTruncation={1}\n >\n {this.selectedSectionLabel}\n </ds-text>\n <ds-icon\n class=\"bar-title__section-chevron ds-interaction-fill__content\"\n name=\"ChevronUpDown\"\n size=\"md\"\n color=\"inherit\"\n aria-hidden=\"true\"\n />\n </button>\n </div>,\n ];\n }\n\n private renderActions() {\n const primary = this.primaryAction;\n if (!primary && !this.showActionMenuTrigger) return null;\n\n return (\n <div class=\"bar-title__actions\">\n {primary && !this.primaryCollapsed ? (\n <ds-button-filled\n class=\"bar-title__primary-action\"\n variant={primary.icon ? 'icon-label' : 'label'}\n icon={primary.icon ?? ''}\n label={primary.label}\n intent={primary.intent ?? 'brand'}\n contrast={primary.contrast ?? 'bold'}\n size=\"md\"\n type={primary.type ?? 'button'}\n isInactive={primary.isInactive}\n isLoading={primary.isLoading}\n onDsClick={() => this.dsAction.emit(primary.id)}\n />\n ) : null}\n {this.showActionMenuTrigger ? (\n <ds-tooltip label=\"Page options\" side=\"bottom\" size=\"sm\">\n <ds-button-unfilled\n ref={el => {\n this.actionTriggerEl = el ?? null;\n }}\n id={this.actionMenuTriggerId}\n class=\"bar-title__more-actions\"\n variant=\"icon\"\n icon=\"Ellipses\"\n aria-label={this.actionsAriaLabel}\n size=\"md\"\n activeFill={!this.compact}\n hasBorder={primary !== null && !this.primaryCollapsed}\n haspopup=\"menu\"\n controls={this.actionMenuId}\n expanded={this.actionMenuOpen}\n onDsClick={(event: CustomEvent<MouseEvent>) => this.toggleActionMenu(event.detail)}\n />\n </ds-tooltip>\n ) : null}\n </div>\n );\n }\n\n private handleBreadcrumbSelect = (event: CustomEvent<BreadcrumbSelectDetail>) => {\n if (this.breadcrumbs.length === 0) {\n this.dsBack.emit(event.detail.originalEvent);\n return;\n }\n const forwarded = this.dsBreadcrumbSelect.emit(event.detail);\n if (forwarded.defaultPrevented) event.preventDefault();\n };\n\n private renderBreadcrumb() {\n const items = this.expandedBreadcrumbItems;\n if (items.length === 0) return null;\n return (\n <ds-breadcrumb\n class=\"bar-title__breadcrumb\"\n items={items}\n ariaLabel={this.breadcrumbAriaLabel}\n onDsSelect={this.handleBreadcrumbSelect}\n />\n );\n }\n\n private renderBack() {\n if (!this.showBack) return null;\n return (\n <ds-tooltip label={this.backTooltipLabel} side=\"bottom\" size=\"sm\">\n <ds-button-unfilled\n class=\"bar-title__back\"\n variant=\"icon\"\n icon=\"ChevronLeft\"\n aria-label={this.backAriaLabel}\n size=\"md\"\n activeFill={false}\n hasBorder={false}\n onDsClick={(event: CustomEvent<MouseEvent>) => this.dsBack.emit(event.detail)}\n />\n </ds-tooltip>\n );\n }\n\n render() {\n const compact = this.compact;\n return (\n <Host\n class={{\n 'bar-title-host--compact': compact,\n 'bar-title-host--expanded': this.variant === 'expanded',\n 'bar-title-host--constrained': this.variant === 'constrained',\n 'bar-title-host--has-description': !!this.description,\n 'bar-title-host--has-back': this.showBack,\n 'bar-title-host--has-breadcrumb': !compact && this.expandedBreadcrumbItems.length > 0,\n }}\n >\n <div\n class={{\n 'bar-title': true,\n 'ds-chrome-header': compact,\n }}\n >\n <div class=\"bar-title__inner\">\n {!compact ? this.renderBreadcrumb() : null}\n <div class=\"bar-title__row\">\n <div class=\"bar-title__leading\">\n <div class=\"bar-title__title-row\" data-shell-page-header-anchor>\n <div class=\"bar-title__identity\">\n {compact ? this.renderBack() : null}\n <ds-text\n class=\"bar-title__heading ds-control--md\"\n variant={compact ? 'text-title-small' : 'text-title-medium'}\n emphasis\n color=\"primary\"\n as=\"h1\"\n lineTruncation={1}\n >\n {this.heading}\n </ds-text>\n </div>\n {this.renderSectionSelector()}\n </div>\n {this.description && !compact ? (\n <ds-text\n class=\"bar-title__description\"\n variant=\"text-body-small\"\n color=\"secondary\"\n lineTruncation={2}\n wrap=\"wrap\"\n as=\"p\"\n >\n {this.description}\n </ds-text>\n ) : null}\n </div>\n {this.renderActions()}\n </div>\n </div>\n </div>\n\n {this.hasSectionSelector ? (\n <ds-menu\n id={this.sectionMenuId}\n class=\"bar-title__section-menu\"\n anchorId={this.sectionMenuTriggerId}\n menuLabel={this.sectionsAriaLabel}\n open={this.sectionMenuOpen}\n initialFocusVisible={this.sectionMenuInitialFocusVisible}\n sections={this.sectionMenuSections}\n onDsClose={this.closeSectionMenu}\n onDsSelect={this.handleSectionSelect}\n />\n ) : null}\n\n {this.showActionMenuTrigger ? (\n <ds-menu\n id={this.actionMenuId}\n class=\"bar-title__action-menu\"\n anchorId={this.actionMenuTriggerId}\n align=\"end\"\n menuLabel={this.actionsAriaLabel}\n open={this.actionMenuOpen}\n initialFocusVisible={this.actionMenuInitialFocusVisible}\n sections={this.actionMenuSections}\n onDsClose={this.closeActionMenu}\n onDsSelect={this.handleActionSelect}\n />\n ) : null}\n </Host>\n );\n }\n}\n",
|
|
482
482
|
"type": "registry:ui"
|
|
483
483
|
},
|
|
484
484
|
{
|
|
@@ -351,7 +351,7 @@
|
|
|
351
351
|
"complexPropertyNote": "Assign these non-primitive values as JavaScript properties: steps, submitAction.",
|
|
352
352
|
"peerDependencies": {
|
|
353
353
|
"required": [
|
|
354
|
-
"@ds-mo/tokens ^5.1.
|
|
354
|
+
"@ds-mo/tokens ^5.1.1",
|
|
355
355
|
"@ds-mo/icons ^6.4.0"
|
|
356
356
|
],
|
|
357
357
|
"frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
|
|
@@ -171,7 +171,7 @@
|
|
|
171
171
|
"complexPropertyNote": "Assign these non-primitive values as JavaScript properties: items.",
|
|
172
172
|
"peerDependencies": {
|
|
173
173
|
"required": [
|
|
174
|
-
"@ds-mo/tokens ^5.1.
|
|
174
|
+
"@ds-mo/tokens ^5.1.1",
|
|
175
175
|
"@ds-mo/icons ^6.4.0"
|
|
176
176
|
],
|
|
177
177
|
"frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
|