@ds-mo/ui 12.7.0 → 12.9.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/.build-stamp +1 -1
- package/dist/agent-patterns.json +22 -9
- package/dist/agent.json +246 -19
- package/dist/angular/ds-button-filled.d.ts +1 -1
- package/dist/angular/ds-button-filled.js +3 -3
- package/dist/angular/ds-button-unfilled.d.ts +1 -1
- package/dist/angular/ds-button-unfilled.js +3 -3
- package/dist/angular/ds-card-overview.d.ts +19 -0
- package/dist/angular/ds-card-overview.js +42 -0
- 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-bar-nav.js +1 -1
- package/dist/components/ds-bar-title.js +1 -1
- package/dist/components/ds-bar-title.js.map +1 -1
- package/dist/components/ds-bar-workflow.js +1 -1
- package/dist/components/ds-breadcrumb.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-bar.js +1 -1
- package/dist/components/ds-card-data-viz-bar.js.map +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-data-viz-line.js +1 -1
- package/dist/components/ds-card-data-viz-line.js.map +1 -1
- package/dist/components/ds-card-overview.d.ts +11 -0
- package/dist/components/ds-card-overview.js +2 -0
- package/dist/components/ds-card-overview.js.map +1 -0
- package/dist/components/ds-card-setting.js +1 -1
- package/dist/components/ds-card-shell-data-viz.js +1 -1
- package/dist/components/ds-chart-bar-stacked.js +1 -1
- package/dist/components/ds-chart-bar-stacked.js.map +1 -1
- package/dist/components/ds-chart-bar.js +1 -1
- package/dist/components/ds-chart-bar.js.map +1 -1
- package/dist/components/ds-chart-donut.js +1 -1
- package/dist/components/ds-chart-donut.js.map +1 -1
- package/dist/components/ds-chart-legend.js +1 -1
- package/dist/components/ds-chart-line.js +1 -1
- package/dist/components/ds-chart-line.js.map +1 -1
- package/dist/components/ds-checkbox.js +1 -1
- package/dist/components/ds-chip.js +1 -1
- package/dist/components/ds-code-block.js +1 -1
- package/dist/components/ds-conversation-list-item.js +1 -1
- package/dist/components/ds-conversation-list-item.js.map +1 -1
- package/dist/components/ds-conversation-list-section.js +1 -1
- package/dist/components/ds-conversation-list.js +1 -1
- package/dist/components/ds-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-input.js +1 -1
- package/dist/components/ds-markdown.js +1 -1
- package/dist/components/ds-menu.js +1 -1
- package/dist/components/ds-message-bubble.js +1 -1
- package/dist/components/ds-message-composer.js +1 -1
- package/dist/components/ds-message-scroller.js +1 -1
- package/dist/components/ds-message-scroller.js.map +1 -1
- package/dist/components/ds-message.js +1 -1
- package/dist/components/ds-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-nav.js.map +1 -1
- package/dist/components/ds-panel-sub-nav.js +1 -1
- package/dist/components/ds-panel-tool-header.js +1 -1
- package/dist/components/ds-panel-tool-search.js +1 -1
- package/dist/components/ds-panel-tools.js +1 -1
- package/dist/components/ds-radio.js +1 -1
- package/dist/components/ds-scroll-overlay.js +1 -1
- package/dist/components/ds-scroll-overlay.js.map +1 -1
- package/dist/components/ds-select-multi.js +1 -1
- package/dist/components/ds-select-multi.js.map +1 -1
- package/dist/components/ds-select.js +1 -1
- package/dist/components/ds-select.js.map +1 -1
- package/dist/components/ds-shell-app.js +1 -1
- package/dist/components/ds-shell-gradient-picker.js +1 -1
- package/dist/components/ds-shell-gradient-picker.js.map +1 -1
- package/dist/components/ds-shell-gradient-swatch.js +1 -1
- package/dist/components/ds-shell-page.js +1 -1
- package/dist/components/ds-shell-page.js.map +1 -1
- package/dist/components/ds-shell-tools.js +1 -1
- package/dist/components/ds-skeleton.js +1 -1
- package/dist/components/ds-skeleton.js.map +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-tag.js +1 -1
- package/dist/components/ds-text.js +1 -1
- package/dist/components/ds-toast.js +1 -1
- package/dist/components/ds-toast.js.map +1 -1
- package/dist/components/ds-tooltip-data-viz.js +1 -1
- package/dist/components/ds-tooltip.js +1 -1
- package/dist/components/ds-typing-indicator.js +1 -1
- package/dist/components/p-2foAUsDN.js +2 -0
- package/dist/components/p-2foAUsDN.js.map +1 -0
- package/dist/components/{p-BuiZcSga.js → p-8GK7B7K4.js} +2 -2
- package/dist/components/p-8GK7B7K4.js.map +1 -0
- package/dist/components/p-B5tMlca-.js +2 -0
- package/dist/components/p-B5tMlca-.js.map +1 -0
- package/dist/components/p-B6Vuayt6.js +2 -0
- package/dist/components/p-B6Vuayt6.js.map +1 -0
- package/dist/components/{p-B-9A0e_0.js → p-B88n0pO3.js} +2 -2
- package/dist/components/p-BBePKRHC.js +2 -0
- package/dist/components/p-BBePKRHC.js.map +1 -0
- package/dist/components/p-BBxcMVqk.js +2 -0
- package/dist/components/p-BBxcMVqk.js.map +1 -0
- package/dist/components/p-BDKtu6Wb.js +2 -0
- package/dist/components/{p-Bl001G0Y.js.map → p-BDKtu6Wb.js.map} +1 -1
- package/dist/components/p-BKOlvCGS.js +2 -0
- package/dist/components/p-BKOlvCGS.js.map +1 -0
- package/dist/components/{p-C7bZ-jiU.js → p-BO5aqZcf.js} +2 -2
- package/dist/components/{p-guQ7hv6W.js → p-BOCfICln.js} +2 -2
- package/dist/components/p-BX0BMhgz.js +2 -0
- package/dist/components/p-BX0BMhgz.js.map +1 -0
- package/dist/components/p-BhV5e6vF.js +2 -0
- package/dist/components/{p-1darPzys.js.map → p-BhV5e6vF.js.map} +1 -1
- package/dist/components/p-C8HU4W8q.js +2 -0
- package/dist/components/{p-pgxlkvUy.js.map → p-C8HU4W8q.js.map} +1 -1
- package/dist/components/{p-CvyTZ4Mg.js → p-CChcuL04.js} +2 -2
- package/dist/components/{p-Crsn82rz.js → p-CCzG5xDD.js} +2 -2
- package/dist/components/p-CGVXbXv9.js +2 -0
- package/dist/components/p-CGVXbXv9.js.map +1 -0
- package/dist/components/{p-BT5hgg5i.js → p-CIiBpjCn.js} +2 -2
- package/dist/components/{p-B_5nxOeB.js → p-CgEYdMee.js} +2 -2
- package/dist/components/{p-k0Hmj1KV.js → p-Cr3tEx7o.js} +2 -2
- package/dist/components/p-Cr3tEx7o.js.map +1 -0
- package/dist/components/{p-5-QAFB4j.js → p-Cuih680v.js} +2 -2
- package/dist/components/{p-C_9ZH0A3.js → p-Cuqxh-Tq.js} +2 -2
- package/dist/components/{p--eKXTylf.js → p-DCvA3Dc9.js} +2 -2
- package/dist/components/{p-DNFd4Ao-.js → p-DLZm_-sU.js} +2 -2
- package/dist/components/{p-C9h13YnX.js → p-DRnIuCiT.js} +2 -2
- package/dist/components/p-DhKvqh5G.js +2 -0
- package/dist/components/p-DhKvqh5G.js.map +1 -0
- package/dist/components/p-DjKkSqrG.js +2 -0
- package/dist/components/p-DjKkSqrG.js.map +1 -0
- package/dist/components/{p-CUAYsK44.js → p-DlscFn2m.js} +2 -2
- package/dist/components/{p-BcfC6phQ.js → p-DxkEC0hR.js} +2 -2
- package/dist/components/p-DxkEC0hR.js.map +1 -0
- package/dist/components/{p-C6xC48hG.js → p-Re2kPrXm.js} +2 -2
- package/dist/components/{p-DIa73GJP.js → p-XMZCaXs7.js} +2 -2
- package/dist/components/p-YR1XeuZf.js +2 -0
- package/dist/components/p-YR1XeuZf.js.map +1 -0
- package/dist/components/{p-CqWV4Hl_.js → p-dyUp3Adm.js} +2 -2
- package/dist/components/{p-CnAkobDW.js → p-nkYN0Bub.js} +2 -2
- package/dist/components/p-yhj1lIPx.js +2 -0
- package/dist/docs/components.json +847 -167
- package/dist/lib/components/Menu/menu-position.d.ts +6 -6
- package/dist/lib/toast/toast-types.d.ts +3 -3
- package/dist/lib/utils/anchored-position.d.ts +34 -0
- package/dist/lib/utils/choice-popup-alignment.d.ts +2 -2
- package/dist/lib/utils/index.d.ts +2 -0
- package/dist/lib/utils/index.js +31 -0
- package/dist/lib/utils/index.js.map +3 -3
- package/dist/lib/utils/metric-change.d.ts +52 -0
- package/dist/lib/utils/token-defaults.d.ts +3 -0
- 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/bar-nav.json +1 -1
- package/dist/mcp-data/registry/bar-title.json +2 -2
- 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 +122 -6
- package/dist/mcp-data/registry/button-unfilled.json +49 -5
- package/dist/mcp-data/registry/card-data-viz-bar.json +2 -2
- package/dist/mcp-data/registry/card-data-viz-donut.json +2 -1
- package/dist/mcp-data/registry/card-data-viz-line.json +2 -2
- package/dist/mcp-data/registry/card-overview.json +360 -0
- 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-stacked.json +14 -9
- package/dist/mcp-data/registry/chart-bar.json +15 -10
- package/dist/mcp-data/registry/chart-donut.json +3 -2
- package/dist/mcp-data/registry/chart-legend.json +1 -1
- package/dist/mcp-data/registry/chart-line.json +15 -10
- 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 +1 -1
- 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 +3 -3
- 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 +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 +1 -1
- package/dist/mcp-data/registry/panel-tools.json +3 -2
- package/dist/mcp-data/registry/radio.json +1 -1
- package/dist/mcp-data/registry/registry.json +249 -19
- package/dist/mcp-data/registry/scroll-overlay.json +1 -1
- package/dist/mcp-data/registry/select-multi.json +2 -2
- package/dist/mcp-data/registry/select.json +2 -2
- 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 +1 -1
- package/dist/mcp-data/registry/shell-page.json +5 -4
- 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 +3 -3
- package/dist/mcp-data/registry/toast.json +2 -2
- package/dist/mcp-data/registry/tooltip-data-viz.json +3 -1
- package/dist/mcp-data/registry/tooltip.json +3 -8
- 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-card-overview.d.ts +12 -0
- package/dist/react/ds-card-overview.js +12 -0
- package/dist/styles/control-elevation.css +11 -0
- package/dist/types/components/ButtonFilled/ButtonFilled.d.ts +32 -0
- package/dist/types/components/ButtonUnfilled/ButtonUnfilled.d.ts +20 -0
- package/dist/types/components/CardOverview/CardOverview.d.ts +64 -0
- package/dist/types/components/CardOverview/card-overview-types.d.ts +34 -0
- package/dist/types/components/CardOverview/index.d.ts +2 -0
- package/dist/types/components/ChartBar/ChartBar.d.ts +11 -0
- package/dist/types/components/ChartBarStacked/ChartBarStacked.d.ts +11 -0
- package/dist/types/components/ChartLine/ChartLine.d.ts +11 -0
- package/dist/types/components/Menu/Menu.d.ts +7 -5
- package/dist/types/components/Menu/menu-position.d.ts +6 -6
- package/dist/types/components/ShellPage/ShellPage.d.ts +1 -0
- package/dist/types/components/Tooltip/Tooltip.d.ts +1 -4
- package/dist/types/components.d.ts +234 -2
- package/dist/types/toast/toast-types.d.ts +3 -3
- package/dist/types/utils/anchored-position-controller.d.ts +105 -0
- package/dist/types/utils/anchored-position.d.ts +35 -0
- package/dist/types/utils/cartesian-chart-layout.d.ts +13 -0
- package/dist/types/utils/chart-viewport-controller.d.ts +21 -0
- package/dist/types/utils/choice-popup-alignment.d.ts +2 -2
- package/dist/types/utils/index.d.ts +2 -0
- package/dist/types/utils/metric-change.d.ts +53 -0
- package/dist/types/utils/select-controller.d.ts +3 -3
- package/dist/types/utils/token-defaults.d.ts +3 -0
- package/package.json +3 -3
- package/dist/components/p-1darPzys.js +0 -2
- package/dist/components/p-B8C_yfLQ.js +0 -2
- package/dist/components/p-B8C_yfLQ.js.map +0 -1
- package/dist/components/p-BcfC6phQ.js.map +0 -1
- package/dist/components/p-Bl001G0Y.js +0 -2
- package/dist/components/p-BuiZcSga.js.map +0 -1
- package/dist/components/p-Bv9frkvW.js +0 -2
- package/dist/components/p-Bv9frkvW.js.map +0 -1
- package/dist/components/p-C0-K8vnW.js +0 -2
- package/dist/components/p-C0-K8vnW.js.map +0 -1
- package/dist/components/p-C2ym_Ich.js +0 -2
- package/dist/components/p-C_ewSXTT.js +0 -2
- package/dist/components/p-C_ewSXTT.js.map +0 -1
- package/dist/components/p-Ca9-fnFl.js +0 -2
- package/dist/components/p-Ca9-fnFl.js.map +0 -1
- package/dist/components/p-CydY-v7X.js +0 -2
- package/dist/components/p-CydY-v7X.js.map +0 -1
- package/dist/components/p-De7NquTI.js +0 -2
- package/dist/components/p-De7NquTI.js.map +0 -1
- package/dist/components/p-Dg3xLna0.js +0 -2
- package/dist/components/p-Dg3xLna0.js.map +0 -1
- package/dist/components/p-k0Hmj1KV.js.map +0 -1
- package/dist/components/p-pgxlkvUy.js +0 -2
- package/dist/lib/utils/anchored-popup.d.ts +0 -23
- package/dist/types/components/Tooltip/tooltip-position.d.ts +0 -21
- package/dist/types/utils/anchored-popup.d.ts +0 -24
- /package/dist/components/{p-B-9A0e_0.js.map → p-B88n0pO3.js.map} +0 -0
- /package/dist/components/{p-C7bZ-jiU.js.map → p-BO5aqZcf.js.map} +0 -0
- /package/dist/components/{p-guQ7hv6W.js.map → p-BOCfICln.js.map} +0 -0
- /package/dist/components/{p-CvyTZ4Mg.js.map → p-CChcuL04.js.map} +0 -0
- /package/dist/components/{p-Crsn82rz.js.map → p-CCzG5xDD.js.map} +0 -0
- /package/dist/components/{p-BT5hgg5i.js.map → p-CIiBpjCn.js.map} +0 -0
- /package/dist/components/{p-B_5nxOeB.js.map → p-CgEYdMee.js.map} +0 -0
- /package/dist/components/{p-5-QAFB4j.js.map → p-Cuih680v.js.map} +0 -0
- /package/dist/components/{p-C_9ZH0A3.js.map → p-Cuqxh-Tq.js.map} +0 -0
- /package/dist/components/{p--eKXTylf.js.map → p-DCvA3Dc9.js.map} +0 -0
- /package/dist/components/{p-DNFd4Ao-.js.map → p-DLZm_-sU.js.map} +0 -0
- /package/dist/components/{p-C9h13YnX.js.map → p-DRnIuCiT.js.map} +0 -0
- /package/dist/components/{p-CUAYsK44.js.map → p-DlscFn2m.js.map} +0 -0
- /package/dist/components/{p-C6xC48hG.js.map → p-Re2kPrXm.js.map} +0 -0
- /package/dist/components/{p-DIa73GJP.js.map → p-XMZCaXs7.js.map} +0 -0
- /package/dist/components/{p-CqWV4Hl_.js.map → p-dyUp3Adm.js.map} +0 -0
- /package/dist/components/{p-CnAkobDW.js.map → p-nkYN0Bub.js.map} +0 -0
- /package/dist/components/{p-C2ym_Ich.js.map → p-yhj1lIPx.js.map} +0 -0
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"themeSetup": "Set data-theme=\"dark\" on <html> for dark mode. Light is default.",
|
|
13
13
|
"peerDependencies": {
|
|
14
14
|
"required": [
|
|
15
|
-
"@ds-mo/tokens ^
|
|
15
|
+
"@ds-mo/tokens ^5.0.0",
|
|
16
16
|
"@ds-mo/icons ^6.4.0"
|
|
17
17
|
],
|
|
18
18
|
"optional": [],
|
|
@@ -1326,7 +1326,8 @@
|
|
|
1326
1326
|
"The action is unfilled icon-only shell or navigation chrome.",
|
|
1327
1327
|
"The element represents navigation rather than a command.",
|
|
1328
1328
|
"The content is a persistent selected state rather than an action.",
|
|
1329
|
-
"A semantic color would be decorative or merely match the surrounding product area."
|
|
1329
|
+
"A semantic color would be decorative or merely match the surrounding product area.",
|
|
1330
|
+
"The control is an overflow or more-options menu whose only content is an Ellipses glyph; that role is never filled."
|
|
1330
1331
|
],
|
|
1331
1332
|
"alternatives": [
|
|
1332
1333
|
{
|
|
@@ -1338,14 +1339,25 @@
|
|
|
1338
1339
|
"when": "The user is choosing a destination in application navigation.",
|
|
1339
1340
|
"component": "component:ds-panel-nav",
|
|
1340
1341
|
"reason": "Navigation components own destination selection, active state, and navigation semantics."
|
|
1342
|
+
},
|
|
1343
|
+
{
|
|
1344
|
+
"when": "The control is the overflow or more-options menu for a row, card, or toolbar.",
|
|
1345
|
+
"component": "component:ds-button-unfilled",
|
|
1346
|
+
"reason": "An icon-only Ellipses trigger is chrome, and its glyph conveys the menu without a chevron; a filled button never carries that role."
|
|
1341
1347
|
}
|
|
1342
1348
|
],
|
|
1343
1349
|
"commonlyComposedWith": [
|
|
1344
1350
|
"component:ds-modal",
|
|
1345
|
-
"component:ds-field"
|
|
1351
|
+
"component:ds-field",
|
|
1352
|
+
"component:ds-menu"
|
|
1353
|
+
],
|
|
1354
|
+
"patterns": [
|
|
1355
|
+
"pattern:menu-trigger"
|
|
1346
1356
|
],
|
|
1347
1357
|
"accessibility": [
|
|
1348
1358
|
"Provide an explicit accessible name for icon-only buttons.",
|
|
1359
|
+
"Set hasMenu when the action opens a Menu; it implies aria-haspopup of menu and renders the trailing chevron. Pass controls with the Menu id and expanded from the same open state given to Menu.",
|
|
1360
|
+
"Use haspopup directly for a non-menu popup such as a dialog; that case gets no chevron.",
|
|
1349
1361
|
"Use inactive state only when the action must remain visible but unavailable.",
|
|
1350
1362
|
"Do not encode the action meaning through color alone.",
|
|
1351
1363
|
"Loading keeps the action focused and named, exposes busy and disabled state, and blocks activation; announce broader operation progress from the owning workflow when users need confirmation.",
|
|
@@ -1360,7 +1372,11 @@
|
|
|
1360
1372
|
"Parent surface context changes only the optional inset border color. Contrast continues to own the button fill, foreground, hover, pressed, and focus treatments; never use surface context to change action hierarchy.",
|
|
1361
1373
|
"Rounded changes only the control radius to the half-radius treatment; it does not change hierarchy, size, variant, intent, or interaction semantics.",
|
|
1362
1374
|
"Loading prevents duplicate activation without applying inactive opacity or removing the current keyboard focus.",
|
|
1363
|
-
"Icon and icon-label loading replace the icon in place. Label-only loading centers the loader while preserving the button's measured width."
|
|
1375
|
+
"Icon and icon-label loading replace the icon in place. Label-only loading centers the loader while preserving the button's measured width.",
|
|
1376
|
+
"Physical press scaling is on by default. Disable it only when an owning composite must keep child or background geometry fixed; hover and pressed interaction washes remain available.",
|
|
1377
|
+
"ButtonFilled has no active or selected state; a filled action is a command, not a toggle or a persistent selection.",
|
|
1378
|
+
"An open popup is a transient pressed state instead. While expanded is true the pressed wash is held for the popup's complete rendered lifecycle and survives hover, so the trigger reads as held open without introducing a selected treatment.",
|
|
1379
|
+
"hasMenu supports only the label and icon-label variants. The chevron is what communicates that a menu will open, so an icon-only filled menu button would leave the affordance unstated."
|
|
1364
1380
|
],
|
|
1365
1381
|
"responsiveBehavior": [
|
|
1366
1382
|
"Use fill width only when the parent layout calls for a full-width action, commonly on narrow form layouts."
|
|
@@ -1396,10 +1412,26 @@
|
|
|
1396
1412
|
"type": "ButtonFilledContrast",
|
|
1397
1413
|
"required": false
|
|
1398
1414
|
},
|
|
1415
|
+
"controls": {
|
|
1416
|
+
"type": "string | undefined",
|
|
1417
|
+
"required": false
|
|
1418
|
+
},
|
|
1419
|
+
"expanded": {
|
|
1420
|
+
"type": "boolean | undefined",
|
|
1421
|
+
"required": false
|
|
1422
|
+
},
|
|
1399
1423
|
"hasBorder": {
|
|
1400
1424
|
"type": "boolean",
|
|
1401
1425
|
"required": false
|
|
1402
1426
|
},
|
|
1427
|
+
"hasMenu": {
|
|
1428
|
+
"type": "boolean",
|
|
1429
|
+
"required": false
|
|
1430
|
+
},
|
|
1431
|
+
"haspopup": {
|
|
1432
|
+
"type": "ButtonFilledPopup | undefined",
|
|
1433
|
+
"required": false
|
|
1434
|
+
},
|
|
1403
1435
|
"icon": {
|
|
1404
1436
|
"type": "string",
|
|
1405
1437
|
"required": false
|
|
@@ -1420,6 +1452,14 @@
|
|
|
1420
1452
|
"type": "string",
|
|
1421
1453
|
"required": false
|
|
1422
1454
|
},
|
|
1455
|
+
"pressScale": {
|
|
1456
|
+
"type": "boolean",
|
|
1457
|
+
"required": false
|
|
1458
|
+
},
|
|
1459
|
+
"pressed": {
|
|
1460
|
+
"type": "boolean | undefined",
|
|
1461
|
+
"required": false
|
|
1462
|
+
},
|
|
1423
1463
|
"rounded": {
|
|
1424
1464
|
"type": "boolean",
|
|
1425
1465
|
"required": false
|
|
@@ -1483,7 +1523,8 @@
|
|
|
1483
1523
|
"useWhen": [
|
|
1484
1524
|
"A secondary command accompanies the single filled primary action in a local decision area.",
|
|
1485
1525
|
"A toolbar, tool rail, overflow area, or shell region needs a compact icon action.",
|
|
1486
|
-
"A command represents a persistent toggle or mode whose active state remains meaningful after activation."
|
|
1526
|
+
"A command represents a persistent toggle or mode whose active state remains meaningful after activation.",
|
|
1527
|
+
"A row, card, or toolbar needs an overflow or more-options menu; this is the only component that carries that role."
|
|
1487
1528
|
],
|
|
1488
1529
|
"avoidWhen": [
|
|
1489
1530
|
"The command is the single primary action in a local decision area; use ButtonFilled instead.",
|
|
@@ -1518,6 +1559,9 @@
|
|
|
1518
1559
|
],
|
|
1519
1560
|
"accessibility": [
|
|
1520
1561
|
"Provide an explicit accessible name for every icon-only action.",
|
|
1562
|
+
"Set hasMenu when the button triggers a Menu; it implies aria-haspopup of menu. Pass controls with the Menu id and expanded from the same open state given to Menu.",
|
|
1563
|
+
"Give an icon-only menu button an accessible name that states the role, such as More options, because no visible label or chevron carries it.",
|
|
1564
|
+
"Use haspopup directly for a non-menu popup such as a listbox or dialog; those cases get no chevron.",
|
|
1521
1565
|
"Use active state only for a persistent toggle or mode, and synchronize pressed semantics unless an owning composite exposes the selection state.",
|
|
1522
1566
|
"Treat notification dots as supplemental unread or status information; never use a dot as the sole state, meaning, or accessible name.",
|
|
1523
1567
|
"Loading keeps the action focused and named, exposes busy and disabled state, and blocks activation; announce broader operation progress from the owning workflow when needed.",
|
|
@@ -1532,7 +1576,11 @@
|
|
|
1532
1576
|
"Surface context must match the actual parent surface; translucent, inverted, media, and always-dark treatments are reserved for those surfaces.",
|
|
1533
1577
|
"Loading prevents duplicate activation without applying inactive opacity or removing the current keyboard focus.",
|
|
1534
1578
|
"Rounded changes only the control radius to the half-radius treatment; it does not change hierarchy, size, variant, or interaction semantics.",
|
|
1535
|
-
"
|
|
1579
|
+
"Physical press scaling is on by default. Disable it only when an owning composite must keep child or background geometry fixed; hover and pressed interaction washes remain available.",
|
|
1580
|
+
"Icon and icon-label loading replace the icon in place. Label-only loading centers the loader while preserving the button's measured width.",
|
|
1581
|
+
"hasMenu covers two distinct menu-button shapes and the variant selects between them. With label or icon-label the action has a menu, and a trailing chevron carries the affordance. With icon the button is a menu, meaning the overflow or more-options control, and no chevron is added.",
|
|
1582
|
+
"Because the icon-only shape has no chevron, its glyph must convey the menu on its own; use Ellipses. Never pair hasMenu with an icon-only glyph that does not read as a menu.",
|
|
1583
|
+
"An open popup is a transient pressed state rather than a selected one. While expanded is true the pressed wash is held for the popup's complete rendered lifecycle and survives hover, including when chrome opts out of selected fills with activeFill set to false."
|
|
1536
1584
|
],
|
|
1537
1585
|
"responsiveBehavior": [
|
|
1538
1586
|
"The consuming layout decides when an icon-label action becomes icon-only and must provide an explicit accessible name after changing variants.",
|
|
@@ -1590,6 +1638,10 @@
|
|
|
1590
1638
|
"type": "boolean",
|
|
1591
1639
|
"required": false
|
|
1592
1640
|
},
|
|
1641
|
+
"hasMenu": {
|
|
1642
|
+
"type": "boolean",
|
|
1643
|
+
"required": false
|
|
1644
|
+
},
|
|
1593
1645
|
"haspopup": {
|
|
1594
1646
|
"type": "ButtonUnfilledPopup | undefined",
|
|
1595
1647
|
"required": false
|
|
@@ -1614,6 +1666,10 @@
|
|
|
1614
1666
|
"type": "string",
|
|
1615
1667
|
"required": false
|
|
1616
1668
|
},
|
|
1669
|
+
"pressScale": {
|
|
1670
|
+
"type": "boolean",
|
|
1671
|
+
"required": false
|
|
1672
|
+
},
|
|
1617
1673
|
"pressed": {
|
|
1618
1674
|
"type": "boolean | undefined",
|
|
1619
1675
|
"required": false
|
|
@@ -1728,7 +1784,7 @@
|
|
|
1728
1784
|
"The owning product controls data, filtering, loading, empty, and error state; the card emits the filter action only."
|
|
1729
1785
|
],
|
|
1730
1786
|
"responsiveBehavior": [
|
|
1731
|
-
"The selected sm, md, or lg shell size controls the card dimensions while the bar chart
|
|
1787
|
+
"The selected sm, md, or lg shell size controls the card dimensions while the bar chart reflows to the chart region without visually scaling its primitives.",
|
|
1732
1788
|
"The horizontal legend wraps complete entries when the available width is constrained.",
|
|
1733
1789
|
"The parent dashboard owns card wrapping and breakpoint changes."
|
|
1734
1790
|
],
|
|
@@ -1844,6 +1900,7 @@
|
|
|
1844
1900
|
],
|
|
1845
1901
|
"responsiveBehavior": [
|
|
1846
1902
|
"Select the token-based card width appropriate to the parent grid; the matching minimum height preserves the chart region.",
|
|
1903
|
+
"The donut measures the chart region and changes radius within its shared size band while token thickness and typography remain fixed.",
|
|
1847
1904
|
"The parent dashboard owns wrapping and column changes while the internal chart-over-legend arrangement remains stable."
|
|
1848
1905
|
],
|
|
1849
1906
|
"frameworkCaveats": {
|
|
@@ -1958,7 +2015,7 @@
|
|
|
1958
2015
|
"The owning product controls data, filtering, loading, empty, and error state; the card emits the filter action only."
|
|
1959
2016
|
],
|
|
1960
2017
|
"responsiveBehavior": [
|
|
1961
|
-
"The selected sm, md, or lg shell size controls the card dimensions while the line chart
|
|
2018
|
+
"The selected sm, md, or lg shell size controls the card dimensions while the line chart reflows to the chart region without visually scaling its primitives.",
|
|
1962
2019
|
"The horizontal legend wraps complete entries when the available width is constrained.",
|
|
1963
2020
|
"The parent dashboard owns card wrapping and breakpoint changes."
|
|
1964
2021
|
],
|
|
@@ -2016,6 +2073,171 @@
|
|
|
2016
2073
|
"card-shell-data-viz"
|
|
2017
2074
|
]
|
|
2018
2075
|
},
|
|
2076
|
+
{
|
|
2077
|
+
"name": "card-overview",
|
|
2078
|
+
"title": "CardOverview",
|
|
2079
|
+
"description": "Page-level summary card pairing one headline score with a reporting period, an application-owned period control, and a responsive grid of comparable measures, with an optional compact 48px presentation.",
|
|
2080
|
+
"type": "registry:ui",
|
|
2081
|
+
"meta": {
|
|
2082
|
+
"library": "@ds-mo/ui",
|
|
2083
|
+
"distribution": "npm",
|
|
2084
|
+
"storybook": "https://zainadeel.github.io/compomo/?path=/story/card-overview",
|
|
2085
|
+
"source": "src/wc/components/CardOverview/CardOverview.tsx",
|
|
2086
|
+
"intentStatus": "complete",
|
|
2087
|
+
"intent": {
|
|
2088
|
+
"audience": "general",
|
|
2089
|
+
"status": "experimental",
|
|
2090
|
+
"summary": "Page-level summary card pairing one headline score with a reporting period, an application-owned period control, and a responsive grid of comparable measures, with an optional compact 48px presentation.",
|
|
2091
|
+
"useWhen": [
|
|
2092
|
+
"A page opens with one headline figure that the surrounding measures explain.",
|
|
2093
|
+
"Every measure is compared against the same period, so one caption describes the comparison for all of them.",
|
|
2094
|
+
"The measures are read together at a glance rather than explored individually."
|
|
2095
|
+
],
|
|
2096
|
+
"avoidWhen": [
|
|
2097
|
+
"There is no headline figure and the measures are unrelated; use the matching card surface for each one.",
|
|
2098
|
+
"A measure needs a chart, sparkline, or other visualization; the grid presents values and change only.",
|
|
2099
|
+
"The measures each require their own comparison period or their own filter control.",
|
|
2100
|
+
"The content is a settings section or a general read-only card."
|
|
2101
|
+
],
|
|
2102
|
+
"alternatives": [
|
|
2103
|
+
{
|
|
2104
|
+
"when": "A measure needs a chart or other data visualization.",
|
|
2105
|
+
"component": "component:ds-card-shell-data-viz",
|
|
2106
|
+
"reason": "CardShellDataViz owns the data-visualization shell contract; the overview grid presents values and change only."
|
|
2107
|
+
},
|
|
2108
|
+
{
|
|
2109
|
+
"when": "The surface is a settings section with an edit workflow.",
|
|
2110
|
+
"component": "component:ds-card-setting",
|
|
2111
|
+
"reason": "CardSetting owns the controlled view and edit chrome."
|
|
2112
|
+
}
|
|
2113
|
+
],
|
|
2114
|
+
"commonlyComposedWith": [
|
|
2115
|
+
"component:ds-select",
|
|
2116
|
+
"component:ds-button-unfilled",
|
|
2117
|
+
"component:ds-text",
|
|
2118
|
+
"component:ds-tooltip"
|
|
2119
|
+
],
|
|
2120
|
+
"accessibility": [
|
|
2121
|
+
"The bar is a labelled region; set overviewLabel to name what is being summarised, such as Safety summary.",
|
|
2122
|
+
"Trend arrows are decorative and hidden from assistive technology, so the formatted change value must read correctly on its own.",
|
|
2123
|
+
"Colour never carries the trend meaning alone; the arrow direction and the value are both present.",
|
|
2124
|
+
"Measures become selectable only when they are not inactive. Selectable measures form one roving tab stop, so the grid is a single stop and arrow keys move between measures.",
|
|
2125
|
+
"Set labelTooltip when a measure's label needs qualifying rather than shortening the label into something ambiguous.",
|
|
2126
|
+
"While loading, the region exposes a busy state and renders skeletons whose type metrics match the resolved content."
|
|
2127
|
+
],
|
|
2128
|
+
"states": [
|
|
2129
|
+
"Score and measures are supplied as data; the component owns their type treatment so summary bars stay consistent across pages.",
|
|
2130
|
+
"Trend is supplied already resolved as a direction, a formatted value, and a tone. Whether a rise reads well is a product decision, so the component never infers tone from the numbers. Derive it with resolveMetricTrend.",
|
|
2131
|
+
"Omit a trend when there is nothing to report; resolveMetricTrend returns null in that case so no zero change is rendered.",
|
|
2132
|
+
"Omit score to render the bar without a headline figure. Set scoreErrorMessage to report that the figure could not be resolved while the measures remain readable.",
|
|
2133
|
+
"Loading replaces the score and the measures with skeletons and does not alter the slotted period control.",
|
|
2134
|
+
"At most six measures are shown. Keep larger collections outside the summary bar rather than turning it into a dense dashboard.",
|
|
2135
|
+
"The period control is slotted rather than owned, because the choice of stepper, select, or date range varies by product surface.",
|
|
2136
|
+
"compact is exactly 48px high and keeps only the score label, score value, trend, reporting copy, and slotted period control. It omits the score band, measures, and footer."
|
|
2137
|
+
],
|
|
2138
|
+
"responsiveBehavior": [
|
|
2139
|
+
"The measure grid reflows to fewer columns and finally stacks, driven by the minimum measure width alone. There is no measurement and no horizontal scrolling.",
|
|
2140
|
+
"Every occupied grid track has the same width. When a final row is incomplete, its measures keep the same width as the row above and the unused tracks remain empty.",
|
|
2141
|
+
"Set metricMinWidth to change the width a measure may shrink to before the grid drops a column.",
|
|
2142
|
+
"The score block hugs its content and never shrinks, so the headline figure holds its size while the grid absorbs the remaining width.",
|
|
2143
|
+
"Once the card reaches its narrow stacked state, one summary block holds the score label, score, trend, score band, reporting period, and the application-owned period control above the single-column measures.",
|
|
2144
|
+
"In that stacked summary, the score and trend share one line at the start, while the score band is hidden to preserve space. The period control stays at the upper end, while the current period and comparison sit on separate right-aligned lines beside the score.",
|
|
2145
|
+
"Every measure occupies a full-width row in the stacked state.",
|
|
2146
|
+
"Set layout to stacked when the owning page shell enters a responsive mode before the card's own container reaches its narrow threshold.",
|
|
2147
|
+
"CardOverview does not observe scroll or choose its own presentation. The page owner may use compact as sticky summary chrome on supported breakpoints."
|
|
2148
|
+
],
|
|
2149
|
+
"frameworkCaveats": {
|
|
2150
|
+
"customElements": [
|
|
2151
|
+
"Assign score and metrics as JavaScript properties; both are objects and cannot be expressed as HTML attributes.",
|
|
2152
|
+
"Place the period control in the filter slot; the component reserves space for it but never renders one."
|
|
2153
|
+
],
|
|
2154
|
+
"react": [
|
|
2155
|
+
"Pass score and metrics as props and read the selected measure from the onDsMetricSelect event detail.",
|
|
2156
|
+
"Derive each trend with resolveMetricTrend so tone stays an explicit product decision."
|
|
2157
|
+
],
|
|
2158
|
+
"angular": [
|
|
2159
|
+
"Bind [score] and [metrics] as properties and read the selected measure from $event.detail on (dsMetricSelect).",
|
|
2160
|
+
"Derive each trend with resolveMetricTrend so tone stays an explicit product decision."
|
|
2161
|
+
]
|
|
2162
|
+
},
|
|
2163
|
+
"references": [
|
|
2164
|
+
{
|
|
2165
|
+
"label": "Storybook structural examples",
|
|
2166
|
+
"path": "src/wc/components/CardOverview/CardOverview.stories.ts"
|
|
2167
|
+
},
|
|
2168
|
+
{
|
|
2169
|
+
"label": "Trend resolution unit coverage",
|
|
2170
|
+
"path": "tests/metric-change.test.ts"
|
|
2171
|
+
}
|
|
2172
|
+
]
|
|
2173
|
+
},
|
|
2174
|
+
"props": {
|
|
2175
|
+
"comparisonLabel": {
|
|
2176
|
+
"type": "string",
|
|
2177
|
+
"required": false
|
|
2178
|
+
},
|
|
2179
|
+
"isLoading": {
|
|
2180
|
+
"type": "boolean",
|
|
2181
|
+
"required": false
|
|
2182
|
+
},
|
|
2183
|
+
"layout": {
|
|
2184
|
+
"type": "CardOverviewLayout",
|
|
2185
|
+
"required": false
|
|
2186
|
+
},
|
|
2187
|
+
"metricMinWidth": {
|
|
2188
|
+
"type": "string",
|
|
2189
|
+
"required": false
|
|
2190
|
+
},
|
|
2191
|
+
"metrics": {
|
|
2192
|
+
"type": "OverviewMetric[]",
|
|
2193
|
+
"required": false
|
|
2194
|
+
},
|
|
2195
|
+
"overviewLabel": {
|
|
2196
|
+
"type": "string",
|
|
2197
|
+
"required": false
|
|
2198
|
+
},
|
|
2199
|
+
"periodLabel": {
|
|
2200
|
+
"type": "string",
|
|
2201
|
+
"required": false
|
|
2202
|
+
},
|
|
2203
|
+
"score": {
|
|
2204
|
+
"type": "OverviewScore | undefined",
|
|
2205
|
+
"required": false
|
|
2206
|
+
},
|
|
2207
|
+
"scoreErrorMessage": {
|
|
2208
|
+
"type": "string | undefined",
|
|
2209
|
+
"required": false
|
|
2210
|
+
},
|
|
2211
|
+
"variant": {
|
|
2212
|
+
"type": "CardOverviewVariant",
|
|
2213
|
+
"required": false
|
|
2214
|
+
}
|
|
2215
|
+
},
|
|
2216
|
+
"events": [
|
|
2217
|
+
{
|
|
2218
|
+
"name": "dsMetricSelect",
|
|
2219
|
+
"detail": "OverviewMetric"
|
|
2220
|
+
}
|
|
2221
|
+
],
|
|
2222
|
+
"slots": [],
|
|
2223
|
+
"exports": {
|
|
2224
|
+
"customElement": "ds-card-overview",
|
|
2225
|
+
"react": "DsCardOverview",
|
|
2226
|
+
"angular": "DsCardOverview"
|
|
2227
|
+
}
|
|
2228
|
+
},
|
|
2229
|
+
"dependencies": [
|
|
2230
|
+
"@ds-mo/ui",
|
|
2231
|
+
"@ds-mo/tokens",
|
|
2232
|
+
"@ds-mo/icons"
|
|
2233
|
+
],
|
|
2234
|
+
"registryDependencies": [
|
|
2235
|
+
"icon",
|
|
2236
|
+
"skeleton",
|
|
2237
|
+
"text",
|
|
2238
|
+
"tooltip"
|
|
2239
|
+
]
|
|
2240
|
+
},
|
|
2019
2241
|
{
|
|
2020
2242
|
"name": "card-setting",
|
|
2021
2243
|
"title": "CardSetting",
|
|
@@ -2334,7 +2556,8 @@
|
|
|
2334
2556
|
"Authored datum colors override the shared categorical palette."
|
|
2335
2557
|
],
|
|
2336
2558
|
"responsiveBehavior": [
|
|
2337
|
-
"
|
|
2559
|
+
"Width and height provide the standalone intrinsic viewport; container constraints are measured and reflow the plot across the available pixels.",
|
|
2560
|
+
"Responsive layout changes bar positions and plot extents without scaling SVG text, strokes, corner radii, or margins.",
|
|
2338
2561
|
"Limit or pre-format long category labels so they remain legible at the chosen width.",
|
|
2339
2562
|
"Time labels remain ordered, pre-formatted buckets with equal spacing rather than raw continuous timestamps."
|
|
2340
2563
|
],
|
|
@@ -2343,10 +2566,10 @@
|
|
|
2343
2566
|
"Assign data as a JavaScript property; arrays are not supported as HTML attributes."
|
|
2344
2567
|
],
|
|
2345
2568
|
"react": [
|
|
2346
|
-
"Pass a stable data array and dimensions
|
|
2569
|
+
"Pass a stable data array; width and height provide standalone intrinsic dimensions while constrained containers reflow automatically."
|
|
2347
2570
|
],
|
|
2348
2571
|
"angular": [
|
|
2349
|
-
"Bind data as a property and
|
|
2572
|
+
"Bind data as a property; width and height provide standalone intrinsic dimensions while constrained containers reflow automatically."
|
|
2350
2573
|
]
|
|
2351
2574
|
},
|
|
2352
2575
|
"references": [
|
|
@@ -2446,8 +2669,9 @@
|
|
|
2446
2669
|
"Negative values are unsupported and render as zero; the owning product supplies loading, empty, and error states."
|
|
2447
2670
|
],
|
|
2448
2671
|
"responsiveBehavior": [
|
|
2449
|
-
"
|
|
2450
|
-
"
|
|
2672
|
+
"Width and height provide the standalone intrinsic viewport; constrained card regions are measured and reflow both axes to their rendered pixel dimensions.",
|
|
2673
|
+
"Responsive layout changes stack positions and plot extents without scaling SVG text, strokes, separators, corner radii, or margins.",
|
|
2674
|
+
"The parent chooses how many time buckets remain legible at each width.",
|
|
2451
2675
|
"Pre-format category labels and reduce bucket count before rendering when horizontal space becomes constrained."
|
|
2452
2676
|
],
|
|
2453
2677
|
"frameworkCaveats": {
|
|
@@ -2562,6 +2786,7 @@
|
|
|
2562
2786
|
"responsiveBehavior": [
|
|
2563
2787
|
"Prefer container-fill sizing inside data-viz cards and explicit sizing only in layouts that own a stable diameter.",
|
|
2564
2788
|
"Fill sizing remains centered and constrained to the shared donut size band.",
|
|
2789
|
+
"Changing the measured diameter changes the ring radius without scaling its token-based thickness, typography, focus stroke, or corner treatment.",
|
|
2565
2790
|
"The owner adapts category count and legend placement for available space."
|
|
2566
2791
|
],
|
|
2567
2792
|
"frameworkCaveats": {
|
|
@@ -2850,7 +3075,8 @@
|
|
|
2850
3075
|
"Authored series colors override the shared categorical palette."
|
|
2851
3076
|
],
|
|
2852
3077
|
"responsiveBehavior": [
|
|
2853
|
-
"
|
|
3078
|
+
"Width and height provide the standalone intrinsic viewport; constrained card regions are measured and reflow both axes to their rendered pixel dimensions.",
|
|
3079
|
+
"Responsive layout changes point positions and plot extents without scaling SVG text, line strokes, point markers, or margins.",
|
|
2854
3080
|
"Category positions are always equally spaced, even when labels represent time.",
|
|
2855
3081
|
"Reduce or pre-format dense labels before passing them; the chart renders authored labels without a continuous time scale."
|
|
2856
3082
|
],
|
|
@@ -2859,10 +3085,10 @@
|
|
|
2859
3085
|
"Assign series and categories as JavaScript properties and keep every series aligned to the category count."
|
|
2860
3086
|
],
|
|
2861
3087
|
"react": [
|
|
2862
|
-
"Pass stable series and category arrays
|
|
3088
|
+
"Pass stable series and category arrays; width and height provide standalone intrinsic dimensions while constrained containers reflow automatically."
|
|
2863
3089
|
],
|
|
2864
3090
|
"angular": [
|
|
2865
|
-
"Bind series and categories as properties
|
|
3091
|
+
"Bind series and categories as properties; width and height provide standalone intrinsic dimensions while constrained containers reflow automatically."
|
|
2866
3092
|
]
|
|
2867
3093
|
},
|
|
2868
3094
|
"references": [
|
|
@@ -6308,6 +6534,7 @@
|
|
|
6308
6534
|
"Tools with search compose ds-panel-tool-search immediately below the shared header. Its 48px row always contains the md search control and must not be reduced to sm or xs.",
|
|
6309
6535
|
"If an active optional tool becomes unavailable, PanelTools closes the drawer, clears activeTool, and emits its deselection.",
|
|
6310
6536
|
"Use isInactive only for a temporarily unavailable visible tool; omit tools the user is not authorized to access.",
|
|
6537
|
+
"Rail actions disable physical press scaling so notification-dot halos remain aligned to the shell gradient; hover and pressed washes continue to paint above the dot and halo.",
|
|
6311
6538
|
"The application owns slotted tool products and data while PanelTools owns rail ordering, drawer selection, open state, motion, and shell events.",
|
|
6312
6539
|
"Opening and closing clip an already-rendered fixed-width surface. Header and tool content remain fully opaque and retained during width motion; transition completion is accepted only at the measured terminal clip width, after which the closed surface becomes inert and paint-skipped.",
|
|
6313
6540
|
"Entering and exiting fullscreen snaps without borrowing the drawer open or close animation.",
|
|
@@ -7638,7 +7865,8 @@
|
|
|
7638
7865
|
"A ShellPage-managed BarTitle remains concealed until headerCapacity is available and its rendered variant matches the resolved presentation, preventing a default-variant flash during route activation.",
|
|
7639
7866
|
"responsiveMode=mobile hides and inerts the desktop header, exposes mobile-header, and disables desktop scroll-compaction geometry.",
|
|
7640
7867
|
"Update headerCapacity synchronously with shell layout state so opening tools or crossing application breakpoints cannot briefly render the wrong presentation.",
|
|
7641
|
-
"
|
|
7868
|
+
"The default content inset is 32px in desktop mode and 16px in tablet and mobile modes, while preserving the larger bottom scroll space.",
|
|
7869
|
+
"On roomy pages, the expanded header scrolls upward until its title and action row reaches the compact row position, while its bottom divider progressively extends from the expanded inset to edge-to-edge; it then snaps variants without moving that shared visual anchor.",
|
|
7642
7870
|
"Scroll compaction preserves the expanded page flow offset so content never jumps beneath the sticky header.",
|
|
7643
7871
|
"ShellPage relies on ShellApp's content scroller and never introduces nested scrolling.",
|
|
7644
7872
|
"BarWorkflow remains compact and is not assigned BarTitle's capacity or scroll variants."
|
|
@@ -8870,7 +9098,7 @@
|
|
|
8870
9098
|
"states": [
|
|
8871
9099
|
"A variant is atomic: font size, line height, weight, and letter spacing stay together and are not overridden by consumers.",
|
|
8872
9100
|
"Emphasis selects the supported stronger recipe for local hierarchy; it does not independently mean selected or active.",
|
|
8873
|
-
"Omitted color inherits currentColor. Explicit semantic aliases or CSS-variable references are used
|
|
9101
|
+
"Omitted color inherits currentColor without imposing a host color declaration, so an owning component may apply its semantic foreground directly without depending on stylesheet order. Explicit semantic aliases or CSS-variable references are used when Text has an independent color role.",
|
|
8874
9102
|
"Prefer the owning control's isInactive or native disabled state over manually selecting tertiary or quaternary so visual treatment and inactive semantics remain synchronized.",
|
|
8875
9103
|
"Line truncation and wrapping require an owner-supplied width constraint and content-aware composition decision.",
|
|
8876
9104
|
"Solid underline is the default link affordance; brand-blue links may omit it at rest. Dotted underline communicates hidden or supplemental interaction such as a tooltip. Decoration does not make Text interactive by itself.",
|
|
@@ -9228,7 +9456,7 @@
|
|
|
9228
9456
|
},
|
|
9229
9457
|
{
|
|
9230
9458
|
"label": "Positioning unit coverage",
|
|
9231
|
-
"path": "tests/
|
|
9459
|
+
"path": "tests/anchored-position.test.ts"
|
|
9232
9460
|
}
|
|
9233
9461
|
]
|
|
9234
9462
|
},
|
|
@@ -9338,6 +9566,8 @@
|
|
|
9338
9566
|
"The owning chart mounts the callout when a local interaction starts and unmounts it when that interaction ends.",
|
|
9339
9567
|
"Coordinate updates track within one mounted interaction session without restarting the show delay.",
|
|
9340
9568
|
"Placement flips horizontally or vertically when the rendered callout approaches a viewport edge.",
|
|
9569
|
+
"Placement is chosen as a quadrant rather than computed as coordinates: the component measures its own rendered rect and flips its side and align props, which CSS consumes. It therefore does not use the shared anchored-position utilities, which resolve an anchor element into x and y.",
|
|
9570
|
+
"This separation is deliberate. The callout follows the pointer instead of an element, so it has no anchor rect to resolve; adopting the shared geometry would mean rewriting its CSS to consume coordinates for no behavioral gain.",
|
|
9341
9571
|
"Reduced motion preserves the final visible state without an opacity transition."
|
|
9342
9572
|
],
|
|
9343
9573
|
"responsiveBehavior": [
|
|
@@ -722,7 +722,7 @@
|
|
|
722
722
|
"complexPropertyNote": "Assign these non-primitive values as JavaScript properties: options, sections, values.",
|
|
723
723
|
"peerDependencies": {
|
|
724
724
|
"required": [
|
|
725
|
-
"@ds-mo/tokens ^
|
|
725
|
+
"@ds-mo/tokens ^5.0.0",
|
|
726
726
|
"@ds-mo/icons ^6.4.0"
|
|
727
727
|
],
|
|
728
728
|
"frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
|
|
@@ -751,7 +751,7 @@
|
|
|
751
751
|
},
|
|
752
752
|
{
|
|
753
753
|
"path": "src/wc/components/SelectMulti/SelectMulti.tsx",
|
|
754
|
-
"content": "import {\n AttachInternals,\n Component,\n Element,\n Event,\n EventEmitter,\n h,\n Host,\n Method,\n Prop,\n State,\n Watch,\n} from '@stencil/core';\nimport {\n controlWidthClass,\n CONTROL_SUPPORTING_TEXT_VARIANT,\n CONTROL_TEXT_VARIANT,\n DEFAULT_REQUIRED_MESSAGE,\n setRequiredValidity,\n type ControlWidth,\n} from '../../utils';\nimport { ChoiceFooter, ChoiceOptionRow, ChoiceSearch } from '../../utils/choice-list-parts';\nimport {\n choiceBackgroundClassMap,\n choiceListUsesSubtext,\n filterChoiceSections,\n flattenChoiceSections,\n resolveChoiceSections,\n} from '../../utils/choice-list';\nimport { SelectController } from '../../utils/select-controller';\nimport type {\n SelectBackground,\n SelectOption,\n SelectSection,\n SelectSize,\n} from '../Select/Select';\n\nexport type SelectMultiOption = Omit<SelectOption, 'icon'>;\nexport type SelectMultiSection = Omit<SelectSection, 'options'> & {\n options: SelectMultiOption[];\n};\nexport type SelectMultiBackground = SelectBackground;\nexport type SelectMultiSize = SelectSize;\nexport type SelectMultiWidth = ControlWidth;\n\nconst ICON_SIZE: Record<SelectMultiSize, 'lg' | 'md' | 'sm' | 'xs'> = {\n lg: 'lg',\n md: 'md',\n sm: 'sm',\n xs: 'xs',\n};\n\nlet selectMultiId = 0;\n\n@Component({\n tag: 'ds-select-multi',\n styleUrl: 'SelectMulti.css',\n scoped: true,\n formAssociated: true,\n})\nexport class SelectMulti {\n @Element() el!: HTMLElement;\n @AttachInternals() internals!: ElementInternals;\n\n /** Flat choices. Assign arrays through the JavaScript property. */\n @Prop() options: SelectMultiOption[] = [];\n /** Grouped choices; takes precedence over options. Assign through JavaScript. */\n @Prop() sections: SelectMultiSection[] = [];\n /** Selected values. Assign arrays through the JavaScript property. */\n @Prop({ mutable: true }) values: string[] = [];\n /** Controlled popup visibility. */\n @Prop({ mutable: true, reflect: true }) open: boolean = false;\n /** Native form field name used for each repeated selected-value entry. */\n @Prop({ reflect: true }) name: string | undefined;\n /** Native disabled state. */\n @Prop({ reflect: true }) disabled: boolean = false;\n /** Require at least one valid selected value. */\n @Prop({ reflect: true }) required: boolean = false;\n /** Validation message used when required is missing. */\n @Prop() requiredMessage: string = DEFAULT_REQUIRED_MESSAGE;\n /** Persistent trigger label; selected option labels never replace it. */\n @Prop() placeholder: string = 'Select';\n /** Control density. */\n @Prop() size: SelectMultiSize = 'md';\n /** Width fit — hug content (default) or fill the parent. */\n @Prop() width: SelectMultiWidth = 'hug';\n /** Shared inactive treatment; removes interaction and form submission. */\n @Prop() isInactive: boolean = false;\n /** Replace the prefix with a loader and disable option interaction. */\n @Prop() isLoading: boolean = false;\n /** Show the selected interaction fill when at least one value exists. */\n @Prop() activeFill: boolean = true;\n /** Show the surface-aware inset border. */\n @Prop() hasBorder: boolean = true;\n /** Optional trigger prefix icon name. */\n @Prop() icon: string | undefined;\n /** Show the clear-all footer action when values exist. */\n @Prop() allowClear: boolean = true;\n /** Localized clear-all action label. */\n @Prop() clearLabel: string = 'Clear';\n /** Localized noun displayed after the selected count. */\n @Prop() selectedLabel: string = 'selected';\n /** Show immediate local filtering over option labels, subtext, and section headings. */\n @Prop() searchable: boolean = false;\n /** Localized search-field placeholder and accessible name. */\n @Prop() searchPlaceholder: string = 'Search';\n /** Localized empty-filter result text. */\n @Prop() noResultsText: string = 'No results found';\n /** Accessible loading status label. */\n @Prop() loadingLabel: string = 'Loading';\n /** Actual parent surface context; omit on primary and secondary surfaces. */\n @Prop() background: SelectMultiBackground | undefined;\n /** Show invalid visual state. */\n @Prop() error: boolean = false;\n /** Error text rendered below the trigger when error is true. */\n @Prop() errorMessage: string | undefined;\n /** ID applied to the internal combobox trigger for external labels. */\n @Prop() inputId: string | undefined;\n /** Direct accessible name when no external label is available. */\n @Prop({ attribute: 'aria-label' }) ariaLabel: string | null = null;\n /** IDs of elements that label the combobox. */\n @Prop({ attribute: 'aria-labelledby' }) ariaLabelledby: string | undefined;\n /** Additional IDs that describe the combobox. */\n @Prop({ attribute: 'aria-describedby' }) ariaDescribedby: string | undefined;\n\n /** Emitted after user toggles or clearing with a new values array. */\n @Event() dsChange!: EventEmitter<string[]>;\n /** Emitted after the footer clear-all action. */\n @Event() dsClear!: EventEmitter<void>;\n /** Emitted whenever popup visibility changes. */\n @Event() dsOpenChange!: EventEmitter<boolean>;\n\n @State() private activeIndex = -1;\n @State() private searchTerm = '';\n @State() private formDisabled = false;\n @State() private position = { x: 0, y: 0 };\n @State() private positionReady = false;\n @State() private focusRingVisible = false;\n\n private readonly generatedId = `ds-select-multi-${++selectMultiId}`;\n private readonly listboxId = `${this.generatedId}-listbox`;\n private readonly errorId = `${this.generatedId}-error`;\n private initialValues: string[] = [];\n private readonly controller = this.createController();\n\n private createController() {\n // eslint-disable-next-line @typescript-eslint/no-this-alias -- adapter getters preserve reactive component ownership without inheritance.\n const owner = this;\n return new SelectController<SelectMultiOption>({\n get host() { return owner.el; },\n generatedId: owner.generatedId,\n get options() { return owner.visibleOptions; },\n get searchable() { return owner.searchable; },\n get isLoading() { return owner.isLoading; },\n get isDisabled() { return owner.isDisabled; },\n get preferredIndex() { return -1; },\n get open() { return owner.open; },\n set open(value) { owner.open = value; },\n get activeIndex() { return owner.activeIndex; },\n set activeIndex(value) { owner.activeIndex = value; },\n get searchTerm() { return owner.searchTerm; },\n set searchTerm(value) { owner.searchTerm = value; },\n get focusRingVisible() { return owner.focusRingVisible; },\n set focusRingVisible(value) { owner.focusRingVisible = value; },\n get position() { return owner.position; },\n set position(value) { owner.position = value; },\n get positionReady() { return owner.positionReady; },\n set positionReady(value) { owner.positionReady = value; },\n selectOption: option => owner.toggleOption(option),\n });\n }\n\n componentWillLoad() {\n this.initialValues = [...this.values];\n this.syncFormValue();\n }\n\n componentDidLoad() {\n this.controller.connect();\n }\n\n disconnectedCallback() {\n this.controller.disconnect();\n }\n\n @Watch('values')\n @Watch('disabled')\n @Watch('isInactive')\n @Watch('required')\n @Watch('name')\n @Watch('options')\n @Watch('sections')\n syncFormValue() {\n const resolvedValues = this.resolvedValues;\n if (this.isDisabled || !this.name || resolvedValues.length === 0) {\n this.internals.setFormValue(null);\n } else {\n const data = new FormData();\n resolvedValues.forEach(value => data.append(this.name as string, value));\n this.internals.setFormValue(data, JSON.stringify(resolvedValues));\n }\n const missing = this.required && !this.isDisabled && resolvedValues.length === 0;\n setRequiredValidity(this.internals, missing, this.requiredMessage);\n this.controller.optionsChanged();\n }\n\n @Watch('isLoading')\n onLoadingChange() {\n this.controller.loadingChanged();\n }\n\n @Watch('open')\n onOpenChange(open: boolean) {\n this.dsOpenChange.emit(open);\n this.controller.openChanged(open);\n }\n\n @Watch('searchTerm')\n onSearchTermChange() {\n this.controller.searchChanged();\n }\n\n @Watch('activeIndex')\n onActiveIndexChange() {\n this.controller.activeIndexChanged();\n }\n\n formDisabledCallback(disabled: boolean) {\n this.formDisabled = disabled;\n this.syncFormValue();\n }\n\n formResetCallback() {\n this.values = [...this.initialValues];\n this.closePopup();\n }\n\n formStateRestoreCallback(state: string | File | FormData | null) {\n if (typeof state !== 'string') {\n this.values = [];\n return;\n }\n try {\n const restored = JSON.parse(state);\n this.values = Array.isArray(restored)\n ? restored.filter((value): value is string => typeof value === 'string')\n : [];\n } catch {\n this.values = [];\n }\n }\n\n @Method()\n async setFocus() {\n this.controller.setFocus();\n }\n\n private get isDisabled(): boolean {\n return this.disabled || this.isInactive || this.formDisabled;\n }\n\n private get allSections(): SelectMultiSection[] {\n return resolveChoiceSections(this.options, this.sections);\n }\n\n private get allOptions(): SelectMultiOption[] {\n return flattenChoiceSections(this.allSections);\n }\n\n private get visibleSections(): SelectMultiSection[] {\n return this.searchable\n ? filterChoiceSections(this.allSections, this.searchTerm)\n : this.allSections;\n }\n\n private get visibleOptions(): SelectMultiOption[] {\n return flattenChoiceSections(this.visibleSections);\n }\n\n private get resolvedValues(): string[] {\n const valid = new Set(this.allOptions.map(option => option.value));\n return [...new Set(this.values.filter(value => valid.has(value)))];\n }\n\n private get hasSelection(): boolean {\n return this.resolvedValues.length > 0;\n }\n\n private get activeOptionId(): string | undefined {\n return this.controller.activeOptionId;\n }\n\n private closePopup(restoreFocus = false) {\n this.controller.closePopup(restoreFocus);\n }\n\n private openPopup(focusVisible: boolean, edge?: 'first' | 'last') {\n this.controller.openPopup(focusVisible, edge);\n }\n\n private toggleOption(option: SelectMultiOption) {\n if (option.isInactive || this.isLoading) return;\n const current = this.resolvedValues;\n const next = current.includes(option.value)\n ? current.filter(value => value !== option.value)\n : [...current, option.value];\n this.values = next;\n this.dsChange.emit([...next]);\n }\n\n private clearSelection = (event: Event) => {\n event.preventDefault();\n event.stopPropagation();\n this.values = [];\n this.dsChange.emit([]);\n this.dsClear.emit();\n this.controller.focusSearchOrTrigger();\n };\n\n private readonly handleListKeyDown = this.controller.handleListKeyDown;\n private readonly handleTriggerKeyDown = this.controller.handleTriggerKeyDown;\n\n private renderOption(option: SelectMultiOption, index: number, usesSubtext: boolean) {\n const selected = this.values.includes(option.value);\n const active = this.activeIndex === index;\n return (\n <ChoiceOptionRow\n size={this.size}\n id={`${this.generatedId}-option-${index}`}\n option={option}\n selected={selected}\n active={active}\n focusRingVisible={this.focusRingVisible}\n usesSubtext={usesSubtext}\n leading={(\n <span class=\"ds-choice-item__icon ds-control-icon-box ds-interaction-fill__content\" aria-hidden=\"true\">\n <ds-checkbox\n class=\"select-option__checkbox\"\n label=\"\"\n size={this.size}\n checked={selected}\n presentation\n />\n </span>\n )}\n onHover={() => {\n this.focusRingVisible = false;\n this.activeIndex = index;\n }}\n onSelect={() => this.toggleOption(option)}\n />\n );\n }\n\n render() {\n const inactive = this.isDisabled;\n const count = this.resolvedValues.length;\n const textVariant = CONTROL_TEXT_VARIANT[this.size];\n const iconSize = ICON_SIZE[this.size];\n const usesOptionSubtext = choiceListUsesSubtext(this.allOptions);\n const showError = this.error && Boolean(this.errorMessage);\n const describedBy = [this.ariaDescribedby, showError ? this.errorId : undefined]\n .filter(Boolean)\n .join(' ') || undefined;\n const popupStyle = {\n position: 'fixed',\n left: '0',\n top: '0',\n transform: `translate(${Math.round(this.position.x)}px, ${Math.round(this.position.y)}px)`,\n zIndex: 'var(--dimension-z-index-floating)',\n visibility: this.positionReady ? 'visible' : 'hidden',\n };\n let flatIndex = 0;\n\n return (\n <Host\n class={{\n 'select-host': true,\n 'ds-field-stack': true,\n 'ds-control-inactive': inactive,\n [`ds-control--${this.size}`]: true,\n ...controlWidthClass(this.width),\n [`select-host--background-${this.background}`]: !!this.background,\n }}\n >\n <button\n ref={element => {\n this.controller.setTriggerElement((element as HTMLButtonElement) ?? null);\n }}\n id={this.inputId ?? this.generatedId}\n type=\"button\"\n class={{\n trigger: true,\n 'ds-control-frame': true,\n 'ds-focus-ring-inset': true,\n 'ds-interaction-fill': true,\n 'ds-interaction-fill--selected':\n !inactive && this.activeFill && this.hasSelection,\n 'trigger--bordered': this.hasBorder || this.error,\n 'trigger--has-value': this.hasSelection,\n 'wrapper--error': this.error,\n [`ds-control--${this.size}`]: true,\n ...choiceBackgroundClassMap(this.background),\n }}\n disabled={inactive}\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-expanded={String(this.open)}\n aria-controls={this.open ? this.listboxId : undefined}\n aria-activedescendant={this.open && !this.searchable ? this.activeOptionId : undefined}\n aria-label={this.ariaLabel}\n aria-labelledby={this.ariaLabelledby}\n aria-describedby={describedBy}\n aria-invalid={this.error ? 'true' : undefined}\n aria-required={this.required || undefined}\n aria-busy={this.isLoading ? 'true' : undefined}\n onClick={() => (this.open ? this.closePopup() : this.openPopup(false))}\n onKeyDown={this.handleTriggerKeyDown}\n >\n {(this.icon || this.isLoading) && (\n <span class=\"trigger__prefix ds-control-icon-box ds-interaction-fill__content\" aria-hidden=\"true\">\n {this.isLoading ? (\n <ds-loader size={iconSize} color=\"inherit\" />\n ) : (\n <ds-icon name={this.icon} size={iconSize} color=\"inherit\" />\n )}\n </span>\n )}\n <ds-text\n class=\"trigger__label ds-control-label-box ds-interaction-fill__content\"\n as=\"span\"\n variant={textVariant}\n color=\"inherit\"\n lineTruncation={1}\n >\n {`${this.placeholder}${count > 0 ? ` · ${count}` : ''}`}\n </ds-text>\n <span class=\"trigger__chevron ds-control-icon-box ds-interaction-fill__content\" aria-hidden=\"true\">\n <ds-icon name=\"ChevronDown\" size={iconSize} color=\"inherit\" />\n </span>\n </button>\n\n {this.open && (\n <div\n ref={element => {\n this.controller.setPopupElement((element as HTMLDivElement) ?? null);\n }}\n class=\"select-popup ds-choice-popup\"\n style={popupStyle}\n >\n {this.searchable && (\n <ChoiceSearch\n size={this.size}\n value={this.searchTerm}\n placeholder={this.searchPlaceholder}\n controls={this.listboxId}\n activeDescendant={this.activeOptionId}\n inputRef={this.controller.setSearchElement}\n clearLabel={this.clearLabel}\n onValueChange={value => {\n this.searchTerm = value;\n }}\n onKeyDown={event => this.handleListKeyDown(event)}\n />\n )}\n <div\n id={this.listboxId}\n class=\"ds-choice-list\"\n role=\"listbox\"\n aria-multiselectable=\"true\"\n aria-label={this.ariaLabel ?? this.placeholder}\n aria-busy={this.isLoading ? 'true' : undefined}\n >\n {this.isLoading ? (\n <div\n class=\"ds-choice-loading\"\n role=\"option\"\n aria-selected=\"false\"\n aria-disabled=\"true\"\n aria-label={this.loadingLabel}\n aria-live=\"polite\"\n >\n <ds-loader size={this.size} color=\"inherit\" />\n </div>\n ) : this.visibleOptions.length === 0 ? (\n <div\n class=\"ds-choice-empty ds-empty-region\"\n role=\"option\"\n aria-selected=\"false\"\n aria-disabled=\"true\"\n aria-label={this.noResultsText}\n aria-live=\"polite\"\n >\n <ds-empty-state body={this.noResultsText} />\n </div>\n ) : (\n this.visibleSections.map(section => (\n <div\n class={{\n 'ds-choice-section': true,\n 'ds-chrome-column': true,\n 'ds-chrome-space--sm': true,\n 'ds-choice-section--divided': !!section.divider,\n }}\n role={section.header ? 'group' : undefined}\n aria-label={section.header}\n >\n {section.header && (\n <ds-text\n class={`ds-choice-section__header ds-control--${this.size}`}\n as=\"span\"\n variant={CONTROL_SUPPORTING_TEXT_VARIANT[this.size]}\n emphasis\n color=\"primary\"\n aria-hidden=\"true\"\n >\n {section.header}\n </ds-text>\n )}\n {section.options.map(option => this.renderOption(\n option,\n flatIndex++,\n usesOptionSubtext,\n ))}\n </div>\n ))\n )}\n </div>\n {this.allowClear && this.hasSelection && !this.isLoading && (\n <ChoiceFooter\n size={this.size}\n summary={`${count} ${this.selectedLabel}`}\n clearLabel={this.clearLabel}\n onClear={this.clearSelection}\n />\n )}\n </div>\n )}\n\n {showError && (\n <ds-text\n class=\"error-text\"\n as=\"div\"\n variant=\"text-body-small\"\n color=\"negative\"\n textId={this.errorId}\n role=\"alert\"\n >\n {this.errorMessage}\n </ds-text>\n )}\n </Host>\n );\n }\n}\n",
|
|
754
|
+
"content": "import {\n AttachInternals,\n Component,\n Element,\n Event,\n EventEmitter,\n h,\n Host,\n Method,\n Prop,\n State,\n Watch,\n} from '@stencil/core';\nimport {\n controlWidthClass,\n CONTROL_SUPPORTING_TEXT_VARIANT,\n CONTROL_TEXT_VARIANT,\n DEFAULT_REQUIRED_MESSAGE,\n setRequiredValidity,\n type ControlWidth,\n} from '../../utils';\nimport { ChoiceFooter, ChoiceOptionRow, ChoiceSearch } from '../../utils/choice-list-parts';\nimport {\n choiceBackgroundClassMap,\n choiceListUsesSubtext,\n filterChoiceSections,\n flattenChoiceSections,\n resolveChoiceSections,\n} from '../../utils/choice-list';\nimport { SelectController } from '../../utils/select-controller';\nimport type { SelectBackground, SelectOption, SelectSection, SelectSize } from '../Select/Select';\n\nexport type SelectMultiOption = Omit<SelectOption, 'icon'>;\nexport type SelectMultiSection = Omit<SelectSection, 'options'> & {\n options: SelectMultiOption[];\n};\nexport type SelectMultiBackground = SelectBackground;\nexport type SelectMultiSize = SelectSize;\nexport type SelectMultiWidth = ControlWidth;\n\nconst ICON_SIZE: Record<SelectMultiSize, 'lg' | 'md' | 'sm' | 'xs'> = {\n lg: 'lg',\n md: 'md',\n sm: 'sm',\n xs: 'xs',\n};\n\nlet selectMultiId = 0;\n\n@Component({\n tag: 'ds-select-multi',\n styleUrl: 'SelectMulti.css',\n scoped: true,\n formAssociated: true,\n})\nexport class SelectMulti {\n @Element() el!: HTMLElement;\n @AttachInternals() internals!: ElementInternals;\n\n /** Flat choices. Assign arrays through the JavaScript property. */\n @Prop() options: SelectMultiOption[] = [];\n /** Grouped choices; takes precedence over options. Assign through JavaScript. */\n @Prop() sections: SelectMultiSection[] = [];\n /** Selected values. Assign arrays through the JavaScript property. */\n @Prop({ mutable: true }) values: string[] = [];\n /** Controlled popup visibility. */\n @Prop({ mutable: true, reflect: true }) open: boolean = false;\n /** Native form field name used for each repeated selected-value entry. */\n @Prop({ reflect: true }) name: string | undefined;\n /** Native disabled state. */\n @Prop({ reflect: true }) disabled: boolean = false;\n /** Require at least one valid selected value. */\n @Prop({ reflect: true }) required: boolean = false;\n /** Validation message used when required is missing. */\n @Prop() requiredMessage: string = DEFAULT_REQUIRED_MESSAGE;\n /** Persistent trigger label; selected option labels never replace it. */\n @Prop() placeholder: string = 'Select';\n /** Control density. */\n @Prop() size: SelectMultiSize = 'md';\n /** Width fit — hug content (default) or fill the parent. */\n @Prop() width: SelectMultiWidth = 'hug';\n /** Shared inactive treatment; removes interaction and form submission. */\n @Prop() isInactive: boolean = false;\n /** Replace the prefix with a loader and disable option interaction. */\n @Prop() isLoading: boolean = false;\n /** Show the selected interaction fill when at least one value exists. */\n @Prop() activeFill: boolean = true;\n /** Show the surface-aware inset border. */\n @Prop() hasBorder: boolean = true;\n /** Optional trigger prefix icon name. */\n @Prop() icon: string | undefined;\n /** Show the clear-all footer action when values exist. */\n @Prop() allowClear: boolean = true;\n /** Localized clear-all action label. */\n @Prop() clearLabel: string = 'Clear';\n /** Localized noun displayed after the selected count. */\n @Prop() selectedLabel: string = 'selected';\n /** Show immediate local filtering over option labels, subtext, and section headings. */\n @Prop() searchable: boolean = false;\n /** Localized search-field placeholder and accessible name. */\n @Prop() searchPlaceholder: string = 'Search';\n /** Localized empty-filter result text. */\n @Prop() noResultsText: string = 'No results found';\n /** Accessible loading status label. */\n @Prop() loadingLabel: string = 'Loading';\n /** Actual parent surface context; omit on primary and secondary surfaces. */\n @Prop() background: SelectMultiBackground | undefined;\n /** Show invalid visual state. */\n @Prop() error: boolean = false;\n /** Error text rendered below the trigger when error is true. */\n @Prop() errorMessage: string | undefined;\n /** ID applied to the internal combobox trigger for external labels. */\n @Prop() inputId: string | undefined;\n /** Direct accessible name when no external label is available. */\n @Prop({ attribute: 'aria-label' }) ariaLabel: string | null = null;\n /** IDs of elements that label the combobox. */\n @Prop({ attribute: 'aria-labelledby' }) ariaLabelledby: string | undefined;\n /** Additional IDs that describe the combobox. */\n @Prop({ attribute: 'aria-describedby' }) ariaDescribedby: string | undefined;\n\n /** Emitted after user toggles or clearing with a new values array. */\n @Event() dsChange!: EventEmitter<string[]>;\n /** Emitted after the footer clear-all action. */\n @Event() dsClear!: EventEmitter<void>;\n /** Emitted whenever popup visibility changes. */\n @Event() dsOpenChange!: EventEmitter<boolean>;\n\n @State() private activeIndex = -1;\n @State() private searchTerm = '';\n @State() private formDisabled = false;\n @State() private position = { x: 0, y: 0 };\n @State() private positionReady = false;\n @State() private focusRingVisible = false;\n\n private readonly generatedId = `ds-select-multi-${++selectMultiId}`;\n private readonly listboxId = `${this.generatedId}-listbox`;\n private readonly errorId = `${this.generatedId}-error`;\n private initialValues: string[] = [];\n private readonly controller = this.createController();\n\n private createController() {\n // eslint-disable-next-line @typescript-eslint/no-this-alias -- adapter getters preserve reactive component ownership without inheritance.\n const owner = this;\n return new SelectController<SelectMultiOption>({\n get host() {\n return owner.el;\n },\n generatedId: owner.generatedId,\n get options() {\n return owner.visibleOptions;\n },\n get searchable() {\n return owner.searchable;\n },\n get isLoading() {\n return owner.isLoading;\n },\n get isDisabled() {\n return owner.isDisabled;\n },\n get preferredIndex() {\n return -1;\n },\n get open() {\n return owner.open;\n },\n set open(value) {\n owner.open = value;\n },\n get activeIndex() {\n return owner.activeIndex;\n },\n set activeIndex(value) {\n owner.activeIndex = value;\n },\n get searchTerm() {\n return owner.searchTerm;\n },\n set searchTerm(value) {\n owner.searchTerm = value;\n },\n get focusRingVisible() {\n return owner.focusRingVisible;\n },\n set focusRingVisible(value) {\n owner.focusRingVisible = value;\n },\n get position() {\n return owner.position;\n },\n set position(value) {\n owner.position = value;\n },\n get positionReady() {\n return owner.positionReady;\n },\n set positionReady(value) {\n owner.positionReady = value;\n },\n selectOption: option => owner.toggleOption(option),\n });\n }\n\n componentWillLoad() {\n this.initialValues = [...this.values];\n this.syncFormValue();\n }\n\n componentDidLoad() {\n this.controller.connect();\n }\n\n disconnectedCallback() {\n this.controller.disconnect();\n }\n\n @Watch('values')\n @Watch('disabled')\n @Watch('isInactive')\n @Watch('required')\n @Watch('name')\n @Watch('options')\n @Watch('sections')\n syncFormValue() {\n const resolvedValues = this.resolvedValues;\n if (this.isDisabled || !this.name || resolvedValues.length === 0) {\n this.internals.setFormValue(null);\n } else {\n const data = new FormData();\n resolvedValues.forEach(value => data.append(this.name as string, value));\n this.internals.setFormValue(data, JSON.stringify(resolvedValues));\n }\n const missing = this.required && !this.isDisabled && resolvedValues.length === 0;\n setRequiredValidity(this.internals, missing, this.requiredMessage);\n this.controller.optionsChanged();\n }\n\n @Watch('isLoading')\n onLoadingChange() {\n this.controller.loadingChanged();\n }\n\n @Watch('open')\n onOpenChange(open: boolean) {\n this.dsOpenChange.emit(open);\n this.controller.openChanged(open);\n }\n\n @Watch('searchTerm')\n onSearchTermChange() {\n this.controller.searchChanged();\n }\n\n @Watch('activeIndex')\n onActiveIndexChange() {\n this.controller.activeIndexChanged();\n }\n\n formDisabledCallback(disabled: boolean) {\n this.formDisabled = disabled;\n this.syncFormValue();\n }\n\n formResetCallback() {\n this.values = [...this.initialValues];\n this.closePopup();\n }\n\n formStateRestoreCallback(state: string | File | FormData | null) {\n if (typeof state !== 'string') {\n this.values = [];\n return;\n }\n try {\n const restored = JSON.parse(state);\n this.values = Array.isArray(restored)\n ? restored.filter((value): value is string => typeof value === 'string')\n : [];\n } catch {\n this.values = [];\n }\n }\n\n @Method()\n async setFocus() {\n this.controller.setFocus();\n }\n\n private get isDisabled(): boolean {\n return this.disabled || this.isInactive || this.formDisabled;\n }\n\n private get allSections(): SelectMultiSection[] {\n return resolveChoiceSections(this.options, this.sections);\n }\n\n private get allOptions(): SelectMultiOption[] {\n return flattenChoiceSections(this.allSections);\n }\n\n private get visibleSections(): SelectMultiSection[] {\n return this.searchable\n ? filterChoiceSections(this.allSections, this.searchTerm)\n : this.allSections;\n }\n\n private get visibleOptions(): SelectMultiOption[] {\n return flattenChoiceSections(this.visibleSections);\n }\n\n private get resolvedValues(): string[] {\n const valid = new Set(this.allOptions.map(option => option.value));\n return [...new Set(this.values.filter(value => valid.has(value)))];\n }\n\n private get hasSelection(): boolean {\n return this.resolvedValues.length > 0;\n }\n\n private get activeOptionId(): string | undefined {\n return this.controller.activeOptionId;\n }\n\n private closePopup(restoreFocus = false) {\n this.controller.closePopup(restoreFocus);\n }\n\n private openPopup(focusVisible: boolean, edge?: 'first' | 'last') {\n this.controller.openPopup(focusVisible, edge);\n }\n\n private toggleOption(option: SelectMultiOption) {\n if (option.isInactive || this.isLoading) return;\n const current = this.resolvedValues;\n const next = current.includes(option.value)\n ? current.filter(value => value !== option.value)\n : [...current, option.value];\n this.values = next;\n this.dsChange.emit([...next]);\n }\n\n private clearSelection = (event: Event) => {\n event.preventDefault();\n event.stopPropagation();\n this.values = [];\n this.dsChange.emit([]);\n this.dsClear.emit();\n this.controller.focusSearchOrTrigger();\n };\n\n private readonly handleListKeyDown = this.controller.handleListKeyDown;\n private readonly handleTriggerKeyDown = this.controller.handleTriggerKeyDown;\n\n private renderOption(option: SelectMultiOption, index: number, usesSubtext: boolean) {\n const selected = this.values.includes(option.value);\n const active = this.activeIndex === index;\n return (\n <ChoiceOptionRow\n size={this.size}\n id={`${this.generatedId}-option-${index}`}\n option={option}\n selected={selected}\n active={active}\n focusRingVisible={this.focusRingVisible}\n usesSubtext={usesSubtext}\n leading={\n <span\n class=\"ds-choice-item__icon ds-control-icon-box ds-interaction-fill__content\"\n aria-hidden=\"true\"\n >\n <ds-checkbox\n class=\"select-option__checkbox\"\n label=\"\"\n size={this.size}\n checked={selected}\n presentation\n />\n </span>\n }\n onHover={() => {\n this.focusRingVisible = false;\n this.activeIndex = index;\n }}\n onSelect={() => this.toggleOption(option)}\n />\n );\n }\n\n render() {\n const inactive = this.isDisabled;\n const count = this.resolvedValues.length;\n const textVariant = CONTROL_TEXT_VARIANT[this.size];\n const iconSize = ICON_SIZE[this.size];\n const usesOptionSubtext = choiceListUsesSubtext(this.allOptions);\n const showError = this.error && Boolean(this.errorMessage);\n const describedBy =\n [this.ariaDescribedby, showError ? this.errorId : undefined].filter(Boolean).join(' ') ||\n undefined;\n const popupStyle = {\n position: 'fixed',\n left: '0',\n top: '0',\n transform: `translate(${Math.round(this.position.x)}px, ${Math.round(this.position.y)}px)`,\n zIndex: 'var(--dimension-z-index-floating)',\n visibility: this.positionReady ? 'visible' : 'hidden',\n };\n let flatIndex = 0;\n\n return (\n <Host\n class={{\n 'select-host': true,\n 'ds-field-stack': true,\n 'ds-control-inactive': inactive,\n [`ds-control--${this.size}`]: true,\n ...controlWidthClass(this.width),\n [`select-host--background-${this.background}`]: !!this.background,\n }}\n >\n <button\n ref={element => {\n this.controller.setTriggerElement((element as HTMLButtonElement) ?? null);\n }}\n id={this.inputId ?? this.generatedId}\n type=\"button\"\n class={{\n trigger: true,\n 'ds-control-frame': true,\n 'ds-focus-ring-inset': true,\n 'ds-interaction-fill': true,\n 'ds-interaction-fill--selected': !inactive && this.activeFill && this.hasSelection,\n 'trigger--bordered': this.hasBorder || this.error,\n 'trigger--has-value': this.hasSelection,\n 'wrapper--error': this.error,\n [`ds-control--${this.size}`]: true,\n ...choiceBackgroundClassMap(this.background),\n }}\n disabled={inactive}\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-expanded={String(this.open)}\n aria-controls={this.open ? this.listboxId : undefined}\n aria-activedescendant={this.open && !this.searchable ? this.activeOptionId : undefined}\n aria-label={this.ariaLabel}\n aria-labelledby={this.ariaLabelledby}\n aria-describedby={describedBy}\n aria-invalid={this.error ? 'true' : undefined}\n aria-required={this.required || undefined}\n aria-busy={this.isLoading ? 'true' : undefined}\n onClick={() => (this.open ? this.closePopup() : this.openPopup(false))}\n onKeyDown={this.handleTriggerKeyDown}\n >\n {(this.icon || this.isLoading) && (\n <span\n class=\"trigger__prefix ds-control-icon-box ds-interaction-fill__content\"\n aria-hidden=\"true\"\n >\n {this.isLoading ? (\n <ds-loader size={iconSize} color=\"inherit\" />\n ) : (\n <ds-icon name={this.icon} size={iconSize} color=\"inherit\" />\n )}\n </span>\n )}\n <ds-text\n class=\"trigger__label ds-control-label-box ds-interaction-fill__content\"\n as=\"span\"\n variant={textVariant}\n color=\"inherit\"\n lineTruncation={1}\n >\n {`${this.placeholder}${count > 0 ? ` · ${count}` : ''}`}\n </ds-text>\n <span\n class=\"trigger__chevron ds-control-icon-box ds-interaction-fill__content\"\n aria-hidden=\"true\"\n >\n <ds-icon name=\"ChevronDown\" size={iconSize} color=\"inherit\" />\n </span>\n </button>\n\n {this.open && (\n <div\n popover=\"manual\"\n ref={element => {\n this.controller.setPopupElement((element as HTMLDivElement) ?? null);\n }}\n class=\"select-popup ds-choice-popup\"\n style={popupStyle}\n >\n {this.searchable && (\n <ChoiceSearch\n size={this.size}\n value={this.searchTerm}\n placeholder={this.searchPlaceholder}\n controls={this.listboxId}\n activeDescendant={this.activeOptionId}\n inputRef={this.controller.setSearchElement}\n clearLabel={this.clearLabel}\n onValueChange={value => {\n this.searchTerm = value;\n }}\n onKeyDown={event => this.handleListKeyDown(event)}\n />\n )}\n <div\n id={this.listboxId}\n class=\"ds-choice-list\"\n role=\"listbox\"\n aria-multiselectable=\"true\"\n aria-label={this.ariaLabel ?? this.placeholder}\n aria-busy={this.isLoading ? 'true' : undefined}\n >\n {this.isLoading ? (\n <div\n class=\"ds-choice-loading\"\n role=\"option\"\n aria-selected=\"false\"\n aria-disabled=\"true\"\n aria-label={this.loadingLabel}\n aria-live=\"polite\"\n >\n <ds-loader size={this.size} color=\"inherit\" />\n </div>\n ) : this.visibleOptions.length === 0 ? (\n <div\n class=\"ds-choice-empty ds-empty-region\"\n role=\"option\"\n aria-selected=\"false\"\n aria-disabled=\"true\"\n aria-label={this.noResultsText}\n aria-live=\"polite\"\n >\n <ds-empty-state body={this.noResultsText} />\n </div>\n ) : (\n this.visibleSections.map(section => (\n <div\n class={{\n 'ds-choice-section': true,\n 'ds-chrome-column': true,\n 'ds-chrome-space--sm': true,\n 'ds-choice-section--divided': !!section.divider,\n }}\n role={section.header ? 'group' : undefined}\n aria-label={section.header}\n >\n {section.header && (\n <ds-text\n class={`ds-choice-section__header ds-control--${this.size}`}\n as=\"span\"\n variant={CONTROL_SUPPORTING_TEXT_VARIANT[this.size]}\n emphasis\n color=\"primary\"\n aria-hidden=\"true\"\n >\n {section.header}\n </ds-text>\n )}\n {section.options.map(option =>\n this.renderOption(option, flatIndex++, usesOptionSubtext)\n )}\n </div>\n ))\n )}\n </div>\n {this.allowClear && this.hasSelection && !this.isLoading && (\n <ChoiceFooter\n size={this.size}\n summary={`${count} ${this.selectedLabel}`}\n clearLabel={this.clearLabel}\n onClear={this.clearSelection}\n />\n )}\n </div>\n )}\n\n {showError && (\n <ds-text\n class=\"error-text\"\n as=\"div\"\n variant=\"text-body-small\"\n color=\"negative\"\n textId={this.errorId}\n role=\"alert\"\n >\n {this.errorMessage}\n </ds-text>\n )}\n </Host>\n );\n }\n}\n",
|
|
755
755
|
"type": "registry:ui"
|
|
756
756
|
}
|
|
757
757
|
]
|