@ds-mo/ui 14.33.2 → 14.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.package-ready.json +1 -1
- package/dist/agent-patterns.json +1 -1
- package/dist/agent.json +311 -24
- package/dist/angular/ds-filter-menu.d.ts +1 -1
- package/dist/angular/ds-filter-menu.js +3 -3
- package/dist/angular/ds-menu.d.ts +1 -1
- package/dist/angular/ds-menu.js +3 -3
- package/dist/angular/ds-select.d.ts +1 -1
- package/dist/angular/ds-select.js +3 -3
- package/dist/angular/ds-tab-group.d.ts +1 -1
- package/dist/angular/ds-tab-group.js +3 -3
- package/dist/angular/ds-table-filter.d.ts +1 -1
- package/dist/angular/ds-table-filter.js +3 -3
- package/dist/angular/ds-table-group.d.ts +1 -1
- package/dist/angular/ds-table-group.js +3 -3
- package/dist/angular/ds-table-preferences.d.ts +39 -0
- package/dist/angular/ds-table-preferences.js +69 -0
- package/dist/angular/ds-table-saved-views.d.ts +1 -1
- package/dist/angular/ds-table-saved-views.js +3 -3
- package/dist/angular/ds-table-search.d.ts +1 -1
- package/dist/angular/ds-table-search.js +3 -3
- package/dist/angular/ds-table-sort.d.ts +1 -1
- package/dist/angular/ds-table-sort.js +3 -3
- package/dist/angular/ds-table-toolbar.d.ts +1 -1
- package/dist/angular/ds-table-toolbar.js +3 -3
- package/dist/angular/ds-table.d.ts +3 -1
- package/dist/angular/ds-table.js +8 -5
- 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-questionnaire.js +1 -1
- package/dist/components/ds-agent-response.js +1 -1
- package/dist/components/ds-agent-source-list.js +1 -1
- package/dist/components/ds-agent-tool-call.js +1 -1
- package/dist/components/ds-attachment-list.js +1 -1
- package/dist/components/ds-badge.js +1 -1
- package/dist/components/ds-banner.js +1 -1
- package/dist/components/ds-bar-action.js +1 -1
- package/dist/components/ds-bar-nav.js +1 -1
- package/dist/components/ds-bar-page-title.js +1 -1
- package/dist/components/ds-bar-title.js +1 -1
- package/dist/components/ds-bar-workflow.js +1 -1
- package/dist/components/ds-breadcrumb.js +1 -1
- package/dist/components/ds-button-filled.js +1 -1
- package/dist/components/ds-button-unfilled.js +1 -1
- package/dist/components/ds-card-action-center.js +1 -1
- package/dist/components/ds-card-chart.js +1 -1
- package/dist/components/ds-card-navigation.js +1 -1
- package/dist/components/ds-card-overview.js +1 -1
- package/dist/components/ds-card-setting.js +1 -1
- package/dist/components/ds-card-settings-scope.js +1 -1
- package/dist/components/ds-chart-legend.js +1 -1
- package/dist/components/ds-chart.js +1 -1
- package/dist/components/ds-checkbox.js +1 -1
- package/dist/components/ds-chip.js +1 -1
- package/dist/components/ds-code-block.js +1 -1
- package/dist/components/ds-conversation-list-item.js +1 -1
- package/dist/components/ds-conversation-list-section.js +1 -1
- package/dist/components/ds-empty-state.js +1 -1
- package/dist/components/ds-field.js +1 -1
- package/dist/components/ds-filter-menu.js +1 -1
- package/dist/components/ds-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-actions.js +1 -1
- package/dist/components/ds-message-bubble.js +1 -1
- package/dist/components/ds-message-bubble.js.map +1 -1
- package/dist/components/ds-message-composer.js +1 -1
- package/dist/components/ds-message-scroller.js +1 -1
- package/dist/components/ds-message.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-pagination.js +1 -1
- package/dist/components/ds-panel-nav.js +1 -1
- package/dist/components/ds-panel-sub-nav.js +1 -1
- package/dist/components/ds-panel-tool-header.js +1 -1
- package/dist/components/ds-panel-tool-search.js +1 -1
- package/dist/components/ds-panel-tools.js +1 -1
- package/dist/components/ds-paper-texture.js +1 -1
- package/dist/components/ds-radio.js +1 -1
- package/dist/components/ds-score.js +1 -1
- package/dist/components/ds-scroll-overlay.js +1 -1
- package/dist/components/ds-select.js +1 -1
- package/dist/components/ds-shell-app.js +1 -1
- package/dist/components/ds-shell-app.js.map +1 -1
- package/dist/components/ds-shell-page.js +1 -1
- package/dist/components/ds-shell-tools.js +1 -1
- package/dist/components/ds-skeleton.js +1 -1
- package/dist/components/ds-slider.js +1 -1
- package/dist/components/ds-swatch-picker.js +1 -1
- package/dist/components/ds-switch.js +1 -1
- package/dist/components/ds-tab-group.js +1 -1
- package/dist/components/ds-table-filter.js +1 -1
- package/dist/components/ds-table-filter.js.map +1 -1
- package/dist/components/ds-table-group.js +1 -1
- package/dist/components/ds-table-group.js.map +1 -1
- package/dist/components/ds-table-preferences.d.ts +11 -0
- package/dist/components/ds-table-preferences.js +2 -0
- package/dist/components/ds-table-preferences.js.map +1 -0
- package/dist/components/ds-table-saved-views.js +1 -1
- package/dist/components/ds-table-saved-views.js.map +1 -1
- package/dist/components/ds-table-search.js +1 -1
- package/dist/components/ds-table-search.js.map +1 -1
- package/dist/components/ds-table-sort.js +1 -1
- package/dist/components/ds-table-sort.js.map +1 -1
- package/dist/components/ds-table-toolbar.js +1 -1
- package/dist/components/ds-table-toolbar.js.map +1 -1
- package/dist/components/ds-table.js +1 -1
- package/dist/components/ds-table.js.map +1 -1
- package/dist/components/ds-tag.js +1 -1
- package/dist/components/ds-text.js +1 -1
- package/dist/components/ds-textarea.js +1 -1
- package/dist/components/ds-toast.js +1 -1
- package/dist/components/ds-tooltip-chart.js +1 -1
- package/dist/components/ds-tooltip.js +1 -1
- package/dist/components/ds-typing-indicator.js +1 -1
- package/dist/components/{p-ByoywoBH.js → p-B-9fdIWv.js} +2 -2
- package/dist/components/{p-CnK36Be0.js → p-BINSI_KF.js} +2 -2
- package/dist/components/{p-CK2S28gU.js → p-BPhbwXOv.js} +2 -2
- package/dist/components/{p-CMq-jKEI.js → p-BPlWEidl.js} +2 -2
- package/dist/components/{p-DyyfWSLB.js → p-BQaO7cg_.js} +2 -2
- package/dist/components/{p-Br0dx2uP.js → p-BQsVI_vO.js} +2 -2
- package/dist/components/{p-B_nPct4G.js → p-BbfBvMRR.js} +2 -2
- package/dist/components/p-BcdHgPi5.js +2 -0
- package/dist/components/p-BcdHgPi5.js.map +1 -0
- package/dist/components/{p-D_QtZZR8.js → p-BmFeeR4q.js} +2 -2
- package/dist/components/{p-CXAa9TUM.js → p-BnWxbhFP.js} +2 -2
- package/dist/components/{p-CvplapTD.js → p-BnzsBNUY.js} +2 -2
- package/dist/components/{p-BlWNMytv.js → p-C1_3CVma.js} +2 -2
- package/dist/components/p-C1_3CVma.js.map +1 -0
- package/dist/components/{p-DjCnGxSd.js → p-C7CZ9pnP.js} +2 -2
- package/dist/components/{p-DBZwI7HY.js → p-C8ek-1SP.js} +2 -2
- package/dist/components/{p-BEttT0UB.js → p-CAxDdYjh.js} +2 -2
- package/dist/components/{p-BqGdNrVl.js → p-CE540WZ7.js} +2 -2
- package/dist/components/{p-B5QVra1K.js → p-CI7VfYC3.js} +2 -2
- package/dist/components/p-CKarsuR2.js +2 -0
- package/dist/components/p-CKarsuR2.js.map +1 -0
- package/dist/components/{p-AkSwrAKR.js → p-CLGtPE0K.js} +2 -2
- package/dist/components/{p-C51ytXM6.js → p-CSnsz3Eb.js} +2 -2
- package/dist/components/{p-Dx7WcWW1.js → p-CZOiTaNa.js} +2 -2
- package/dist/components/{p-xxuHxP3u.js → p-Cmj04E5Q.js} +2 -2
- package/dist/components/p-Cmj04E5Q.js.map +1 -0
- package/dist/components/{p-zZuAAiiI.js → p-Cq_Qpe_C.js} +2 -2
- package/dist/components/{p-CweS0FfJ.js → p-CqlBlv55.js} +2 -2
- package/dist/components/p-CwqFJMvP.js +2 -0
- package/dist/components/{p-DEyvEIYL.js.map → p-CwqFJMvP.js.map} +1 -1
- package/dist/components/{p-C8XJgV1n.js → p-Cwz9TPVS.js} +2 -2
- package/dist/components/{p-C1QHxVvP.js → p-D44g2Kw1.js} +2 -2
- package/dist/components/{p-CKo2nySr.js → p-DC69DgAR.js} +2 -2
- package/dist/components/{p-CQqZgTYk.js → p-DEGNrPLe.js} +2 -2
- package/dist/components/{p-OxdAzO4v.js → p-DFjIBS5B.js} +2 -2
- package/dist/components/{p-BwsS_Hyr.js → p-DGzn4k4h.js} +2 -2
- package/dist/components/{p-Dj296RmE.js → p-DQQFN2sw.js} +2 -2
- package/dist/components/{p-CIoeHcCk.js → p-DTDLlinw.js} +2 -2
- package/dist/components/p-D_u8WhlU.js +2 -0
- package/dist/components/{p-CHBuzAYH.js.map → p-D_u8WhlU.js.map} +1 -1
- package/dist/components/{p-CqeMumqy.js → p-DdspY23v.js} +2 -2
- package/dist/components/{p-DzbEJN1A.js → p-Dk-8Ju0Q.js} +2 -2
- package/dist/components/{p-Da9VpZX2.js → p-DtLQIMsa.js} +2 -2
- package/dist/components/{p-Cw_npFwN.js → p-FPZbS19l.js} +2 -2
- package/dist/components/{p-BMERUCe1.js → p-KBrC9IG4.js} +2 -2
- package/dist/components/p-N5SHSVo2.js +2 -0
- package/dist/components/{p-CHUdAW1Q.js.map → p-N5SHSVo2.js.map} +1 -1
- package/dist/components/p-QjkufCcn.js +2 -0
- package/dist/components/p-QjkufCcn.js.map +1 -0
- package/dist/components/p-UAIxh67Q.js +2 -0
- package/dist/components/p-UAIxh67Q.js.map +1 -0
- package/dist/components/{p-CIYJhyKt.js → p-beCz9OnD.js} +2 -2
- package/dist/components/{p-LZ8F08Fj.js → p-beRFLHbB.js} +2 -2
- package/dist/components/p-beRFLHbB.js.map +1 -0
- package/dist/components/{p-4DNyCYKM.js → p-mpk_Mm8_.js} +2 -2
- package/dist/components/{p-DrjY8Mpy.js → p-n68x-ZKp.js} +2 -2
- package/dist/components/{p-CNDTHTf6.js → p-omPm8_vx.js} +2 -2
- package/dist/components/{p-lv4YQEsa.js → p-umAf0fBn.js} +2 -2
- package/dist/components/{p-ya8A_SMw.js → p-xHGqrz_p.js} +2 -2
- package/dist/components/{p-ya8A_SMw.js.map → p-xHGqrz_p.js.map} +1 -1
- package/dist/components/{p-CBQzTgvI.js → p-y06IxxoN.js} +2 -2
- package/dist/components/{p-CoE7yza3.js → p-yZRXPHDe.js} +2 -2
- package/dist/docs/components.json +2242 -720
- package/dist/lib/components/ShellApp/shell-app-types.d.ts +3 -1
- package/dist/mcp-data/registry/filter-menu.json +102 -15
- package/dist/mcp-data/registry/menu.json +22 -3
- package/dist/mcp-data/registry/registry.json +313 -23
- package/dist/mcp-data/registry/select.json +43 -8
- package/dist/mcp-data/registry/shell-app.json +4 -3
- package/dist/mcp-data/registry/tab-group.json +20 -3
- package/dist/mcp-data/registry/table-filter.json +26 -6
- package/dist/mcp-data/registry/table-group.json +59 -7
- package/dist/mcp-data/registry/table-preferences.json +511 -0
- package/dist/mcp-data/registry/table-saved-views.json +45 -7
- package/dist/mcp-data/registry/table-search.json +24 -6
- package/dist/mcp-data/registry/table-sort.json +22 -4
- package/dist/mcp-data/registry/table-toolbar.json +26 -6
- package/dist/mcp-data/registry/table.json +74 -8
- package/dist/react/components.d.ts +1 -0
- package/dist/react/components.js +1 -0
- package/dist/react/ds-table-preferences.d.ts +21 -0
- package/dist/react/ds-table-preferences.js +23 -0
- package/dist/react/ds-table.d.ts +1 -0
- package/dist/react/ds-table.js +1 -0
- package/dist/styles/table.css +26 -2
- package/dist/types/components/FilterMenu/FilterMenu.d.ts +16 -0
- package/dist/types/components/Menu/Menu.d.ts +2 -0
- package/dist/types/components/Select/Select.d.ts +5 -1
- package/dist/types/components/ShellApp/shell-app-types.d.ts +3 -1
- package/dist/types/components/TabGroup/TabGroup.d.ts +1 -0
- package/dist/types/components/Table/Table.d.ts +8 -0
- package/dist/types/components/Table/table-cell-model.d.ts +1 -1
- package/dist/types/components/Table/table-types.d.ts +13 -3
- package/dist/types/components/TableFilter/TableFilter.d.ts +2 -0
- package/dist/types/components/TableGroup/TableGroup.d.ts +5 -0
- package/dist/types/components/TablePreferences/TablePreferences.d.ts +56 -0
- package/dist/types/components/TableSavedViews/TableSavedViews.d.ts +4 -0
- package/dist/types/components/TableSearch/TableSearch.d.ts +2 -0
- package/dist/types/components/TableSort/TableSort.d.ts +2 -0
- package/dist/types/components/TableToolbar/TableToolbar.d.ts +2 -0
- package/dist/types/components.d.ts +420 -18
- package/dist/vue/components.d.ts +1 -0
- package/dist/vue/components.js +1 -0
- package/dist/vue/ds-filter-menu.js +5 -0
- package/dist/vue/ds-menu.js +1 -0
- package/dist/vue/ds-select.js +2 -0
- package/dist/vue/ds-tab-group.js +1 -0
- package/dist/vue/ds-table-filter.js +1 -0
- package/dist/vue/ds-table-group.js +3 -0
- package/dist/vue/ds-table-preferences.d.ts +3 -0
- package/dist/vue/ds-table-preferences.js +45 -0
- package/dist/vue/ds-table-saved-views.js +2 -0
- package/dist/vue/ds-table-search.js +1 -0
- package/dist/vue/ds-table-sort.js +1 -0
- package/dist/vue/ds-table-toolbar.js +2 -1
- package/dist/vue/ds-table.js +5 -0
- package/package.json +1 -1
- package/dist/components/p-BlWNMytv.js.map +0 -1
- package/dist/components/p-CHBuzAYH.js +0 -2
- package/dist/components/p-CHUdAW1Q.js +0 -2
- package/dist/components/p-CUL5_8JM.js +0 -2
- package/dist/components/p-CUL5_8JM.js.map +0 -1
- package/dist/components/p-DEyvEIYL.js +0 -2
- package/dist/components/p-DVWOUQfT.js +0 -2
- package/dist/components/p-DVWOUQfT.js.map +0 -1
- package/dist/components/p-LZ8F08Fj.js.map +0 -1
- package/dist/components/p-xxuHxP3u.js.map +0 -1
- /package/dist/components/{p-ByoywoBH.js.map → p-B-9fdIWv.js.map} +0 -0
- /package/dist/components/{p-CnK36Be0.js.map → p-BINSI_KF.js.map} +0 -0
- /package/dist/components/{p-CK2S28gU.js.map → p-BPhbwXOv.js.map} +0 -0
- /package/dist/components/{p-CMq-jKEI.js.map → p-BPlWEidl.js.map} +0 -0
- /package/dist/components/{p-DyyfWSLB.js.map → p-BQaO7cg_.js.map} +0 -0
- /package/dist/components/{p-Br0dx2uP.js.map → p-BQsVI_vO.js.map} +0 -0
- /package/dist/components/{p-B_nPct4G.js.map → p-BbfBvMRR.js.map} +0 -0
- /package/dist/components/{p-D_QtZZR8.js.map → p-BmFeeR4q.js.map} +0 -0
- /package/dist/components/{p-CXAa9TUM.js.map → p-BnWxbhFP.js.map} +0 -0
- /package/dist/components/{p-CvplapTD.js.map → p-BnzsBNUY.js.map} +0 -0
- /package/dist/components/{p-DjCnGxSd.js.map → p-C7CZ9pnP.js.map} +0 -0
- /package/dist/components/{p-DBZwI7HY.js.map → p-C8ek-1SP.js.map} +0 -0
- /package/dist/components/{p-BEttT0UB.js.map → p-CAxDdYjh.js.map} +0 -0
- /package/dist/components/{p-BqGdNrVl.js.map → p-CE540WZ7.js.map} +0 -0
- /package/dist/components/{p-B5QVra1K.js.map → p-CI7VfYC3.js.map} +0 -0
- /package/dist/components/{p-AkSwrAKR.js.map → p-CLGtPE0K.js.map} +0 -0
- /package/dist/components/{p-C51ytXM6.js.map → p-CSnsz3Eb.js.map} +0 -0
- /package/dist/components/{p-Dx7WcWW1.js.map → p-CZOiTaNa.js.map} +0 -0
- /package/dist/components/{p-zZuAAiiI.js.map → p-Cq_Qpe_C.js.map} +0 -0
- /package/dist/components/{p-CweS0FfJ.js.map → p-CqlBlv55.js.map} +0 -0
- /package/dist/components/{p-C8XJgV1n.js.map → p-Cwz9TPVS.js.map} +0 -0
- /package/dist/components/{p-C1QHxVvP.js.map → p-D44g2Kw1.js.map} +0 -0
- /package/dist/components/{p-CKo2nySr.js.map → p-DC69DgAR.js.map} +0 -0
- /package/dist/components/{p-CQqZgTYk.js.map → p-DEGNrPLe.js.map} +0 -0
- /package/dist/components/{p-OxdAzO4v.js.map → p-DFjIBS5B.js.map} +0 -0
- /package/dist/components/{p-BwsS_Hyr.js.map → p-DGzn4k4h.js.map} +0 -0
- /package/dist/components/{p-Dj296RmE.js.map → p-DQQFN2sw.js.map} +0 -0
- /package/dist/components/{p-CIoeHcCk.js.map → p-DTDLlinw.js.map} +0 -0
- /package/dist/components/{p-CqeMumqy.js.map → p-DdspY23v.js.map} +0 -0
- /package/dist/components/{p-DzbEJN1A.js.map → p-Dk-8Ju0Q.js.map} +0 -0
- /package/dist/components/{p-Da9VpZX2.js.map → p-DtLQIMsa.js.map} +0 -0
- /package/dist/components/{p-Cw_npFwN.js.map → p-FPZbS19l.js.map} +0 -0
- /package/dist/components/{p-BMERUCe1.js.map → p-KBrC9IG4.js.map} +0 -0
- /package/dist/components/{p-CIYJhyKt.js.map → p-beCz9OnD.js.map} +0 -0
- /package/dist/components/{p-4DNyCYKM.js.map → p-mpk_Mm8_.js.map} +0 -0
- /package/dist/components/{p-DrjY8Mpy.js.map → p-n68x-ZKp.js.map} +0 -0
- /package/dist/components/{p-CNDTHTf6.js.map → p-omPm8_vx.js.map} +0 -0
- /package/dist/components/{p-lv4YQEsa.js.map → p-umAf0fBn.js.map} +0 -0
- /package/dist/components/{p-CBQzTgvI.js.map → p-y06IxxoN.js.map} +0 -0
- /package/dist/components/{p-CoE7yza3.js.map → p-yZRXPHDe.js.map} +0 -0
|
@@ -92,7 +92,8 @@
|
|
|
92
92
|
"When collapseLabel is set inside a named ds-table container narrower than 900px, the trigger omits its label and chevron, becomes icon-only, and exposes its label through a hover and keyboard-focus tooltip. A selected value promotes the prefix icon to primary.",
|
|
93
93
|
"The popup matches the trigger minimum width, may grow for content, and clamps to the nearest data-ds-overlay-boundary rectangle or the viewport fallback. Assign boundary as an HTMLElement only for an explicit override.",
|
|
94
94
|
"Keep popupAlign at start for ordinary fields and filters. Use end when a control sits against a right layout edge and the popup should grow inward.",
|
|
95
|
-
"Long option collections scroll inside the popup; virtualization and remote search are not part of this component."
|
|
95
|
+
"Long option collections scroll inside the popup; virtualization and remote search are not part of this component.",
|
|
96
|
+
"An explicitly compact composition can keep only the prefix icon while preserving the accessible name and tooltip."
|
|
96
97
|
],
|
|
97
98
|
"frameworkCaveats": {
|
|
98
99
|
"customElements": [
|
|
@@ -131,8 +132,7 @@
|
|
|
131
132
|
"attribute": "active-fill",
|
|
132
133
|
"default": "false",
|
|
133
134
|
"required": false,
|
|
134
|
-
"mutable": false
|
|
135
|
-
"description": "Show the selected interaction fill when a valid value exists."
|
|
135
|
+
"mutable": false
|
|
136
136
|
},
|
|
137
137
|
"allowClear": {
|
|
138
138
|
"type": "boolean",
|
|
@@ -261,9 +261,18 @@
|
|
|
261
261
|
"mutable": false,
|
|
262
262
|
"description": "Optional trigger prefix icon name."
|
|
263
263
|
},
|
|
264
|
+
"iconOnly": {
|
|
265
|
+
"type": "boolean",
|
|
266
|
+
"resolvedType": "boolean",
|
|
267
|
+
"attribute": "icon-only",
|
|
268
|
+
"default": "false",
|
|
269
|
+
"required": false,
|
|
270
|
+
"mutable": false,
|
|
271
|
+
"description": "Always show only the prefix icon; retain an accessible name via aria-label."
|
|
272
|
+
},
|
|
264
273
|
"indicator": {
|
|
265
274
|
"type": "SelectIndicator",
|
|
266
|
-
"resolvedType": "\"down\" | \"up-down\"",
|
|
275
|
+
"resolvedType": "\"down\" | \"none\" | \"up-down\"",
|
|
267
276
|
"attribute": "indicator",
|
|
268
277
|
"default": "'down'",
|
|
269
278
|
"required": false,
|
|
@@ -322,6 +331,15 @@
|
|
|
322
331
|
"mutable": false,
|
|
323
332
|
"description": "Native form field name."
|
|
324
333
|
},
|
|
334
|
+
"neutralTrigger": {
|
|
335
|
+
"type": "boolean",
|
|
336
|
+
"resolvedType": "boolean",
|
|
337
|
+
"attribute": "neutral-trigger",
|
|
338
|
+
"default": "false",
|
|
339
|
+
"required": false,
|
|
340
|
+
"mutable": false,
|
|
341
|
+
"description": "Keep trigger colors neutral when a value is selected."
|
|
342
|
+
},
|
|
325
343
|
"noResultsText": {
|
|
326
344
|
"type": "string",
|
|
327
345
|
"resolvedType": "string",
|
|
@@ -529,8 +547,7 @@
|
|
|
529
547
|
"attribute": "active-fill",
|
|
530
548
|
"default": "false",
|
|
531
549
|
"required": false,
|
|
532
|
-
"mutable": false
|
|
533
|
-
"description": "Show the selected interaction fill when a valid value exists."
|
|
550
|
+
"mutable": false
|
|
534
551
|
},
|
|
535
552
|
"allowClear": {
|
|
536
553
|
"type": "boolean",
|
|
@@ -659,9 +676,18 @@
|
|
|
659
676
|
"mutable": false,
|
|
660
677
|
"description": "Optional trigger prefix icon name."
|
|
661
678
|
},
|
|
679
|
+
"iconOnly": {
|
|
680
|
+
"type": "boolean",
|
|
681
|
+
"resolvedType": "boolean",
|
|
682
|
+
"attribute": "icon-only",
|
|
683
|
+
"default": "false",
|
|
684
|
+
"required": false,
|
|
685
|
+
"mutable": false,
|
|
686
|
+
"description": "Always show only the prefix icon; retain an accessible name via aria-label."
|
|
687
|
+
},
|
|
662
688
|
"indicator": {
|
|
663
689
|
"type": "SelectIndicator",
|
|
664
|
-
"resolvedType": "\"down\" | \"up-down\"",
|
|
690
|
+
"resolvedType": "\"down\" | \"none\" | \"up-down\"",
|
|
665
691
|
"attribute": "indicator",
|
|
666
692
|
"default": "'down'",
|
|
667
693
|
"required": false,
|
|
@@ -720,6 +746,15 @@
|
|
|
720
746
|
"mutable": false,
|
|
721
747
|
"description": "Native form field name."
|
|
722
748
|
},
|
|
749
|
+
"neutralTrigger": {
|
|
750
|
+
"type": "boolean",
|
|
751
|
+
"resolvedType": "boolean",
|
|
752
|
+
"attribute": "neutral-trigger",
|
|
753
|
+
"default": "false",
|
|
754
|
+
"required": false,
|
|
755
|
+
"mutable": false,
|
|
756
|
+
"description": "Keep trigger colors neutral when a value is selected."
|
|
757
|
+
},
|
|
723
758
|
"noResultsText": {
|
|
724
759
|
"type": "string",
|
|
725
760
|
"resolvedType": "string",
|
|
@@ -978,7 +1013,7 @@
|
|
|
978
1013
|
},
|
|
979
1014
|
{
|
|
980
1015
|
"path": "src/wc/components/Select/Select.tsx",
|
|
981
|
-
"content": "import {\n AttachInternals,\n Component,\n Element,\n Event,\n EventEmitter,\n h,\n Host,\n Method,\n Prop,\n State,\n type VNode,\n Watch,\n} from '@stencil/core';\nimport {\n controlWidthClass,\n CONTROL_SUPPORTING_TEXT_VARIANT,\n CONTROL_TEXT_VARIANT,\n DEFAULT_REQUIRED_MESSAGE,\n restoreStringArrayFormState,\n restoreStringFormState,\n setFormControlValue,\n setRepeatedFormControlValue,\n setRequiredValidity,\n type ControlWidth,\n} from '../../utils';\nimport {\n ChoiceActionFooter,\n ChoiceFooter,\n ChoiceOptionRow,\n ChoiceSearch,\n} from '../../utils/choice-list-parts';\nimport {\n choiceBackgroundClassMap,\n choiceListUsesIcons,\n choiceListUsesSubtext,\n enabledChoiceIndexes,\n filterChoiceSections,\n flattenChoiceSections,\n resolveChoiceSections,\n type ChoiceBackground,\n type ChoiceOption,\n type ChoiceSection,\n} from '../../utils/choice-list';\nimport { SelectController } from '../../utils/select-controller';\nimport { observeTableCaptionCompact } from '../../utils/table-caption-compact';\n\nexport type SelectOption = ChoiceOption;\nexport type SelectSection = ChoiceSection;\nexport type SelectBackground = ChoiceBackground;\nexport type SelectSize = 'lg' | 'md' | 'sm' | 'xs';\nexport type SelectWidth = ControlWidth;\nexport type SelectPopupAlign = 'start' | 'end';\nexport type SelectValue = string | string[];\nexport type SelectIndicator = 'down' | 'up-down';\n\nexport interface SelectOptionActionDetail {\n value: string;\n anchorId: string;\n originalEvent: MouseEvent;\n}\n\nexport interface SelectOptionSubtextActionDetail {\n value: string;\n actionValue: string;\n originalEvent: MouseEvent;\n}\n\nconst ICON_SIZE: Record<SelectSize, 'lg' | 'md' | 'sm' | 'xs'> = {\n lg: 'lg',\n md: 'md',\n sm: 'sm',\n xs: 'xs',\n};\n\nconst OPTION_ACTION_SIZE: Record<SelectSize, 'md' | 'sm' | 'xs'> = {\n lg: 'md',\n md: 'sm',\n sm: 'xs',\n xs: 'xs',\n};\n\nlet selectId = 0;\n\n@Component({\n tag: 'ds-select',\n styleUrl: 'Select.css',\n scoped: true,\n formAssociated: true,\n})\nexport class Select {\n @Element() el!: HTMLElement;\n @AttachInternals() internals!: ElementInternals;\n\n /** Flat choices. Assign arrays through the JavaScript property. */\n @Prop() options: SelectOption[] = [];\n /** Grouped choices; takes precedence over options. Assign through JavaScript. */\n @Prop() sections: SelectSection[] = [];\n /** Enable independent multi-value selection while keeping the popup open. */\n @Prop({ reflect: true }) multiple: boolean = false;\n /** Selected scalar or array value according to `multiple`. */\n @Prop({ mutable: true }) value: SelectValue = '';\n /** Controlled popup visibility. */\n @Prop({ mutable: true, reflect: true }) open: boolean = false;\n /** Native form field name. */\n @Prop({ reflect: true }) name: string | undefined;\n /** Native disabled state. */\n @Prop({ reflect: true }) disabled: boolean = false;\n /** Require 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 /** Trigger text shown when no valid value is selected. */\n @Prop() placeholder: string = 'Select';\n /** Optional scalar-mode trigger text that does not change the selected option label. */\n @Prop() triggerLabel: string | undefined;\n /** Present triggerLabel with placeholder emphasis while preserving the selected value. */\n @Prop() triggerLabelPlaceholder: boolean = false;\n /** Show a supplemental notification dot beside the trigger label. */\n @Prop() dot: boolean = false;\n /** Control density. */\n @Prop() size: SelectSize = 'md';\n /** Width fit — hug content (default) or fill the parent. */\n @Prop() width: SelectWidth = 'hug';\n /** Align the popup's choice edge to the trigger start or end edge. */\n @Prop() popupAlign: SelectPopupAlign = 'start';\n /** Explicit collision owner; otherwise the nearest data-ds-overlay-boundary ancestor is used. */\n @Prop() boundary: HTMLElement | undefined;\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 a valid value exists. */\n @Prop() activeFill: boolean = false;\n /**\n * Opt into table-caption icon-only chrome below 900px. The trigger omits its\n * visible label and chevron; keep an accessible name via aria-label.\n */\n @Prop({ reflect: true }) collapseLabel: boolean = false;\n /** Show the surface-aware inset border, including focused and invalid strokes. */\n @Prop() hasBorder: boolean = true;\n /** Optional trigger prefix icon name. */\n @Prop() icon: string | undefined;\n /** Trailing choice indicator. Use up-down for compact value steppers such as page size. */\n @Prop() indicator: SelectIndicator = 'down';\n /** Show the clear footer action when a value exists. */\n @Prop() allowClear: boolean = true;\n /** Localized clear action label. */\n @Prop() clearLabel: string = 'Clear';\n /** Localized noun displayed after the selected count in multiple mode. */\n @Prop() selectedLabel: string = 'selected';\n /** Optional text action shown in the popup footer instead of the clear action. */\n @Prop() footerActionLabel: string | undefined;\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: SelectBackground | 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 selection or clearing with the next scalar or array value. */\n @Event() dsChange!: EventEmitter<SelectValue>;\n /** Emitted after the footer clear action. */\n @Event() dsClear!: EventEmitter<void>;\n /** Emitted whenever popup visibility changes. */\n @Event() dsOpenChange!: EventEmitter<boolean>;\n /** Emitted when the optional popup footer action is activated. */\n @Event() dsFooterAction!: EventEmitter<void>;\n /** Emitted when an option's contextual ellipsis action is activated. */\n @Event() dsOptionAction!: EventEmitter<SelectOptionActionDetail>;\n /** Emitted when an option's supporting text action is activated. */\n @Event() dsOptionSubtextAction!: EventEmitter<SelectOptionSubtextActionDetail>;\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 @State() private compactFooterSummary = false;\n @State() private captionCompact = false;\n\n private readonly generatedId = `ds-select-${++selectId}`;\n private readonly listboxId = `${this.generatedId}-listbox`;\n private readonly errorId = `${this.generatedId}-error`;\n private initialValue: SelectValue = '';\n private readonly controller = this.createController();\n private footerContentElement?: HTMLDivElement;\n private footerSummaryMeasureElement?: HTMLElement;\n private footerClearElement?: HTMLButtonElement;\n private footerResizeObserver?: ResizeObserver;\n private observedFooterContentElement?: HTMLDivElement;\n private footerMeasurementFrame?: number;\n private captionCompactDisconnect: (() => void) | undefined;\n private hasLoaded = false;\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<SelectOption>({\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 if (owner.multiple) return -1;\n return owner.visibleOptions.findIndex(\n option => option.value === owner.scalarValue && !option.isInactive\n );\n },\n get popupAlign() {\n return owner.popupAlign;\n },\n get boundary() {\n return owner.boundary;\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.selectOption(option),\n });\n }\n\n componentWillLoad() {\n this.value = this.normalizeValueForMode(this.value);\n this.initialValue = Array.isArray(this.value) ? [...this.value] : this.value;\n this.syncFormValue();\n }\n\n componentDidLoad() {\n this.hasLoaded = true;\n this.controller.connect();\n this.observeFooterContent();\n this.syncCaptionCompactObserver();\n }\n\n connectedCallback() {\n if (!this.hasLoaded) return;\n this.controller.connect();\n this.observeFooterContent();\n this.syncCaptionCompactObserver();\n }\n\n componentDidRender() {\n this.observeFooterContent();\n this.scheduleFooterSummaryMeasurement();\n }\n\n disconnectedCallback() {\n this.controller.disconnect();\n this.footerResizeObserver?.disconnect();\n this.observedFooterContentElement = undefined;\n if (this.footerMeasurementFrame !== undefined) {\n cancelAnimationFrame(this.footerMeasurementFrame);\n this.footerMeasurementFrame = undefined;\n }\n this.disconnectCaptionCompactObserver();\n }\n\n @Watch('collapseLabel')\n onCollapseLabelChange() {\n this.syncCaptionCompactObserver();\n }\n\n @Watch('value')\n @Watch('multiple')\n @Watch('disabled')\n @Watch('isInactive')\n @Watch('required')\n @Watch('name')\n @Watch('options')\n @Watch('sections')\n syncFormValue() {\n if (\n (this.multiple && !Array.isArray(this.value)) ||\n (!this.multiple && Array.isArray(this.value))\n ) {\n this.value = this.normalizeValueForMode(this.value);\n }\n const inactive = this.isDisabled;\n if (this.multiple) {\n const values = this.resolvedValues;\n setRepeatedFormControlValue(this.internals, this.name, values, { inactive });\n } else {\n const value = this.hasSelection ? this.scalarValue : '';\n setFormControlValue(this.internals, value, { inactive });\n }\n const missing = this.required && !inactive && !this.hasSelection;\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('popupAlign')\n @Watch('boundary')\n onPopupAlignChange() {\n this.controller.positionChanged();\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.value = this.normalizeValueForMode(this.initialValue);\n this.closePopup();\n }\n\n formStateRestoreCallback(state: string | File | FormData | null) {\n if (!this.multiple) {\n this.value = restoreStringFormState(state);\n return;\n }\n this.value = restoreStringArrayFormState(state);\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 normalizeValueForMode(value: SelectValue): SelectValue {\n if (this.multiple) {\n return Array.isArray(value) ? [...value] : value ? [value] : [];\n }\n return Array.isArray(value) ? (value[0] ?? '') : value;\n }\n\n private get allSections(): SelectSection[] {\n return resolveChoiceSections(this.options, this.sections);\n }\n\n private get allOptions(): SelectOption[] {\n return flattenChoiceSections(this.allSections);\n }\n\n private get visibleSections(): SelectSection[] {\n return this.searchable\n ? filterChoiceSections(this.allSections, this.searchTerm)\n : this.allSections;\n }\n\n private get visibleOptions(): SelectOption[] {\n return flattenChoiceSections(this.visibleSections);\n }\n\n private get scalarValue(): string {\n return typeof this.value === 'string' ? this.value : '';\n }\n\n private get resolvedValues(): string[] {\n if (!this.multiple || !Array.isArray(this.value)) return [];\n const valid = new Set(this.allOptions.map(option => option.value));\n return [...new Set(this.value.filter(value => valid.has(value)))];\n }\n\n private get selectedOption(): SelectOption | undefined {\n if (this.multiple) return undefined;\n return this.allOptions.find(option => option.value === this.scalarValue);\n }\n\n private get hasSelection(): boolean {\n return this.multiple ? this.resolvedValues.length > 0 : Boolean(this.selectedOption);\n }\n\n private get activeOptionId(): string | undefined {\n return this.controller.activeOptionId;\n }\n\n private get popupRole(): 'listbox' | 'grid' {\n return this.allOptions.some(option => Boolean(option.action)) ? 'grid' : 'listbox';\n }\n\n private observeFooterContent() {\n const content =\n this.open && this.multiple && this.hasSelection ? this.footerContentElement : undefined;\n if (!content?.isConnected) {\n this.footerResizeObserver?.disconnect();\n this.observedFooterContentElement = undefined;\n return;\n }\n if (this.observedFooterContentElement === content) return;\n\n this.footerResizeObserver?.disconnect();\n if (typeof ResizeObserver !== 'undefined') {\n this.footerResizeObserver = new ResizeObserver(() => this.scheduleFooterSummaryMeasurement());\n this.footerResizeObserver.observe(content);\n }\n this.observedFooterContentElement = content;\n }\n\n private scheduleFooterSummaryMeasurement() {\n if (\n !this.open ||\n !this.multiple ||\n !this.hasSelection ||\n this.footerMeasurementFrame !== undefined\n ) {\n return;\n }\n this.footerMeasurementFrame = requestAnimationFrame(() => {\n this.footerMeasurementFrame = undefined;\n this.measureFooterSummary();\n });\n }\n\n private measureFooterSummary() {\n const content = this.footerContentElement;\n const summary = this.footerSummaryMeasureElement;\n const clear = this.footerClearElement;\n if (!content?.isConnected || !summary?.isConnected || !clear?.isConnected) return;\n\n const styles = getComputedStyle(content);\n const innerWidth =\n content.clientWidth -\n Number.parseFloat(styles.paddingInlineStart || '0') -\n Number.parseFloat(styles.paddingInlineEnd || '0');\n const requiredWidth =\n summary.getBoundingClientRect().width +\n clear.getBoundingClientRect().width +\n Number.parseFloat(styles.columnGap || '0');\n const nextCompact = requiredWidth > innerWidth + 0.5;\n if (nextCompact !== this.compactFooterSummary) {\n this.compactFooterSummary = nextCompact;\n }\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 selectOption(option: SelectOption) {\n if (option.isInactive || this.isLoading) return;\n if (this.multiple) {\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.value = next;\n this.dsChange.emit([...next]);\n return;\n }\n this.value = option.value;\n this.dsChange.emit(option.value);\n this.controller.closePopup(true);\n }\n\n private clearSelection = (event: Event) => {\n event.preventDefault();\n event.stopPropagation();\n this.value = this.multiple ? [] : '';\n this.dsChange.emit(this.multiple ? [] : '');\n this.dsClear.emit();\n if (!this.multiple) {\n const enabled = enabledChoiceIndexes(this.visibleOptions);\n this.activeIndex = enabled[0] ?? -1;\n }\n this.controller.focusSearchOrTrigger();\n };\n\n private activateFooterAction = (event: MouseEvent) => {\n event.preventDefault();\n event.stopPropagation();\n this.dsFooterAction.emit();\n this.closePopup();\n };\n\n private readonly handleListKeyDown = this.controller.handleListKeyDown;\n private readonly handleTriggerKeyDown = this.controller.handleTriggerKeyDown;\n\n private renderOption(\n option: SelectOption,\n index: number,\n usesIcons: boolean,\n usesSubtext: boolean\n ) {\n const selected = this.multiple\n ? this.resolvedValues.includes(option.value)\n : option.value === this.scalarValue;\n const active = index === this.activeIndex;\n const actionId = `${this.generatedId}-option-action-${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 actionOpen={option.action?.surfaceOpen ?? option.action?.expanded}\n popupRole={this.popupRole}\n leading={\n this.multiple ? (\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 ) : usesIcons && option.icon ? (\n <span\n class=\"ds-choice-item__icon ds-control-icon-box ds-interaction-fill__content\"\n aria-hidden=\"true\"\n >\n <ds-icon name={option.icon} size={this.size} color=\"inherit\" />\n </span>\n ) : undefined\n }\n supporting={\n option.subtextActions?.length ? (\n <span class=\"ds-choice-item__subtext ds-control-label-box select-option__subtext-actions\">\n {option.subtextActions.map((action, actionIndex) => [\n actionIndex > 0 ? (\n <ds-text\n class=\"select-option__subtext-action-separator\"\n as=\"span\"\n variant={CONTROL_SUPPORTING_TEXT_VARIANT[this.size]}\n color=\"inherit\"\n aria-hidden=\"true\"\n >\n ·\n </ds-text>\n ) : null,\n <button\n type=\"button\"\n class={{\n 'select-option__subtext-action': true,\n 'select-option__subtext-action--negative': action.tone === 'negative',\n 'ds-text-action': true,\n 'ds-focus-ring': true,\n }}\n onKeyDown={(event: KeyboardEvent) => event.stopPropagation()}\n onClick={(event: MouseEvent) => {\n event.preventDefault();\n event.stopPropagation();\n this.dsOptionSubtextAction.emit({\n value: option.value,\n actionValue: action.value,\n originalEvent: event,\n });\n }}\n >\n <ds-text\n as=\"span\"\n variant={CONTROL_SUPPORTING_TEXT_VARIANT[this.size]}\n color=\"inherit\"\n >\n {action.label}\n </ds-text>\n </button>,\n ])}\n </span>\n ) : undefined\n }\n action={\n option.action ? (\n <ds-tooltip label={option.action.label} side=\"left\" size=\"sm\">\n <ds-button-unfilled\n id={actionId}\n variant=\"icon\"\n size={OPTION_ACTION_SIZE[this.size]}\n icon=\"Ellipses\"\n rounded\n hasBorder={false}\n activeFill={false}\n ariaLabel={option.action.label}\n haspopup=\"menu\"\n controls={option.action.controls}\n expanded={option.action.expanded}\n surfaceOpen={option.action.surfaceOpen}\n onKeyDown={event => {\n event.stopPropagation();\n if (event.key === 'Escape' || event.key === 'ArrowLeft') {\n event.preventDefault();\n this.controller.focusSearchOrTrigger();\n }\n }}\n onDsClick={(event: CustomEvent<MouseEvent>) => {\n event.stopPropagation();\n this.dsOptionAction.emit({\n value: option.value,\n anchorId: actionId,\n originalEvent: event.detail,\n });\n }}\n />\n </ds-tooltip>\n ) : undefined\n }\n onHover={() => {\n this.focusRingVisible = false;\n this.activeIndex = index;\n }}\n onSelect={() => this.selectOption(option)}\n />\n );\n }\n\n private get captionIconOnly(): boolean {\n return this.collapseLabel && this.captionCompact && Boolean(this.icon || this.isLoading);\n }\n\n private syncCaptionCompactObserver(): void {\n this.disconnectCaptionCompactObserver();\n if (!this.collapseLabel) {\n if (this.captionCompact) this.captionCompact = false;\n return;\n }\n this.captionCompactDisconnect = observeTableCaptionCompact(this.el, compact => {\n if (this.captionCompact !== compact) this.captionCompact = compact;\n });\n }\n\n private disconnectCaptionCompactObserver(): void {\n this.captionCompactDisconnect?.();\n this.captionCompactDisconnect = undefined;\n }\n\n private renderCaptionTooltip(trigger: VNode, label: string): VNode {\n if (!this.collapseLabel) return trigger;\n return (\n <ds-tooltip label={label} side=\"top\" size=\"sm\">\n {trigger}\n </ds-tooltip>\n );\n }\n\n render() {\n const inactive = this.isDisabled;\n const showPlaceholder = !this.hasSelection;\n const count = this.resolvedValues.length;\n const label = this.multiple\n ? `${this.placeholder}${count > 0 ? ` · ${count}` : ''}`\n : showPlaceholder\n ? this.placeholder\n : (this.triggerLabel ?? this.selectedOption?.label);\n const textVariant = CONTROL_TEXT_VARIANT[this.size];\n const iconSize = ICON_SIZE[this.size];\n const usesOptionIcons = !this.multiple && choiceListUsesIcons(this.allOptions);\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-select-trigger-host': true,\n 'select-host--multiple': this.multiple,\n 'ds-field-stack': true,\n 'ds-control-inactive': inactive,\n [`ds-control--${this.size}`]: true,\n 'ds-table-caption-control': this.collapseLabel,\n 'ds-table-caption-control--compact': this.captionIconOnly,\n ...controlWidthClass(this.width),\n [`select-host--background-${this.background}`]: !!this.background,\n [`ds-select-trigger-host--background-${this.background}`]: !!this.background,\n }}\n >\n {this.renderCaptionTooltip(\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--expanded': !inactive && this.open,\n 'ds-interaction-fill--surface-open': !inactive && this.open,\n 'trigger--bordered': this.hasBorder,\n 'trigger--placeholder': showPlaceholder && !this.multiple,\n 'trigger--has-value': this.hasSelection,\n 'trigger--label-placeholder':\n !this.multiple && Boolean(this.triggerLabel) && this.triggerLabelPlaceholder,\n 'wrapper--error': this.hasBorder && this.error,\n [`ds-control--${this.size}`]: true,\n ...choiceBackgroundClassMap(this.background),\n }}\n disabled={inactive}\n role=\"combobox\"\n aria-haspopup={this.popupRole}\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 ?? (this.captionIconOnly ? label : undefined)}\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={event => {\n const action = this.visibleOptions[this.activeIndex]?.action;\n if (\n this.open &&\n action &&\n (event.key === 'ArrowRight' || (event.key === 'F10' && event.shiftKey))\n ) {\n event.preventDefault();\n document\n .getElementById(`${this.generatedId}-option-action-${this.activeIndex}`)\n ?.querySelector<HTMLElement>('button')\n ?.focus();\n return;\n }\n this.handleTriggerKeyDown(event);\n }}\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 {this.captionIconOnly ? null : (\n <span\n class={{\n 'trigger__label-box': true,\n 'ds-interaction-fill__content': true,\n }}\n >\n <span\n class={{\n 'trigger__label-content': true,\n 'ds-control-label-box': true,\n 'ds-control-label-dot': this.dot,\n 'trigger__label-content--dot': this.dot,\n }}\n >\n <ds-text\n class=\"trigger__label\"\n as=\"span\"\n variant={textVariant}\n color=\"inherit\"\n lineTruncation={1}\n >\n {label}\n </ds-text>\n {this.dot && (\n <ds-badge\n class=\"trigger__dot ds-control-label-dot__badge\"\n variant=\"dot\"\n hasRing={false}\n label=\"\"\n aria-hidden=\"true\"\n />\n )}\n </span>\n </span>\n )}\n {this.captionIconOnly ? null : (\n <span\n class=\"trigger__chevron ds-control-icon-box ds-interaction-fill__content\"\n aria-hidden=\"true\"\n >\n <ds-icon\n name={this.indicator === 'up-down' ? 'ChevronUpDown' : 'ChevronDown'}\n size={iconSize}\n color=\"inherit\"\n />\n </span>\n )}\n </button>,\n this.captionIconOnly ? this.ariaLabel?.trim() || this.placeholder : ''\n )}\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={this.popupRole}\n aria-multiselectable={this.multiple ? 'true' : undefined}\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 ds-empty-region\"\n role={this.popupRole === 'grid' ? 'row' : 'option'}\n aria-selected=\"false\"\n aria-disabled=\"true\"\n aria-label={this.loadingLabel}\n aria-live=\"polite\"\n >\n {this.popupRole === 'grid' ? (\n <span role=\"gridcell\">\n <ds-loader size={this.size} color=\"inherit\" />\n </span>\n ) : (\n <ds-loader size={this.size} color=\"inherit\" />\n )}\n </div>\n ) : this.visibleOptions.length === 0 ? (\n <div\n class=\"ds-choice-empty ds-empty-region\"\n role={this.popupRole === 'grid' ? 'row' : 'option'}\n aria-selected=\"false\"\n aria-disabled=\"true\"\n aria-label={this.noResultsText}\n aria-live=\"polite\"\n >\n {this.popupRole === 'grid' ? (\n <span role=\"gridcell\">\n <ds-empty-state body={this.noResultsText} />\n </span>\n ) : (\n <ds-empty-state body={this.noResultsText} />\n )}\n </div>\n ) : (\n this.visibleSections.map((section, sectionIndex) => (\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 'ds-choice-section--headed-after-first':\n sectionIndex > 0 && Boolean(section.header),\n }}\n role={\n this.popupRole === 'grid' ? 'rowgroup' : section.header ? 'group' : undefined\n }\n aria-label={section.header}\n >\n {section.header && this.popupRole === 'grid' ? (\n <ds-text\n class={`ds-choice-section__header ds-control-section-heading ds-control--${this.size}`}\n as=\"span\"\n variant={CONTROL_SUPPORTING_TEXT_VARIANT[this.size]}\n emphasis\n color=\"primary\"\n role=\"row\"\n >\n <span class=\"ds-choice-section__header-label\" role=\"gridcell\">\n {section.header}\n </span>\n </ds-text>\n ) : section.header ? (\n <ds-text\n class={`ds-choice-section__header ds-control-section-heading 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 <span class=\"ds-choice-section__header-label\">{section.header}</span>\n </ds-text>\n ) : null}\n {section.options.map(option =>\n this.renderOption(option, flatIndex++, usesOptionIcons, usesOptionSubtext)\n )}\n </div>\n ))\n )}\n </div>\n {this.footerActionLabel && !this.isLoading ? (\n <ChoiceActionFooter\n size={this.size}\n label={this.footerActionLabel}\n onAction={this.activateFooterAction}\n />\n ) : this.allowClear && this.hasSelection && !this.isLoading ? (\n <ChoiceFooter\n size={this.size}\n summary={this.multiple ? `${count} ${this.selectedLabel}` : undefined}\n compactSummary={this.multiple ? String(count) : undefined}\n useCompactSummary={this.compactFooterSummary}\n contentRef={element => {\n this.footerContentElement = element ?? undefined;\n }}\n summaryMeasureRef={element => {\n this.footerSummaryMeasureElement = element ?? undefined;\n }}\n clearRef={element => {\n this.footerClearElement = element ?? undefined;\n }}\n clearLabel={this.clearLabel}\n onClear={this.clearSelection}\n />\n ) : null}\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",
|
|
1016
|
+
"content": "import {\n AttachInternals,\n Component,\n Element,\n Event,\n EventEmitter,\n h,\n Host,\n Method,\n Prop,\n State,\n type VNode,\n Watch,\n} from '@stencil/core';\nimport {\n controlWidthClass,\n CONTROL_SUPPORTING_TEXT_VARIANT,\n CONTROL_TEXT_VARIANT,\n DEFAULT_REQUIRED_MESSAGE,\n restoreStringArrayFormState,\n restoreStringFormState,\n setFormControlValue,\n setRepeatedFormControlValue,\n setRequiredValidity,\n type ControlWidth,\n} from '../../utils';\nimport {\n ChoiceActionFooter,\n ChoiceFooter,\n ChoiceOptionRow,\n ChoiceSearch,\n} from '../../utils/choice-list-parts';\nimport {\n choiceBackgroundClassMap,\n choiceListUsesIcons,\n choiceListUsesSubtext,\n enabledChoiceIndexes,\n filterChoiceSections,\n flattenChoiceSections,\n resolveChoiceSections,\n type ChoiceBackground,\n type ChoiceOption,\n type ChoiceSection,\n} from '../../utils/choice-list';\nimport { SelectController } from '../../utils/select-controller';\nimport { observeTableCaptionCompact } from '../../utils/table-caption-compact';\n\nexport type SelectOption = ChoiceOption;\nexport type SelectSection = ChoiceSection;\nexport type SelectBackground = ChoiceBackground;\nexport type SelectSize = 'lg' | 'md' | 'sm' | 'xs';\nexport type SelectWidth = ControlWidth;\nexport type SelectPopupAlign = 'start' | 'end';\nexport type SelectValue = string | string[];\nexport type SelectIndicator = 'down' | 'up-down' | 'none';\n\nexport interface SelectOptionActionDetail {\n value: string;\n anchorId: string;\n originalEvent: MouseEvent;\n}\n\nexport interface SelectOptionSubtextActionDetail {\n value: string;\n actionValue: string;\n originalEvent: MouseEvent;\n}\n\nconst ICON_SIZE: Record<SelectSize, 'lg' | 'md' | 'sm' | 'xs'> = {\n lg: 'lg',\n md: 'md',\n sm: 'sm',\n xs: 'xs',\n};\n\nconst OPTION_ACTION_SIZE: Record<SelectSize, 'md' | 'sm' | 'xs'> = {\n lg: 'md',\n md: 'sm',\n sm: 'xs',\n xs: 'xs',\n};\n\nlet selectId = 0;\n\n@Component({\n tag: 'ds-select',\n styleUrl: 'Select.css',\n scoped: true,\n formAssociated: true,\n})\nexport class Select {\n @Element() el!: HTMLElement;\n @AttachInternals() internals!: ElementInternals;\n\n /** Flat choices. Assign arrays through the JavaScript property. */\n @Prop() options: SelectOption[] = [];\n /** Grouped choices; takes precedence over options. Assign through JavaScript. */\n @Prop() sections: SelectSection[] = [];\n /** Enable independent multi-value selection while keeping the popup open. */\n @Prop({ reflect: true }) multiple: boolean = false;\n /** Selected scalar or array value according to `multiple`. */\n @Prop({ mutable: true }) value: SelectValue = '';\n /** Controlled popup visibility. */\n @Prop({ mutable: true, reflect: true }) open: boolean = false;\n /** Native form field name. */\n @Prop({ reflect: true }) name: string | undefined;\n /** Native disabled state. */\n @Prop({ reflect: true }) disabled: boolean = false;\n /** Require 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 /** Trigger text shown when no valid value is selected. */\n @Prop() placeholder: string = 'Select';\n /** Optional scalar-mode trigger text that does not change the selected option label. */\n @Prop() triggerLabel: string | undefined;\n /** Present triggerLabel with placeholder emphasis while preserving the selected value. */\n @Prop() triggerLabelPlaceholder: boolean = false;\n /** Show a supplemental notification dot beside the trigger label. */\n @Prop() dot: boolean = false;\n /** Control density. */\n @Prop() size: SelectSize = 'md';\n /** Width fit — hug content (default) or fill the parent. */\n @Prop() width: SelectWidth = 'hug';\n /** Align the popup's choice edge to the trigger start or end edge. */\n @Prop() popupAlign: SelectPopupAlign = 'start';\n /** Explicit collision owner; otherwise the nearest data-ds-overlay-boundary ancestor is used. */\n @Prop() boundary: HTMLElement | undefined;\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 a valid value exists. */\n /** Keep trigger colors neutral when a value is selected. */\n @Prop() neutralTrigger: boolean = false;\n @Prop() activeFill: boolean = false;\n /**\n * Opt into table-caption icon-only chrome below 900px. The trigger omits its\n * visible label and chevron; keep an accessible name via aria-label.\n */\n @Prop({ reflect: true }) collapseLabel: boolean = false;\n /** Always show only the prefix icon; retain an accessible name via aria-label. */\n @Prop() iconOnly: boolean = false;\n /** Show the surface-aware inset border, including focused and invalid strokes. */\n @Prop() hasBorder: boolean = true;\n /** Optional trigger prefix icon name. */\n @Prop() icon: string | undefined;\n /** Trailing choice indicator. Use up-down for compact value steppers such as page size. */\n @Prop() indicator: SelectIndicator = 'down';\n /** Show the clear footer action when a value exists. */\n @Prop() allowClear: boolean = true;\n /** Localized clear action label. */\n @Prop() clearLabel: string = 'Clear';\n /** Localized noun displayed after the selected count in multiple mode. */\n @Prop() selectedLabel: string = 'selected';\n /** Optional text action shown in the popup footer instead of the clear action. */\n @Prop() footerActionLabel: string | undefined;\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: SelectBackground | 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 selection or clearing with the next scalar or array value. */\n @Event() dsChange!: EventEmitter<SelectValue>;\n /** Emitted after the footer clear action. */\n @Event() dsClear!: EventEmitter<void>;\n /** Emitted whenever popup visibility changes. */\n @Event() dsOpenChange!: EventEmitter<boolean>;\n /** Emitted when the optional popup footer action is activated. */\n @Event() dsFooterAction!: EventEmitter<void>;\n /** Emitted when an option's contextual ellipsis action is activated. */\n @Event() dsOptionAction!: EventEmitter<SelectOptionActionDetail>;\n /** Emitted when an option's supporting text action is activated. */\n @Event() dsOptionSubtextAction!: EventEmitter<SelectOptionSubtextActionDetail>;\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 @State() private compactFooterSummary = false;\n @State() private captionCompact = false;\n\n private readonly generatedId = `ds-select-${++selectId}`;\n private readonly listboxId = `${this.generatedId}-listbox`;\n private readonly errorId = `${this.generatedId}-error`;\n private initialValue: SelectValue = '';\n private readonly controller = this.createController();\n private footerContentElement?: HTMLDivElement;\n private footerSummaryMeasureElement?: HTMLElement;\n private footerClearElement?: HTMLButtonElement;\n private footerResizeObserver?: ResizeObserver;\n private observedFooterContentElement?: HTMLDivElement;\n private footerMeasurementFrame?: number;\n private captionCompactDisconnect: (() => void) | undefined;\n private hasLoaded = false;\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<SelectOption>({\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 if (owner.multiple) return -1;\n return owner.visibleOptions.findIndex(\n option => option.value === owner.scalarValue && !option.isInactive\n );\n },\n get popupAlign() {\n return owner.popupAlign;\n },\n get boundary() {\n return owner.boundary;\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.selectOption(option),\n });\n }\n\n componentWillLoad() {\n this.value = this.normalizeValueForMode(this.value);\n this.initialValue = Array.isArray(this.value) ? [...this.value] : this.value;\n this.syncFormValue();\n }\n\n componentDidLoad() {\n this.hasLoaded = true;\n this.controller.connect();\n this.observeFooterContent();\n this.syncCaptionCompactObserver();\n }\n\n connectedCallback() {\n if (!this.hasLoaded) return;\n this.controller.connect();\n this.observeFooterContent();\n this.syncCaptionCompactObserver();\n }\n\n componentDidRender() {\n this.observeFooterContent();\n this.scheduleFooterSummaryMeasurement();\n }\n\n disconnectedCallback() {\n this.controller.disconnect();\n this.footerResizeObserver?.disconnect();\n this.observedFooterContentElement = undefined;\n if (this.footerMeasurementFrame !== undefined) {\n cancelAnimationFrame(this.footerMeasurementFrame);\n this.footerMeasurementFrame = undefined;\n }\n this.disconnectCaptionCompactObserver();\n }\n\n @Watch('collapseLabel')\n onCollapseLabelChange() {\n this.syncCaptionCompactObserver();\n }\n\n @Watch('value')\n @Watch('multiple')\n @Watch('disabled')\n @Watch('isInactive')\n @Watch('required')\n @Watch('name')\n @Watch('options')\n @Watch('sections')\n syncFormValue() {\n if (\n (this.multiple && !Array.isArray(this.value)) ||\n (!this.multiple && Array.isArray(this.value))\n ) {\n this.value = this.normalizeValueForMode(this.value);\n }\n const inactive = this.isDisabled;\n if (this.multiple) {\n const values = this.resolvedValues;\n setRepeatedFormControlValue(this.internals, this.name, values, { inactive });\n } else {\n const value = this.hasSelection ? this.scalarValue : '';\n setFormControlValue(this.internals, value, { inactive });\n }\n const missing = this.required && !inactive && !this.hasSelection;\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('popupAlign')\n @Watch('boundary')\n onPopupAlignChange() {\n this.controller.positionChanged();\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.value = this.normalizeValueForMode(this.initialValue);\n this.closePopup();\n }\n\n formStateRestoreCallback(state: string | File | FormData | null) {\n if (!this.multiple) {\n this.value = restoreStringFormState(state);\n return;\n }\n this.value = restoreStringArrayFormState(state);\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 normalizeValueForMode(value: SelectValue): SelectValue {\n if (this.multiple) {\n return Array.isArray(value) ? [...value] : value ? [value] : [];\n }\n return Array.isArray(value) ? (value[0] ?? '') : value;\n }\n\n private get allSections(): SelectSection[] {\n return resolveChoiceSections(this.options, this.sections);\n }\n\n private get allOptions(): SelectOption[] {\n return flattenChoiceSections(this.allSections);\n }\n\n private get visibleSections(): SelectSection[] {\n return this.searchable\n ? filterChoiceSections(this.allSections, this.searchTerm)\n : this.allSections;\n }\n\n private get visibleOptions(): SelectOption[] {\n return flattenChoiceSections(this.visibleSections);\n }\n\n private get scalarValue(): string {\n return typeof this.value === 'string' ? this.value : '';\n }\n\n private get resolvedValues(): string[] {\n if (!this.multiple || !Array.isArray(this.value)) return [];\n const valid = new Set(this.allOptions.map(option => option.value));\n return [...new Set(this.value.filter(value => valid.has(value)))];\n }\n\n private get selectedOption(): SelectOption | undefined {\n if (this.multiple) return undefined;\n return this.allOptions.find(option => option.value === this.scalarValue);\n }\n\n private get hasSelection(): boolean {\n return this.multiple ? this.resolvedValues.length > 0 : Boolean(this.selectedOption);\n }\n\n private get activeOptionId(): string | undefined {\n return this.controller.activeOptionId;\n }\n\n private get popupRole(): 'listbox' | 'grid' {\n return this.allOptions.some(option => Boolean(option.action)) ? 'grid' : 'listbox';\n }\n\n private observeFooterContent() {\n const content =\n this.open && this.multiple && this.hasSelection ? this.footerContentElement : undefined;\n if (!content?.isConnected) {\n this.footerResizeObserver?.disconnect();\n this.observedFooterContentElement = undefined;\n return;\n }\n if (this.observedFooterContentElement === content) return;\n\n this.footerResizeObserver?.disconnect();\n if (typeof ResizeObserver !== 'undefined') {\n this.footerResizeObserver = new ResizeObserver(() => this.scheduleFooterSummaryMeasurement());\n this.footerResizeObserver.observe(content);\n }\n this.observedFooterContentElement = content;\n }\n\n private scheduleFooterSummaryMeasurement() {\n if (\n !this.open ||\n !this.multiple ||\n !this.hasSelection ||\n this.footerMeasurementFrame !== undefined\n ) {\n return;\n }\n this.footerMeasurementFrame = requestAnimationFrame(() => {\n this.footerMeasurementFrame = undefined;\n this.measureFooterSummary();\n });\n }\n\n private measureFooterSummary() {\n const content = this.footerContentElement;\n const summary = this.footerSummaryMeasureElement;\n const clear = this.footerClearElement;\n if (!content?.isConnected || !summary?.isConnected || !clear?.isConnected) return;\n\n const styles = getComputedStyle(content);\n const innerWidth =\n content.clientWidth -\n Number.parseFloat(styles.paddingInlineStart || '0') -\n Number.parseFloat(styles.paddingInlineEnd || '0');\n const requiredWidth =\n summary.getBoundingClientRect().width +\n clear.getBoundingClientRect().width +\n Number.parseFloat(styles.columnGap || '0');\n const nextCompact = requiredWidth > innerWidth + 0.5;\n if (nextCompact !== this.compactFooterSummary) {\n this.compactFooterSummary = nextCompact;\n }\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 selectOption(option: SelectOption) {\n if (option.isInactive || this.isLoading) return;\n if (this.multiple) {\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.value = next;\n this.dsChange.emit([...next]);\n return;\n }\n this.value = option.value;\n this.dsChange.emit(option.value);\n this.controller.closePopup(true);\n }\n\n private clearSelection = (event: Event) => {\n event.preventDefault();\n event.stopPropagation();\n this.value = this.multiple ? [] : '';\n this.dsChange.emit(this.multiple ? [] : '');\n this.dsClear.emit();\n if (!this.multiple) {\n const enabled = enabledChoiceIndexes(this.visibleOptions);\n this.activeIndex = enabled[0] ?? -1;\n }\n this.controller.focusSearchOrTrigger();\n };\n\n private activateFooterAction = (event: MouseEvent) => {\n event.preventDefault();\n event.stopPropagation();\n this.dsFooterAction.emit();\n this.closePopup();\n };\n\n private readonly handleListKeyDown = this.controller.handleListKeyDown;\n private readonly handleTriggerKeyDown = this.controller.handleTriggerKeyDown;\n\n private renderOption(\n option: SelectOption,\n index: number,\n usesIcons: boolean,\n usesSubtext: boolean\n ) {\n const selected = this.multiple\n ? this.resolvedValues.includes(option.value)\n : option.value === this.scalarValue;\n const active = index === this.activeIndex;\n const actionId = `${this.generatedId}-option-action-${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 actionOpen={option.action?.surfaceOpen ?? option.action?.expanded}\n popupRole={this.popupRole}\n leading={\n this.multiple ? (\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 ) : usesIcons && option.icon ? (\n <span\n class=\"ds-choice-item__icon ds-control-icon-box ds-interaction-fill__content\"\n aria-hidden=\"true\"\n >\n <ds-icon name={option.icon} size={this.size} color=\"inherit\" />\n </span>\n ) : undefined\n }\n supporting={\n option.subtextActions?.length ? (\n <span class=\"ds-choice-item__subtext ds-control-label-box select-option__subtext-actions\">\n {option.subtextActions.map((action, actionIndex) => [\n actionIndex > 0 ? (\n <ds-text\n class=\"select-option__subtext-action-separator\"\n as=\"span\"\n variant={CONTROL_SUPPORTING_TEXT_VARIANT[this.size]}\n color=\"inherit\"\n aria-hidden=\"true\"\n >\n ·\n </ds-text>\n ) : null,\n <button\n type=\"button\"\n class={{\n 'select-option__subtext-action': true,\n 'select-option__subtext-action--negative': action.tone === 'negative',\n 'ds-text-action': true,\n 'ds-focus-ring': true,\n }}\n onKeyDown={(event: KeyboardEvent) => event.stopPropagation()}\n onClick={(event: MouseEvent) => {\n event.preventDefault();\n event.stopPropagation();\n this.dsOptionSubtextAction.emit({\n value: option.value,\n actionValue: action.value,\n originalEvent: event,\n });\n }}\n >\n <ds-text\n as=\"span\"\n variant={CONTROL_SUPPORTING_TEXT_VARIANT[this.size]}\n color=\"inherit\"\n >\n {action.label}\n </ds-text>\n </button>,\n ])}\n </span>\n ) : undefined\n }\n action={\n option.action ? (\n <ds-tooltip label={option.action.label} side=\"left\" size=\"sm\">\n <ds-button-unfilled\n id={actionId}\n variant=\"icon\"\n size={OPTION_ACTION_SIZE[this.size]}\n icon=\"Ellipses\"\n rounded\n hasBorder={false}\n activeFill={false}\n ariaLabel={option.action.label}\n haspopup=\"menu\"\n controls={option.action.controls}\n expanded={option.action.expanded}\n surfaceOpen={option.action.surfaceOpen}\n onKeyDown={event => {\n event.stopPropagation();\n if (event.key === 'Escape' || event.key === 'ArrowLeft') {\n event.preventDefault();\n this.controller.focusSearchOrTrigger();\n }\n }}\n onDsClick={(event: CustomEvent<MouseEvent>) => {\n event.stopPropagation();\n this.dsOptionAction.emit({\n value: option.value,\n anchorId: actionId,\n originalEvent: event.detail,\n });\n }}\n />\n </ds-tooltip>\n ) : undefined\n }\n onHover={() => {\n this.focusRingVisible = false;\n this.activeIndex = index;\n }}\n onSelect={() => this.selectOption(option)}\n />\n );\n }\n\n private get captionIconOnly(): boolean {\n return (\n (this.iconOnly || (this.collapseLabel && this.captionCompact)) &&\n Boolean(this.icon || this.isLoading)\n );\n }\n\n private syncCaptionCompactObserver(): void {\n this.disconnectCaptionCompactObserver();\n if (!this.collapseLabel) {\n if (this.captionCompact) this.captionCompact = false;\n return;\n }\n this.captionCompactDisconnect = observeTableCaptionCompact(this.el, compact => {\n if (this.captionCompact !== compact) this.captionCompact = compact;\n });\n }\n\n private disconnectCaptionCompactObserver(): void {\n this.captionCompactDisconnect?.();\n this.captionCompactDisconnect = undefined;\n }\n\n private renderCaptionTooltip(trigger: VNode, label: string): VNode {\n if (!this.collapseLabel) return trigger;\n return (\n <ds-tooltip label={label} side=\"top\" size=\"sm\">\n {trigger}\n </ds-tooltip>\n );\n }\n\n render() {\n const inactive = this.isDisabled;\n const showPlaceholder = !this.hasSelection;\n const count = this.resolvedValues.length;\n const label = this.multiple\n ? `${this.placeholder}${count > 0 ? ` · ${count}` : ''}`\n : showPlaceholder\n ? this.placeholder\n : (this.triggerLabel ?? this.selectedOption?.label);\n const textVariant = CONTROL_TEXT_VARIANT[this.size];\n const iconSize = ICON_SIZE[this.size];\n const usesOptionIcons = !this.multiple && choiceListUsesIcons(this.allOptions);\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-select-trigger-host': true,\n 'select-host--multiple': this.multiple,\n 'ds-field-stack': true,\n 'ds-control-inactive': inactive,\n [`ds-control--${this.size}`]: true,\n 'ds-table-caption-control': this.collapseLabel,\n 'ds-table-caption-control--compact': this.captionIconOnly,\n ...controlWidthClass(this.width),\n [`select-host--background-${this.background}`]: !!this.background,\n [`ds-select-trigger-host--background-${this.background}`]: !!this.background,\n }}\n >\n {this.renderCaptionTooltip(\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--expanded': !inactive && this.open,\n 'ds-interaction-fill--surface-open': !inactive && this.open,\n 'trigger--bordered': this.hasBorder,\n 'trigger--placeholder': showPlaceholder && !this.multiple,\n 'trigger--has-value': this.hasSelection && !this.neutralTrigger,\n 'trigger--label-placeholder':\n !this.multiple && Boolean(this.triggerLabel) && this.triggerLabelPlaceholder,\n 'wrapper--error': this.hasBorder && this.error,\n [`ds-control--${this.size}`]: true,\n ...choiceBackgroundClassMap(this.background),\n }}\n disabled={inactive}\n role=\"combobox\"\n aria-haspopup={this.popupRole}\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 ?? (this.captionIconOnly ? label : undefined)}\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={event => {\n const action = this.visibleOptions[this.activeIndex]?.action;\n if (\n this.open &&\n action &&\n (event.key === 'ArrowRight' || (event.key === 'F10' && event.shiftKey))\n ) {\n event.preventDefault();\n document\n .getElementById(`${this.generatedId}-option-action-${this.activeIndex}`)\n ?.querySelector<HTMLElement>('button')\n ?.focus();\n return;\n }\n this.handleTriggerKeyDown(event);\n }}\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 {this.captionIconOnly ? null : (\n <span\n class={{\n 'trigger__label-box': true,\n 'ds-interaction-fill__content': true,\n }}\n >\n <span\n class={{\n 'trigger__label-content': true,\n 'ds-control-label-box': true,\n 'ds-control-label-dot': this.dot,\n 'trigger__label-content--dot': this.dot,\n }}\n >\n <ds-text\n class=\"trigger__label\"\n as=\"span\"\n variant={textVariant}\n color=\"inherit\"\n lineTruncation={1}\n >\n {label}\n </ds-text>\n {this.dot && (\n <ds-badge\n class=\"trigger__dot ds-control-label-dot__badge\"\n variant=\"dot\"\n hasRing={false}\n label=\"\"\n aria-hidden=\"true\"\n />\n )}\n </span>\n </span>\n )}\n {this.captionIconOnly || this.indicator === 'none' ? null : (\n <span\n class=\"trigger__chevron ds-control-icon-box ds-interaction-fill__content\"\n aria-hidden=\"true\"\n >\n <ds-icon\n name={this.indicator === 'up-down' ? 'ChevronUpDown' : 'ChevronDown'}\n size={iconSize}\n color=\"inherit\"\n />\n </span>\n )}\n </button>,\n this.captionIconOnly ? this.ariaLabel?.trim() || this.placeholder : ''\n )}\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={this.popupRole}\n aria-multiselectable={this.multiple ? 'true' : undefined}\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 ds-empty-region\"\n role={this.popupRole === 'grid' ? 'row' : 'option'}\n aria-selected=\"false\"\n aria-disabled=\"true\"\n aria-label={this.loadingLabel}\n aria-live=\"polite\"\n >\n {this.popupRole === 'grid' ? (\n <span role=\"gridcell\">\n <ds-loader size={this.size} color=\"inherit\" />\n </span>\n ) : (\n <ds-loader size={this.size} color=\"inherit\" />\n )}\n </div>\n ) : this.visibleOptions.length === 0 ? (\n <div\n class=\"ds-choice-empty ds-empty-region\"\n role={this.popupRole === 'grid' ? 'row' : 'option'}\n aria-selected=\"false\"\n aria-disabled=\"true\"\n aria-label={this.noResultsText}\n aria-live=\"polite\"\n >\n {this.popupRole === 'grid' ? (\n <span role=\"gridcell\">\n <ds-empty-state body={this.noResultsText} />\n </span>\n ) : (\n <ds-empty-state body={this.noResultsText} />\n )}\n </div>\n ) : (\n this.visibleSections.map((section, sectionIndex) => (\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 'ds-choice-section--headed-after-first':\n sectionIndex > 0 && Boolean(section.header),\n }}\n role={\n this.popupRole === 'grid' ? 'rowgroup' : section.header ? 'group' : undefined\n }\n aria-label={section.header}\n >\n {section.header && this.popupRole === 'grid' ? (\n <ds-text\n class={`ds-choice-section__header ds-control-section-heading ds-control--${this.size}`}\n as=\"span\"\n variant={CONTROL_SUPPORTING_TEXT_VARIANT[this.size]}\n emphasis\n color=\"primary\"\n role=\"row\"\n >\n <span class=\"ds-choice-section__header-label\" role=\"gridcell\">\n {section.header}\n </span>\n </ds-text>\n ) : section.header ? (\n <ds-text\n class={`ds-choice-section__header ds-control-section-heading 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 <span class=\"ds-choice-section__header-label\">{section.header}</span>\n </ds-text>\n ) : null}\n {section.options.map(option =>\n this.renderOption(option, flatIndex++, usesOptionIcons, usesOptionSubtext)\n )}\n </div>\n ))\n )}\n </div>\n {this.footerActionLabel && !this.isLoading ? (\n <ChoiceActionFooter\n size={this.size}\n label={this.footerActionLabel}\n onAction={this.activateFooterAction}\n />\n ) : this.allowClear && this.hasSelection && !this.isLoading ? (\n <ChoiceFooter\n size={this.size}\n summary={this.multiple ? `${count} ${this.selectedLabel}` : undefined}\n compactSummary={this.multiple ? String(count) : undefined}\n useCompactSummary={this.compactFooterSummary}\n contentRef={element => {\n this.footerContentElement = element ?? undefined;\n }}\n summaryMeasureRef={element => {\n this.footerSummaryMeasureElement = element ?? undefined;\n }}\n clearRef={element => {\n this.footerClearElement = element ?? undefined;\n }}\n clearLabel={this.clearLabel}\n onClear={this.clearSelection}\n />\n ) : null}\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",
|
|
982
1017
|
"type": "registry:ui"
|
|
983
1018
|
}
|
|
984
1019
|
]
|