@ds-mo/ui 12.0.0 → 12.2.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 +18 -1
- package/dist/.build-stamp +1 -1
- package/dist/agent-patterns.json +3 -2
- package/dist/agent.json +314 -41
- package/dist/angular/ds-avatar.d.ts +1 -1
- package/dist/angular/ds-avatar.js +3 -3
- package/dist/angular/ds-bar-workflow.d.ts +28 -0
- package/dist/angular/ds-bar-workflow.js +48 -0
- package/dist/angular/ds-conversation-list.d.ts +1 -1
- package/dist/angular/ds-conversation-list.js +4 -3
- package/dist/angular/ds-scroll-overlay.d.ts +19 -0
- package/dist/angular/ds-scroll-overlay.js +43 -0
- package/dist/angular/index.d.ts +1 -1
- package/dist/angular/index.js +2 -0
- package/dist/angular/proxies.d.ts +2 -0
- package/dist/angular/proxies.js +2 -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-avatar.js +1 -1
- package/dist/components/ds-avatar.js.map +1 -1
- package/dist/components/ds-badge.js +1 -1
- package/dist/components/ds-bar-nav.js +1 -1
- package/dist/components/ds-bar-nav.js.map +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.d.ts +11 -0
- package/dist/components/ds-bar-workflow.js +2 -0
- package/dist/components/ds-bar-workflow.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-donut.js +1 -1
- package/dist/components/ds-card-data-viz-donut.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-card-shell-data-viz.js +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-chip.js.map +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-section.js.map +1 -1
- package/dist/components/ds-conversation-list.js +1 -1
- package/dist/components/ds-conversation-list.js.map +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-icon.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-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-composer.js.map +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-panel-nav.js +1 -1
- package/dist/components/ds-panel-sub-nav.js +1 -1
- package/dist/components/ds-panel-sub-nav.js.map +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-radio.js.map +1 -1
- package/dist/components/ds-scroll-overlay.d.ts +11 -0
- package/dist/components/ds-scroll-overlay.js +2 -0
- package/dist/components/ds-scroll-overlay.js.map +1 -0
- 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-gradient-picker.js +1 -1
- package/dist/components/ds-shell-gradient-swatch.js +1 -1
- package/dist/components/ds-shell-gradient-swatch.js.map +1 -1
- package/dist/components/ds-shell-page.js +1 -1
- package/dist/components/ds-shell-page.js.map +1 -1
- 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-tag.js.map +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-2W-ZjZCR.js +2 -0
- package/dist/components/{p-CwTVEAl2.js.map → p-2W-ZjZCR.js.map} +1 -1
- package/dist/components/p-8OOCLGph.js +2 -0
- package/dist/components/p-8OOCLGph.js.map +1 -0
- package/dist/components/{p-BYHKdFzV.js → p-B8CubPgQ.js} +2 -2
- package/dist/components/{p-DuNZGFkV.js → p-BAfG3M8z.js} +2 -2
- package/dist/components/p-BAfG3M8z.js.map +1 -0
- package/dist/components/p-BE_CDJeL.js +2 -0
- package/dist/components/p-BE_CDJeL.js.map +1 -0
- package/dist/components/{p-BH3es4p7.js → p-BRLDJmkh.js} +2 -2
- package/dist/components/p-BpA9uYKX.js +2 -0
- package/dist/components/p-BpA9uYKX.js.map +1 -0
- package/dist/components/{p-Bs6SS34m.js → p-C0_8K4y7.js} +2 -2
- package/dist/components/{p-BhKhX1er.js → p-CA3kLO7K.js} +2 -2
- package/dist/components/{p-DfIch2Dk.js → p-CCHqsF_H.js} +2 -2
- package/dist/components/p-CG5EX4NW.js +2 -0
- package/dist/components/{p-DABj3qTX.js.map → p-CG5EX4NW.js.map} +1 -1
- package/dist/components/{p-DVk0wxxX.js → p-CHkI3oIS.js} +2 -2
- package/dist/components/{p-liMPkhUP.js → p-CNffejay.js} +2 -2
- package/dist/components/{p-B3YvoGjS.js → p-Cxpl-rzP.js} +2 -2
- package/dist/components/p-CydY-v7X.js +2 -0
- package/dist/components/p-CydY-v7X.js.map +1 -0
- package/dist/components/{p-BkwBtZs3.js → p-D6jK2Hyi.js} +2 -2
- package/dist/components/{p-Cy04lXlc.js → p-DBi1bmOL.js} +2 -2
- package/dist/components/{p-DxeHimqk.js → p-DOCKtrS4.js} +2 -2
- package/dist/components/p-DbpfpeQT.js +2 -0
- package/dist/components/p-DbpfpeQT.js.map +1 -0
- package/dist/components/{p-6vj4kh70.js → p-DcGpdIlT.js} +2 -2
- package/dist/components/p-DyGB97Oy.js +2 -0
- package/dist/components/{p-Dy_sZ-dI.js.map → p-DyGB97Oy.js.map} +1 -1
- package/dist/components/p-GMDGgtY8.js +2 -0
- package/dist/components/{p-BUmDHPr3.js.map → p-GMDGgtY8.js.map} +1 -1
- package/dist/components/{p-Cf2J9MEo.js → p-XgIQ3oI_.js} +2 -2
- package/dist/components/{p-Cf-i-6aE.js → p-ky1msbJ8.js} +2 -2
- package/dist/components/p-xY3TjBF5.js +2 -0
- package/dist/components/{p-DUHcJWTQ.js.map → p-xY3TjBF5.js.map} +1 -1
- package/dist/docs/components.json +685 -56
- package/dist/lib/utils/anchored-popup.d.ts +5 -1
- package/dist/mcp-data/registry/agent-activity.json +1 -1
- package/dist/mcp-data/registry/agent-response.json +10 -5
- 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 +27 -8
- package/dist/mcp-data/registry/badge.json +1 -1
- package/dist/mcp-data/registry/bar-nav.json +1 -1
- package/dist/mcp-data/registry/bar-title.json +13 -8
- package/dist/mcp-data/registry/bar-workflow.json +368 -0
- package/dist/mcp-data/registry/button-filled.json +2 -2
- package/dist/mcp-data/registry/button-unfilled.json +2 -2
- package/dist/mcp-data/registry/card-data-viz-donut.json +1 -1
- package/dist/mcp-data/registry/card-setting.json +1 -1
- package/dist/mcp-data/registry/card-shell-data-viz.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 +2 -2
- 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 +2 -2
- package/dist/mcp-data/registry/conversation-list-section.json +3 -3
- package/dist/mcp-data/registry/conversation-list.json +37 -10
- 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 +2 -2
- package/dist/mcp-data/registry/input.json +1 -1
- package/dist/mcp-data/registry/loader.json +1 -1
- package/dist/mcp-data/registry/markdown.json +16 -11
- package/dist/mcp-data/registry/menu.json +10 -8
- 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 +4 -4
- package/dist/mcp-data/registry/message.json +1 -1
- package/dist/mcp-data/registry/modal.json +1 -1
- 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 +1 -1
- package/dist/mcp-data/registry/panel-tool-search.json +2 -2
- package/dist/mcp-data/registry/panel-tools.json +1 -1
- package/dist/mcp-data/registry/radio.json +1 -1
- package/dist/mcp-data/registry/registry.json +320 -41
- package/dist/mcp-data/registry/scroll-overlay.json +183 -0
- package/dist/mcp-data/registry/select-multi.json +1 -1
- package/dist/mcp-data/registry/select.json +1 -1
- package/dist/mcp-data/registry/shell-app.json +1 -1
- package/dist/mcp-data/registry/shell-gradient-picker.json +1 -1
- package/dist/mcp-data/registry/shell-gradient-swatch.json +2 -2
- package/dist/mcp-data/registry/shell-page.json +15 -11
- package/dist/mcp-data/registry/skeleton.json +1 -1
- package/dist/mcp-data/registry/slider.json +2 -2
- package/dist/mcp-data/registry/swatch-picker.json +1 -1
- package/dist/mcp-data/registry/switch.json +2 -2
- package/dist/mcp-data/registry/tab-group.json +2 -2
- 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 +2 -0
- package/dist/react/components.js +2 -0
- package/dist/react/ds-bar-workflow.d.ts +14 -0
- package/dist/react/ds-bar-workflow.js +16 -0
- package/dist/react/ds-scroll-overlay.d.ts +12 -0
- package/dist/react/ds-scroll-overlay.js +12 -0
- package/dist/styles/prose.css +214 -0
- package/dist/types/components/Avatar/Avatar.d.ts +4 -1
- package/dist/types/components/BarTitle/BarTitle.d.ts +2 -0
- package/dist/types/components/BarWorkflow/BarWorkflow.d.ts +37 -0
- package/dist/types/components/BarWorkflow/bar-workflow-types.d.ts +12 -0
- package/dist/types/components/ConversationList/ConversationList.d.ts +12 -0
- package/dist/types/components/Menu/Menu.d.ts +1 -2
- package/dist/types/components/MessageScroller/MessageScroller.d.ts +1 -6
- package/dist/types/components/ScrollOverlay/ScrollOverlay.d.ts +28 -0
- package/dist/types/components/ShellPage/ShellPage.d.ts +5 -0
- package/dist/types/components.d.ts +231 -3
- package/dist/types/stories/isolated-overlay-docs.d.ts +13 -0
- package/dist/types/utils/anchored-popup.d.ts +5 -1
- package/dist/types/utils/scroll-overlay-controller.d.ts +35 -0
- package/package.json +6 -5
- package/dist/components/p-BUmDHPr3.js +0 -2
- package/dist/components/p-BZZwQfmN.js +0 -2
- package/dist/components/p-BZZwQfmN.js.map +0 -1
- package/dist/components/p-CUik7PCi.js +0 -2
- package/dist/components/p-CUik7PCi.js.map +0 -1
- package/dist/components/p-CwTVEAl2.js +0 -2
- package/dist/components/p-DABj3qTX.js +0 -2
- package/dist/components/p-DUHcJWTQ.js +0 -2
- package/dist/components/p-DuNZGFkV.js.map +0 -1
- package/dist/components/p-Dy_sZ-dI.js +0 -2
- package/dist/components/p-l6PmnW_P.js +0 -2
- package/dist/components/p-l6PmnW_P.js.map +0 -1
- package/dist/components/p-xFbUt9Tc.js +0 -2
- package/dist/components/p-xFbUt9Tc.js.map +0 -1
- /package/dist/components/{p-BYHKdFzV.js.map → p-B8CubPgQ.js.map} +0 -0
- /package/dist/components/{p-BH3es4p7.js.map → p-BRLDJmkh.js.map} +0 -0
- /package/dist/components/{p-Bs6SS34m.js.map → p-C0_8K4y7.js.map} +0 -0
- /package/dist/components/{p-BhKhX1er.js.map → p-CA3kLO7K.js.map} +0 -0
- /package/dist/components/{p-DfIch2Dk.js.map → p-CCHqsF_H.js.map} +0 -0
- /package/dist/components/{p-DVk0wxxX.js.map → p-CHkI3oIS.js.map} +0 -0
- /package/dist/components/{p-liMPkhUP.js.map → p-CNffejay.js.map} +0 -0
- /package/dist/components/{p-B3YvoGjS.js.map → p-Cxpl-rzP.js.map} +0 -0
- /package/dist/components/{p-BkwBtZs3.js.map → p-D6jK2Hyi.js.map} +0 -0
- /package/dist/components/{p-Cy04lXlc.js.map → p-DBi1bmOL.js.map} +0 -0
- /package/dist/components/{p-DxeHimqk.js.map → p-DOCKtrS4.js.map} +0 -0
- /package/dist/components/{p-6vj4kh70.js.map → p-DcGpdIlT.js.map} +0 -0
- /package/dist/components/{p-Cf2J9MEo.js.map → p-XgIQ3oI_.js.map} +0 -0
- /package/dist/components/{p-Cf-i-6aE.js.map → p-ky1msbJ8.js.map} +0 -0
|
@@ -12,7 +12,11 @@ export interface AnchoredPopupPositionInput {
|
|
|
12
12
|
viewportWidth: number;
|
|
13
13
|
viewportHeight: number;
|
|
14
14
|
}
|
|
15
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* Pure viewport-fixed anchored-popup layout math shared by menus and selects.
|
|
17
|
+
* `side` is preferred: when the popup does not fit there and the opposite side
|
|
18
|
+
* has more room, placement flips on the main axis before viewport clamping.
|
|
19
|
+
*/
|
|
16
20
|
export declare function computeAnchoredPopupPosition(input: AnchoredPopupPositionInput): {
|
|
17
21
|
x: number;
|
|
18
22
|
y: number;
|
|
@@ -36,15 +36,20 @@
|
|
|
36
36
|
],
|
|
37
37
|
"states": [
|
|
38
38
|
"The owner replaces immutable part arrays as scripted or live output changes.",
|
|
39
|
-
"AgentResponse renders directly in Message without borrowing a plain MessageBubble surface."
|
|
39
|
+
"AgentResponse renders directly in Message without borrowing a plain MessageBubble surface.",
|
|
40
|
+
"Markdown parts use the optional safe renderer; prose CSS owns only semantic-tree styling and never response ordering or streaming state."
|
|
40
41
|
],
|
|
41
42
|
"responsiveBehavior": [
|
|
42
|
-
"Rich content uses the available transcript width while respecting readable measure and overflow constraints."
|
|
43
|
+
"Rich content uses the available transcript width while respecting readable measure and local overflow constraints."
|
|
43
44
|
],
|
|
44
45
|
"references": [
|
|
45
46
|
{
|
|
46
|
-
"label": "
|
|
47
|
-
"path": "src/wc/components/
|
|
47
|
+
"label": "Representative streamed response",
|
|
48
|
+
"path": "src/wc/components/AgentResponse/AgentResponse.stories.ts"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"label": "Prose distribution decision",
|
|
52
|
+
"path": "docs/prose-foundation.md"
|
|
48
53
|
}
|
|
49
54
|
]
|
|
50
55
|
},
|
|
@@ -178,7 +183,7 @@
|
|
|
178
183
|
"peerDependencies": {
|
|
179
184
|
"required": [
|
|
180
185
|
"@ds-mo/tokens ^3.1.1",
|
|
181
|
-
"@ds-mo/icons ^6.
|
|
186
|
+
"@ds-mo/icons ^6.4.0"
|
|
182
187
|
],
|
|
183
188
|
"frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
|
|
184
189
|
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"status": "experimental",
|
|
16
16
|
"summary": "Compact icon avatar for visually identifying a person, group, channel, or conversation type.",
|
|
17
17
|
"useWhen": [
|
|
18
|
-
"A list or message composition needs a consistent
|
|
18
|
+
"A list or message composition needs a consistent circular identity marker represented by an icon at md, sm, or xs control density."
|
|
19
19
|
],
|
|
20
20
|
"avoidWhen": [
|
|
21
21
|
"The identity has a supplied photograph, initials, presence state, or editable profile behavior; those variants are not part of this primitive."
|
|
@@ -26,10 +26,11 @@
|
|
|
26
26
|
],
|
|
27
27
|
"accessibility": [
|
|
28
28
|
"Provide label only when the icon communicates meaning that surrounding text does not already convey.",
|
|
29
|
-
"Omit label when the owning row or message already provides an equivalent accessible name."
|
|
29
|
+
"Omit label when the owning row or message already provides an equivalent accessible name.",
|
|
30
|
+
"Treat primary and secondary as general icon hierarchy; an owning product may map that hierarchy to states such as unread and read, but Avatar does not own those states."
|
|
30
31
|
],
|
|
31
32
|
"responsiveBehavior": [
|
|
32
|
-
"The
|
|
33
|
+
"The selected size stays fixed across viewports: md is a 32px circle with a 20px icon, sm is 24px with 16px, and xs is 16px with 12px."
|
|
33
34
|
],
|
|
34
35
|
"references": [
|
|
35
36
|
{
|
|
@@ -56,7 +57,7 @@
|
|
|
56
57
|
"default": "'secondary'",
|
|
57
58
|
"required": false,
|
|
58
59
|
"mutable": false,
|
|
59
|
-
"description": "Semantic
|
|
60
|
+
"description": "Semantic icon hierarchy. Use primary for stronger emphasis; secondary is the default."
|
|
60
61
|
},
|
|
61
62
|
"label": {
|
|
62
63
|
"type": "string | undefined",
|
|
@@ -65,6 +66,15 @@
|
|
|
65
66
|
"required": false,
|
|
66
67
|
"mutable": false,
|
|
67
68
|
"description": "Optional accessible label. Omit when the surrounding content already conveys the meaning."
|
|
69
|
+
},
|
|
70
|
+
"size": {
|
|
71
|
+
"type": "AvatarSize",
|
|
72
|
+
"resolvedType": "\"md\" | \"sm\" | \"xs\"",
|
|
73
|
+
"attribute": "size",
|
|
74
|
+
"default": "'md'",
|
|
75
|
+
"required": false,
|
|
76
|
+
"mutable": false,
|
|
77
|
+
"description": "Circle and icon density. Maps to 32/20, 24/16, or 16/12 px."
|
|
68
78
|
}
|
|
69
79
|
},
|
|
70
80
|
"events": [],
|
|
@@ -88,7 +98,7 @@
|
|
|
88
98
|
"default": "'secondary'",
|
|
89
99
|
"required": false,
|
|
90
100
|
"mutable": false,
|
|
91
|
-
"description": "Semantic
|
|
101
|
+
"description": "Semantic icon hierarchy. Use primary for stronger emphasis; secondary is the default."
|
|
92
102
|
},
|
|
93
103
|
"label": {
|
|
94
104
|
"type": "string | undefined",
|
|
@@ -97,6 +107,15 @@
|
|
|
97
107
|
"required": false,
|
|
98
108
|
"mutable": false,
|
|
99
109
|
"description": "Optional accessible label. Omit when the surrounding content already conveys the meaning."
|
|
110
|
+
},
|
|
111
|
+
"size": {
|
|
112
|
+
"type": "AvatarSize",
|
|
113
|
+
"resolvedType": "\"md\" | \"sm\" | \"xs\"",
|
|
114
|
+
"attribute": "size",
|
|
115
|
+
"default": "'md'",
|
|
116
|
+
"required": false,
|
|
117
|
+
"mutable": false,
|
|
118
|
+
"description": "Circle and icon density. Maps to 32/20, 24/16, or 16/12 px."
|
|
100
119
|
}
|
|
101
120
|
},
|
|
102
121
|
"events": [],
|
|
@@ -125,7 +144,7 @@
|
|
|
125
144
|
"peerDependencies": {
|
|
126
145
|
"required": [
|
|
127
146
|
"@ds-mo/tokens ^3.1.1",
|
|
128
|
-
"@ds-mo/icons ^6.
|
|
147
|
+
"@ds-mo/icons ^6.4.0"
|
|
129
148
|
],
|
|
130
149
|
"frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
|
|
131
150
|
}
|
|
@@ -142,12 +161,12 @@
|
|
|
142
161
|
"files": [
|
|
143
162
|
{
|
|
144
163
|
"path": "src/wc/components/Avatar/Avatar.css",
|
|
145
|
-
"content": ":host {\n display: inline-flex;\n flex-shrink: 0;\n width: var(--
|
|
164
|
+
"content": "@import '../../utils/control-density.css';\n\n:host {\n display: inline-flex;\n flex-shrink: 0;\n width: var(--ds-control-height);\n height: var(--ds-control-height);\n}\n\n:host(.avatar--md) {\n --_avatar-stroke: var(--dimension-stroke-width-015);\n}\n\n:host(.avatar--sm) {\n --_avatar-stroke: var(--dimension-stroke-width-012);\n}\n\n:host(.avatar--xs) {\n --_avatar-stroke: calc(var(--dimension-stroke-width-base) * 3 / 32);\n}\n\n.avatar {\n display: flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n width: 100%;\n height: 100%;\n border-radius: var(--dimension-radius-half);\n box-shadow: inset 0 0 0 var(--_avatar-stroke) var(--color-foreground-quaternary);\n}\n",
|
|
146
165
|
"type": "registry:ui"
|
|
147
166
|
},
|
|
148
167
|
{
|
|
149
168
|
"path": "src/wc/components/Avatar/Avatar.tsx",
|
|
150
|
-
"content": "import { Component, h, Host, Prop } from '@stencil/core';\nimport type { IconColor } from '../Icon/Icon';\n\n@Component({\n tag: 'ds-avatar',\n styleUrl: 'Avatar.css',\n scoped: true,\n})\nexport class Avatar {\n /** Canonical IcoMo icon name displayed inside the avatar. */\n @Prop() icon: string = '';\n\n /** Semantic
|
|
169
|
+
"content": "import { Component, h, Host, Prop } from '@stencil/core';\nimport type { IconColor } from '../Icon/Icon';\n\nexport type AvatarSize = 'md' | 'sm' | 'xs';\n\n@Component({\n tag: 'ds-avatar',\n styleUrl: 'Avatar.css',\n scoped: true,\n})\nexport class Avatar {\n /** Canonical IcoMo icon name displayed inside the avatar. */\n @Prop() icon: string = '';\n\n /** Semantic icon hierarchy. Use primary for stronger emphasis; secondary is the default. */\n @Prop() iconColor: IconColor = 'secondary';\n\n /** Circle and icon density. Maps to 32/20, 24/16, or 16/12 px. */\n @Prop() size: AvatarSize = 'md';\n\n /** Optional accessible label. Omit when the surrounding content already conveys the meaning. */\n @Prop() label: string | undefined;\n\n render() {\n return (\n <Host class={{ [`ds-control--${this.size}`]: true, [`avatar--${this.size}`]: true }}>\n <span\n class=\"avatar\"\n role={this.label ? 'img' : 'presentation'}\n aria-label={this.label}\n aria-hidden={!this.label ? 'true' : undefined}\n >\n <ds-icon name={this.icon} size={this.size} color={this.iconColor} />\n </span>\n </Host>\n );\n }\n}\n",
|
|
151
170
|
"type": "registry:ui"
|
|
152
171
|
}
|
|
153
172
|
]
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
3
|
"name": "bar-title",
|
|
4
4
|
"title": "BarTitle",
|
|
5
|
-
"description": "Page-level title chrome with optional
|
|
5
|
+
"description": "Page-level title chrome with optional route navigation, supporting description, inline active-section menu, one primary action, grouped overflow actions, and explicit expanded, compact, or constrained variants.",
|
|
6
6
|
"type": "registry:ui",
|
|
7
7
|
"meta": {
|
|
8
8
|
"library": "@ds-mo/ui",
|
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
"intent": {
|
|
14
14
|
"audience": "general",
|
|
15
15
|
"status": "stable",
|
|
16
|
-
"summary": "Page-level title chrome with optional
|
|
16
|
+
"summary": "Page-level title chrome with optional route navigation, supporting description, inline active-section menu, one primary action, grouped overflow actions, and explicit expanded, compact, or constrained variants.",
|
|
17
17
|
"useWhen": [
|
|
18
18
|
"A top-level page needs one h1 above its content.",
|
|
19
19
|
"A list or detail page has multiple route-level sections that belong in the inline active-section menu.",
|
|
20
|
-
"A detail
|
|
20
|
+
"A detail page needs a page-level Back action.",
|
|
21
21
|
"A page needs one primary action plus optional secondary or destructive overflow commands."
|
|
22
22
|
],
|
|
23
23
|
"avoidWhen": [
|
|
@@ -28,6 +28,11 @@
|
|
|
28
28
|
"Automatic sticky or responsive page orchestration is required; compose BarTitle in ShellPage rather than implementing it in the header."
|
|
29
29
|
],
|
|
30
30
|
"alternatives": [
|
|
31
|
+
{
|
|
32
|
+
"when": "A create or edit workflow needs Exit, persistent step navigation, and a final Save or Submit action.",
|
|
33
|
+
"component": "component:ds-bar-workflow",
|
|
34
|
+
"reason": "BarWorkflow owns compact-only workflow progression rather than responsive page identity."
|
|
35
|
+
},
|
|
31
36
|
{
|
|
32
37
|
"when": "The header belongs to a PanelTools product view.",
|
|
33
38
|
"component": "component:ds-panel-tool-header",
|
|
@@ -60,8 +65,8 @@
|
|
|
60
65
|
"Omitting Back, sections, primaryAction, and actions produces the top-level title-only header.",
|
|
61
66
|
"Two or more selectable sections add the inline section trigger; one section does not render a redundant menu.",
|
|
62
67
|
"Dividers in sections or actions create separate Menu groups without becoming selectable rows.",
|
|
63
|
-
"expanded renders the larger heading, optional description, inset divider, and an xs icon-label Back breadcrumb
|
|
64
|
-
"primaryAction collapse defaults to auto. Use never for
|
|
68
|
+
"expanded renders the larger heading, optional description, inset divider, and an xs icon-label Back breadcrumb in the top inset. The title and right-side actions share one top alignment whether Back is present, while the description remains in the left title column; compact renders 48px chrome with inline Back; constrained uses compact chrome and moves an auto-collapse primary action into overflow.",
|
|
69
|
+
"primaryAction collapse defaults to auto. Use never for exceptional actions whose hierarchy must remain visible in constrained layouts."
|
|
65
70
|
],
|
|
66
71
|
"responsiveBehavior": [
|
|
67
72
|
"BarTitle does not observe viewport, shell, scroll, or container state. Its owner selects an explicit variant.",
|
|
@@ -392,7 +397,7 @@
|
|
|
392
397
|
"peerDependencies": {
|
|
393
398
|
"required": [
|
|
394
399
|
"@ds-mo/tokens ^3.1.1",
|
|
395
|
-
"@ds-mo/icons ^6.
|
|
400
|
+
"@ds-mo/icons ^6.4.0"
|
|
396
401
|
],
|
|
397
402
|
"frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
|
|
398
403
|
}
|
|
@@ -414,12 +419,12 @@
|
|
|
414
419
|
"files": [
|
|
415
420
|
{
|
|
416
421
|
"path": "src/wc/components/BarTitle/BarTitle.css",
|
|
417
|
-
"content": "@import '../../utils/control-density.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.bar-title {\n position: relative;\n min-width: 0;\n padding: 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(--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 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:
|
|
422
|
+
"content": "@import '../../utils/control-density.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 padding: 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(--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-050);\n min-width: 0;\n}\n\n/*\n * Pull the whole leading block up by exactly the breadcrumb row's own box\n * (its xs control-height + its top margin + the row-gap below it), so the\n * title row lands flush with the actions' top without moving actions.\n * Tied to the same tokens that size the breadcrumb, not a guessed pixel value.\n */\n:host(.bar-title-host--expanded.bar-title-host--has-back) .bar-title__leading {\n margin-block-start: calc(-1 * (var(--dimension-size-200) + var(--dimension-space-050) * 2));\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__breadcrumb-back,\n.bar-title__section-chevron {\n flex: 0 0 auto;\n}\n\n.bar-title__breadcrumb {\n display: flex;\n align-items: center;\n min-width: 0;\n margin-block-start: var(--dimension-space-050);\n}\n\n.bar-title__heading {\n display: block;\n width: auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\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: pointer;\n font-family: inherit;\n user-select: none;\n transition: color var(--effect-motion-short-2);\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 overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\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 padding: var(--dimension-space-100);\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 height: 100%;\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\n@media (prefers-reduced-motion: reduce) {\n .bar-title__section-trigger {\n transition: none;\n }\n}\n",
|
|
418
423
|
"type": "registry:ui"
|
|
419
424
|
},
|
|
420
425
|
{
|
|
421
426
|
"path": "src/wc/components/BarTitle/BarTitle.tsx",
|
|
422
|
-
"content": "import { Component, Event, EventEmitter, h, Host, Prop, State, Watch } from '@stencil/core';\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 /** 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 Back breadcrumb. */\n @Prop() backLabel: string = 'Back';\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 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 @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 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 >\n {this.selectedSectionLabel}\n </ds-text>\n <ds-icon\n class=\"bar-title__section-chevron ds-interaction-fill__content\"\n name=\"ChevronUpDown\"\n size=\"sm\"\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-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={!this.compact && !this.primaryCollapsed}\n haspopup=\"menu\"\n controls={this.actionMenuId}\n expanded={this.actionMenuOpen}\n onDsClick={(event: CustomEvent<MouseEvent>) => this.toggleActionMenu(event.detail)}\n />\n ) : null}\n </div>\n );\n }\n\n private renderBack(compact: boolean) {\n if (!this.showBack) return null;\n\n if (!compact) {\n return (\n <div class=\"bar-title__breadcrumb\">\n <ds-button-unfilled\n class=\"bar-title__breadcrumb-back\"\n variant=\"icon-label\"\n icon=\"ChevronLeft\"\n label={this.backLabel}\n aria-label={this.backAriaLabel}\n size=\"xs\"\n hasBorder={false}\n onDsClick={(event: CustomEvent<MouseEvent>) => this.dsBack.emit(event.detail)}\n />\n </div>\n );\n }\n\n return (\n <ds-tooltip label={this.backAriaLabel} 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 }}\n >\n <div class=\"bar-title\">\n <div class=\"bar-title__inner\">\n {!compact ? this.renderBack(false) : null}\n <div class=\"bar-title__row\">\n <div class=\"bar-title__identity\">\n {compact ? this.renderBack(true) : 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 {this.renderActions()}\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 </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",
|
|
427
|
+
"content": "import { Component, Event, EventEmitter, h, Host, Prop, State, Watch } from '@stencil/core';\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 /** 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 Back breadcrumb. */\n @Prop() backLabel: string = 'Back';\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 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 @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 /** 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 >\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={!this.compact && !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 renderBack(compact: boolean) {\n if (!this.showBack) return null;\n\n if (!compact) {\n return (\n <div class=\"bar-title__breadcrumb\">\n <ds-button-unfilled\n class=\"bar-title__breadcrumb-back\"\n variant=\"icon-label\"\n icon=\"ChevronLeft\"\n label={this.backLabel}\n aria-label={this.backAriaLabel}\n size=\"xs\"\n hasBorder={false}\n onDsClick={(event: CustomEvent<MouseEvent>) => this.dsBack.emit(event.detail)}\n />\n </div>\n );\n }\n\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 }}\n >\n <div class=\"bar-title\">\n <div class=\"bar-title__inner\">\n <div class=\"bar-title__row\">\n <div class=\"bar-title__leading\">\n {!compact ? this.renderBack(false) : null}\n <div class=\"bar-title__title-row\">\n <div class=\"bar-title__identity\">\n {compact ? this.renderBack(true) : 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",
|
|
423
428
|
"type": "registry:ui"
|
|
424
429
|
},
|
|
425
430
|
{
|