@ds-mo/ui 14.18.1 → 14.19.1
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 +16 -9
- package/dist/angular/ds-pagination.d.ts +1 -1
- package/dist/angular/ds-pagination.js +3 -3
- 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-avatar.js +1 -1
- package/dist/components/ds-badge.js +1 -1
- package/dist/components/ds-banner.js +1 -1
- package/dist/components/ds-banner.js.map +1 -1
- package/dist/components/ds-bar-action.js +1 -1
- package/dist/components/ds-bar-action.js.map +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-workflow.js +1 -1
- package/dist/components/ds-bar-workflow.js.map +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-chart.js +1 -1
- package/dist/components/ds-card-chart.js.map +1 -1
- package/dist/components/ds-card-overview.js +1 -1
- package/dist/components/ds-card-overview.js.map +1 -1
- package/dist/components/ds-card-setting.js +1 -1
- package/dist/components/ds-card-setting.js.map +1 -1
- package/dist/components/ds-chart-legend.js +1 -1
- package/dist/components/ds-chart-legend.js.map +1 -1
- package/dist/components/ds-chart.js +1 -1
- package/dist/components/ds-chart.js.map +1 -1
- package/dist/components/ds-checkbox.js +1 -1
- package/dist/components/ds-chip.js +1 -1
- package/dist/components/ds-chip.js.map +1 -1
- package/dist/components/ds-code-block.js +1 -1
- package/dist/components/ds-conversation-list-item.js +1 -1
- package/dist/components/ds-conversation-list-item.js.map +1 -1
- package/dist/components/ds-conversation-list-section.js +1 -1
- package/dist/components/ds-conversation-list.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-filter-menu.js +1 -1
- package/dist/components/ds-icon.js +1 -1
- package/dist/components/ds-input.js +1 -1
- package/dist/components/ds-loader.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-composer.js.map +1 -1
- package/dist/components/ds-message-scroller.js +1 -1
- package/dist/components/ds-message-scroller.js.map +1 -1
- package/dist/components/ds-message.js +1 -1
- package/dist/components/ds-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-sub-nav.js.map +1 -1
- package/dist/components/ds-panel-tool-header.js +1 -1
- package/dist/components/ds-panel-tool-search.js +1 -1
- package/dist/components/ds-panel-tool-search.js.map +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-scroll-overlay.js +1 -1
- package/dist/components/ds-scroll-overlay.js.map +1 -1
- package/dist/components/ds-select.js +1 -1
- package/dist/components/ds-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-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-table-filter.js +1 -1
- package/dist/components/ds-table-saved-views.js +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.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-toast.js +1 -1
- package/dist/components/ds-toast.js.map +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-0XTyQNRG.js +2 -0
- package/dist/components/{p-JPTkZ3Ji.js.map → p-0XTyQNRG.js.map} +1 -1
- package/dist/components/{p-DFQ9mcbK.js → p-1YHFQeI7.js} +1 -1
- package/dist/components/p-1YHFQeI7.js.map +1 -0
- package/dist/components/p-B-HsfQOK.js +2 -0
- package/dist/components/p-B-HsfQOK.js.map +1 -0
- package/dist/components/p-B4wTnsUL.js +2 -0
- package/dist/components/p-B4wTnsUL.js.map +1 -0
- package/dist/components/p-B6WQre5D.js +2 -0
- package/dist/components/p-B6WQre5D.js.map +1 -0
- package/dist/components/{p-B1JRCDHS.js → p-BHhmB9-4.js} +2 -2
- package/dist/components/{p-B1JRCDHS.js.map → p-BHhmB9-4.js.map} +1 -1
- package/dist/components/{p-CqQFowZd.js → p-BNq9fr9S.js} +2 -2
- package/dist/components/p-BNq9fr9S.js.map +1 -0
- package/dist/components/{p-BKp__CQX.js → p-BWwiCvep.js} +2 -2
- package/dist/components/p-BWwiCvep.js.map +1 -0
- package/dist/components/{p-BBG6Sswb.js → p-BYFS2qWb.js} +2 -2
- package/dist/components/{p-BBG6Sswb.js.map → p-BYFS2qWb.js.map} +1 -1
- package/dist/components/{p-uI6z53BE.js → p-Bb4KnRy0.js} +2 -2
- package/dist/components/{p-HB_cf_aR.js → p-BeA6Jv-3.js} +2 -2
- package/dist/components/{p-HB_cf_aR.js.map → p-BeA6Jv-3.js.map} +1 -1
- package/dist/components/{p-1Hu9jlju.js → p-Bf7PZJPT.js} +2 -2
- package/dist/components/{p-Bbb9A3NV.js → p-BjRD57w3.js} +2 -2
- package/dist/components/p-BjRD57w3.js.map +1 -0
- package/dist/components/{p-CKVHrOEp.js → p-BmlGeaY8.js} +2 -2
- package/dist/components/p-BmlGeaY8.js.map +1 -0
- package/dist/components/{p-CLVxgTme.js → p-BubgDQlb.js} +2 -2
- package/dist/components/p-BubgDQlb.js.map +1 -0
- package/dist/components/{p-BNkU9dcc.js → p-Bv6FSDPR.js} +2 -2
- package/dist/components/{p-BNkU9dcc.js.map → p-Bv6FSDPR.js.map} +1 -1
- package/dist/components/p-Bx1IHqJb.js +2 -0
- package/dist/components/p-Bx1IHqJb.js.map +1 -0
- package/dist/components/{p-D3fMaUJ3.js → p-Bx5BSd6F.js} +2 -2
- package/dist/components/p-Bx5BSd6F.js.map +1 -0
- package/dist/components/{p-B8q-6a03.js → p-C0qN3PSb.js} +1 -1
- package/dist/components/{p-B8q-6a03.js.map → p-C0qN3PSb.js.map} +1 -1
- package/dist/components/{p-H5T258T-.js → p-CFsLF-ts.js} +2 -2
- package/dist/components/p-CFsLF-ts.js.map +1 -0
- package/dist/components/p-CKtJ6FHM.js +2 -0
- package/dist/components/p-CKtJ6FHM.js.map +1 -0
- package/dist/components/p-CRSwNbPk.js +2 -0
- package/dist/components/p-CRSwNbPk.js.map +1 -0
- package/dist/components/{p-BFfLvMZd.js → p-Cea7rROU.js} +2 -2
- package/dist/components/{p-BFfLvMZd.js.map → p-Cea7rROU.js.map} +1 -1
- package/dist/components/{p-B0Byw6OX.js → p-CgGcgMjs.js} +1 -1
- package/dist/components/{p-B0Byw6OX.js.map → p-CgGcgMjs.js.map} +1 -1
- package/dist/components/{p-C-Y7gYgz.js → p-ChfVRPHw.js} +2 -2
- package/dist/components/p-ChfVRPHw.js.map +1 -0
- package/dist/components/{p-CJq3n5kA.js → p-Ck1hgD6g.js} +2 -2
- package/dist/components/p-Ck1hgD6g.js.map +1 -0
- package/dist/components/{p-BMc0DE59.js → p-CochmPyg.js} +1 -1
- package/dist/components/p-CochmPyg.js.map +1 -0
- package/dist/components/{p-iMuFgSoM.js → p-Cp5Hr7Vz.js} +2 -2
- package/dist/components/p-Cp5Hr7Vz.js.map +1 -0
- package/dist/components/p-CpUXubax.js +2 -0
- package/dist/components/{p-DtndC9f1.js.map → p-CpUXubax.js.map} +1 -1
- package/dist/components/p-Crz606uD.js +2 -0
- package/dist/components/p-Crz606uD.js.map +1 -0
- package/dist/components/p-Cxr0IQjC.js.map +1 -1
- package/dist/components/p-D-MWvpg6.js +2 -0
- package/dist/components/p-D-MWvpg6.js.map +1 -0
- package/dist/components/{p-Bu3dOjpW.js → p-D7v6Na7U.js} +2 -2
- package/dist/components/{p-Bu3dOjpW.js.map → p-D7v6Na7U.js.map} +1 -1
- package/dist/components/{p-C6DNhlES.js → p-DA5H_Iq_.js} +1 -1
- package/dist/components/p-DA5H_Iq_.js.map +1 -0
- package/dist/components/p-DBYCwq9L.js +2 -0
- package/dist/components/{p-BXN-e7vF.js.map → p-DBYCwq9L.js.map} +1 -1
- package/dist/components/{p-CY4YFAT3.js → p-DCbKsABt.js} +1 -1
- package/dist/components/p-DCbKsABt.js.map +1 -0
- package/dist/components/{p-C6R5Lft0.js → p-DFC0sRM-.js} +2 -2
- package/dist/components/p-DFC0sRM-.js.map +1 -0
- package/dist/components/{p-CsBICYmt.js → p-DGkn8FwV.js} +2 -2
- package/dist/components/p-DGkn8FwV.js.map +1 -0
- package/dist/components/p-DIqT1m2r.js +2 -0
- package/dist/components/p-DIqT1m2r.js.map +1 -0
- package/dist/components/p-DKt_8s-U.js +2 -0
- package/dist/components/p-DKt_8s-U.js.map +1 -0
- package/dist/components/p-DOUk4KoT.js.map +1 -1
- package/dist/components/p-DQBT1Jkf.js +2 -0
- package/dist/components/p-DQBT1Jkf.js.map +1 -0
- package/dist/components/p-DW5Ita-P.js +2 -0
- package/dist/components/{p-DUdOmSFR.js.map → p-DW5Ita-P.js.map} +1 -1
- package/dist/components/{p-DNpLDbWY.js → p-DZ58Sg5a.js} +1 -1
- package/dist/components/p-DZ58Sg5a.js.map +1 -0
- package/dist/components/{p-DDgeXGbB.js → p-DZv2g9Pe.js} +2 -2
- package/dist/components/p-DZv2g9Pe.js.map +1 -0
- package/dist/components/{p-BknXYAEc.js → p-DdH7b4JF.js} +2 -2
- package/dist/components/p-DdH7b4JF.js.map +1 -0
- package/dist/components/{p-DBue3ISk.js → p-Demfba7T.js} +2 -2
- package/dist/components/p-Demfba7T.js.map +1 -0
- package/dist/components/p-Djxw2FjZ.js +2 -0
- package/dist/components/{p-CwjVgUqy.js.map → p-Djxw2FjZ.js.map} +1 -1
- package/dist/components/p-DmWoFSGs.js +2 -0
- package/dist/components/p-DmWoFSGs.js.map +1 -0
- package/dist/components/p-Do8wbFff.js +2 -0
- package/dist/components/p-Do8wbFff.js.map +1 -0
- package/dist/components/p-Drnyq8z7.js +2 -0
- package/dist/components/p-Drnyq8z7.js.map +1 -0
- package/dist/components/p-DsgEY6mZ.js +2 -0
- package/dist/components/p-DsgEY6mZ.js.map +1 -0
- package/dist/components/p-Is1lFmwp.js +2 -0
- package/dist/components/p-Is1lFmwp.js.map +1 -0
- package/dist/components/{p-BUDAwJVV.js → p-caLOcQUK.js} +2 -2
- package/dist/components/p-caLOcQUK.js.map +1 -0
- package/dist/components/{p-D1eEgwcP.js → p-etg-Uk1u.js} +1 -1
- package/dist/components/{p-D1eEgwcP.js.map → p-etg-Uk1u.js.map} +1 -1
- package/dist/components/{p-C9gC4haA.js → p-fRDzpfi9.js} +2 -2
- package/dist/components/p-fRDzpfi9.js.map +1 -0
- package/dist/components/{p-C-vRkDyn.js → p-rCeztgVn.js} +1 -1
- package/dist/components/{p-C-vRkDyn.js.map → p-rCeztgVn.js.map} +1 -1
- package/dist/components/{p-B_jZIC9R.js → p-vrotWZxt.js} +2 -2
- package/dist/components/{p-B_jZIC9R.js.map → p-vrotWZxt.js.map} +1 -1
- package/dist/docs/components.json +35 -6
- package/dist/lib/shell/index.d.ts +2 -2
- package/dist/lib/shell/index.js +8 -6
- package/dist/lib/shell/index.js.map +2 -2
- package/dist/lib/toast/index.js +1 -4
- package/dist/lib/toast/index.js.map +2 -2
- package/dist/lib/utils/index.d.ts +1 -1
- package/dist/lib/utils/index.js +104 -22
- package/dist/lib/utils/index.js.map +2 -2
- package/dist/mcp/cli.js +70 -31
- package/dist/mcp-data/registry/agent-activity.json +1 -1
- package/dist/mcp-data/registry/agent-questionnaire.json +3 -3
- package/dist/mcp-data/registry/agent-source-list.json +2 -2
- package/dist/mcp-data/registry/agent-tool-call.json +2 -2
- package/dist/mcp-data/registry/attachment-list.json +1 -1
- package/dist/mcp-data/registry/badge.json +1 -1
- package/dist/mcp-data/registry/banner.json +2 -2
- package/dist/mcp-data/registry/bar-action.json +1 -1
- package/dist/mcp-data/registry/bar-nav.json +3 -3
- package/dist/mcp-data/registry/bar-workflow.json +1 -1
- package/dist/mcp-data/registry/breadcrumb.json +1 -1
- package/dist/mcp-data/registry/button-filled.json +1 -1
- package/dist/mcp-data/registry/button-unfilled.json +1 -1
- package/dist/mcp-data/registry/card-chart.json +2 -2
- package/dist/mcp-data/registry/card-overview.json +3 -3
- package/dist/mcp-data/registry/card-setting.json +1 -1
- package/dist/mcp-data/registry/chart-legend.json +1 -1
- package/dist/mcp-data/registry/chart.json +1 -1
- package/dist/mcp-data/registry/checkbox.json +1 -1
- package/dist/mcp-data/registry/chip.json +1 -1
- package/dist/mcp-data/registry/code-block.json +1 -1
- package/dist/mcp-data/registry/conversation-list-item.json +1 -1
- package/dist/mcp-data/registry/conversation-list.json +2 -2
- 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/filter-menu.json +1 -1
- package/dist/mcp-data/registry/icon.json +2 -2
- package/dist/mcp-data/registry/input.json +2 -2
- package/dist/mcp-data/registry/loader.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 +2 -2
- package/dist/mcp-data/registry/message-scroller.json +3 -3
- package/dist/mcp-data/registry/mobile-bar-nav.json +1 -1
- package/dist/mcp-data/registry/mobile-header.json +2 -2
- package/dist/mcp-data/registry/mobile-section-switcher.json +2 -2
- package/dist/mcp-data/registry/mobile-sheet-nav.json +1 -1
- package/dist/mcp-data/registry/modal.json +2 -2
- package/dist/mcp-data/registry/pagination.json +32 -11
- package/dist/mcp-data/registry/panel-nav.json +2 -2
- 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 +2 -2
- package/dist/mcp-data/registry/radio.json +1 -1
- package/dist/mcp-data/registry/registry.json +15 -8
- package/dist/mcp-data/registry/select.json +1 -1
- package/dist/mcp-data/registry/shell-app.json +2 -2
- package/dist/mcp-data/registry/shell-page.json +1 -1
- package/dist/mcp-data/registry/shell-tools.json +1 -1
- package/dist/mcp-data/registry/skeleton.json +2 -2
- package/dist/mcp-data/registry/slider.json +2 -2
- package/dist/mcp-data/registry/swatch-picker.json +3 -3
- package/dist/mcp-data/registry/switch.json +2 -2
- package/dist/mcp-data/registry/tab-group.json +2 -2
- package/dist/mcp-data/registry/table-sort.json +1 -1
- package/dist/mcp-data/registry/table.json +18 -18
- package/dist/mcp-data/registry/tag.json +2 -2
- package/dist/mcp-data/registry/text.json +4 -4
- package/dist/mcp-data/registry/toast.json +2 -2
- package/dist/mcp-data/registry/tooltip-chart.json +2 -2
- package/dist/mcp-data/registry/tooltip.json +2 -2
- package/dist/styles/prose.css +42 -38
- package/dist/styles/table.css +39 -67
- package/dist/types/components/CardSetting/index.d.ts +1 -1
- package/dist/types/components/Pagination/Pagination.d.ts +2 -0
- package/dist/types/components/Pagination/pagination-types.d.ts +2 -0
- package/dist/types/components/TooltipChart/index.d.ts +1 -1
- package/dist/types/components.d.ts +11 -0
- package/dist/types/shell/index.d.ts +2 -2
- package/dist/types/utils/index.d.ts +1 -1
- package/dist/vue/ds-pagination.js +1 -0
- package/package.json +11 -1
- package/dist/components/p-B00PEE6t.js +0 -2
- package/dist/components/p-B00PEE6t.js.map +0 -1
- package/dist/components/p-BCOl1x0_.js +0 -2
- package/dist/components/p-BCOl1x0_.js.map +0 -1
- package/dist/components/p-BHVOiWz5.js +0 -2
- package/dist/components/p-BHVOiWz5.js.map +0 -1
- package/dist/components/p-BKp__CQX.js.map +0 -1
- package/dist/components/p-BMc0DE59.js.map +0 -1
- package/dist/components/p-BPG0r_mC.js +0 -2
- package/dist/components/p-BPG0r_mC.js.map +0 -1
- package/dist/components/p-BUDAwJVV.js.map +0 -1
- package/dist/components/p-BXN-e7vF.js +0 -2
- package/dist/components/p-Bbb9A3NV.js.map +0 -1
- package/dist/components/p-Bece5KH3.js +0 -2
- package/dist/components/p-Bece5KH3.js.map +0 -1
- package/dist/components/p-BhYS_Ump.js +0 -2
- package/dist/components/p-BhYS_Ump.js.map +0 -1
- package/dist/components/p-Bkh7scMQ.js +0 -2
- package/dist/components/p-Bkh7scMQ.js.map +0 -1
- package/dist/components/p-BknXYAEc.js.map +0 -1
- package/dist/components/p-C-Y7gYgz.js.map +0 -1
- package/dist/components/p-C6DNhlES.js.map +0 -1
- package/dist/components/p-C6R5Lft0.js.map +0 -1
- package/dist/components/p-C9gC4haA.js.map +0 -1
- package/dist/components/p-CAnnQL1F.js +0 -2
- package/dist/components/p-CAnnQL1F.js.map +0 -1
- package/dist/components/p-CE2IKOsy.js +0 -2
- package/dist/components/p-CE2IKOsy.js.map +0 -1
- package/dist/components/p-CJq3n5kA.js.map +0 -1
- package/dist/components/p-CKVHrOEp.js.map +0 -1
- package/dist/components/p-CLVxgTme.js.map +0 -1
- package/dist/components/p-CY4YFAT3.js.map +0 -1
- package/dist/components/p-Cg0kimXZ.js +0 -2
- package/dist/components/p-Cg0kimXZ.js.map +0 -1
- package/dist/components/p-CqQFowZd.js.map +0 -1
- package/dist/components/p-CsBICYmt.js.map +0 -1
- package/dist/components/p-CwjVgUqy.js +0 -2
- package/dist/components/p-D123Jvp-.js +0 -2
- package/dist/components/p-D123Jvp-.js.map +0 -1
- package/dist/components/p-D3fMaUJ3.js.map +0 -1
- package/dist/components/p-D7R4yE2C.js +0 -2
- package/dist/components/p-D7R4yE2C.js.map +0 -1
- package/dist/components/p-D7haeL5y.js +0 -2
- package/dist/components/p-D7haeL5y.js.map +0 -1
- package/dist/components/p-DAvh38E4.js +0 -2
- package/dist/components/p-DAvh38E4.js.map +0 -1
- package/dist/components/p-DBue3ISk.js.map +0 -1
- package/dist/components/p-DDgeXGbB.js.map +0 -1
- package/dist/components/p-DFQ9mcbK.js.map +0 -1
- package/dist/components/p-DNpLDbWY.js.map +0 -1
- package/dist/components/p-DUdOmSFR.js +0 -2
- package/dist/components/p-DtndC9f1.js +0 -2
- package/dist/components/p-H5T258T-.js.map +0 -1
- package/dist/components/p-JPTkZ3Ji.js +0 -2
- package/dist/components/p-LK-jpxwq.js +0 -2
- package/dist/components/p-LK-jpxwq.js.map +0 -1
- package/dist/components/p-ag6Io-kL.js +0 -2
- package/dist/components/p-ag6Io-kL.js.map +0 -1
- package/dist/components/p-iMuFgSoM.js.map +0 -1
- /package/dist/components/{p-uI6z53BE.js.map → p-Bb4KnRy0.js.map} +0 -0
- /package/dist/components/{p-1Hu9jlju.js.map → p-Bf7PZJPT.js.map} +0 -0
|
@@ -543,17 +543,17 @@
|
|
|
543
543
|
},
|
|
544
544
|
{
|
|
545
545
|
"path": "src/wc/components/Menu/menu-reorder.ts",
|
|
546
|
-
"content": "import {\n isMenuPickerSection,\n type MenuItemData,\n type MenuReorderDetail,\n type MenuSection,\n} from './menu-types';\n\nexport interface MenuReorderRange {\n start: number;\n end: number;\n}\n\n/** Contiguous reorderable run containing `index`, or null when that row cannot move. */\nexport function menuReorderableRange(\n items: readonly MenuItemData[],\n index: number
|
|
546
|
+
"content": "import {\n isMenuPickerSection,\n type MenuItemData,\n type MenuReorderDetail,\n type MenuSection,\n} from './menu-types';\n\nexport interface MenuReorderRange {\n start: number;\n end: number;\n}\n\n/** Contiguous reorderable run containing `index`, or null when that row cannot move. */\nexport function menuReorderableRange(\n items: readonly MenuItemData[],\n index: number\n): MenuReorderRange | null {\n if (!items[index]?.reorderable) return null;\n\n let start = index;\n while (start > 0 && items[start - 1]?.reorderable) start -= 1;\n\n let end = index;\n while (end < items.length - 1 && items[end + 1]?.reorderable) end += 1;\n\n return { start, end };\n}\n\n/**\n * Move a reorderable row so it would be inserted before `insertBefore`.\n * `insertBefore` is clamped to the contiguous reorderable run.\n */\nexport function moveReorderableMenuItemBefore(\n items: readonly MenuItemData[],\n fromIndex: number,\n insertBefore: number\n): MenuItemData[] | null {\n const range = menuReorderableRange(items, fromIndex);\n if (!range) return null;\n\n const clamped = Math.min(Math.max(insertBefore, range.start), range.end + 1);\n let toIndex = clamped;\n if (fromIndex < toIndex) toIndex -= 1;\n if (toIndex === fromIndex || toIndex < range.start || toIndex > range.end) return null;\n\n const next = [...items];\n const [item] = next.splice(fromIndex, 1);\n next.splice(toIndex, 0, item);\n return next;\n}\n\n/** Keyboard step: move one slot within the reorderable run. */\nexport function moveReorderableMenuItemBy(\n items: readonly MenuItemData[],\n fromIndex: number,\n offset: number\n): MenuItemData[] | null {\n if (offset === 0) return null;\n return moveReorderableMenuItemBefore(\n items,\n fromIndex,\n fromIndex + (offset < 0 ? offset : offset + 1)\n );\n}\n\nexport function locateMenuItem(\n sections: readonly MenuSection[],\n flatIndex: number\n): { sectionIndex: number; itemIndex: number; items: MenuItemData[] } | null {\n let remaining = flatIndex;\n for (let sectionIndex = 0; sectionIndex < sections.length; sectionIndex += 1) {\n const section = sections[sectionIndex];\n if (isMenuPickerSection(section)) continue;\n if (remaining < section.items.length) {\n return { sectionIndex, itemIndex: remaining, items: section.items };\n }\n remaining -= section.items.length;\n }\n return null;\n}\n\nexport function createMenuReorderDetail(\n items: MenuItemData[],\n fromIndex: number,\n nextItems: MenuItemData[],\n sectionIndex: number\n): MenuReorderDetail | null {\n const item = items[fromIndex];\n if (!item) return null;\n const toIndex = nextItems.indexOf(item);\n if (toIndex < 0) {\n const value = item.value;\n const matched = value ? nextItems.findIndex(candidate => candidate.value === value) : -1;\n if (matched < 0) return null;\n return { item, fromIndex, toIndex: matched, sectionIndex, items: nextItems };\n }\n return { item, fromIndex, toIndex, sectionIndex, items: nextItems };\n}\n",
|
|
547
547
|
"type": "registry:ui"
|
|
548
548
|
},
|
|
549
549
|
{
|
|
550
550
|
"path": "src/wc/components/Menu/menu-sections.ts",
|
|
551
|
-
"content": "import type { MenuSection } from './menu-types';\nimport { isMenuSwatchPickerSection } from './menu-types';\n\n/** Preserve the exact menu context painted during exit motion. */\nexport function snapshotMenuSections(sections: readonly MenuSection[]): MenuSection[] {\n return sections.map(section => {\n if (isMenuSwatchPickerSection(section)) {\n return {\n ...section,\n ...(section.options
|
|
551
|
+
"content": "import type { MenuSection } from './menu-types';\nimport { isMenuSwatchPickerSection } from './menu-types';\n\n/** Preserve the exact menu context painted during exit motion. */\nexport function snapshotMenuSections(sections: readonly MenuSection[]): MenuSection[] {\n return sections.map(section => {\n if (isMenuSwatchPickerSection(section)) {\n return {\n ...section,\n ...(section.options ? { options: section.options.map(option => ({ ...option })) } : {}),\n ...(section.sections\n ? {\n sections: section.sections.map(swatchSection => ({\n ...swatchSection,\n options: swatchSection.options.map(option => ({ ...option })),\n })),\n }\n : {}),\n };\n }\n return { ...section, items: section.items.map(item => ({ ...item })) };\n });\n}\n",
|
|
552
552
|
"type": "registry:ui"
|
|
553
553
|
},
|
|
554
554
|
{
|
|
555
555
|
"path": "src/wc/components/Menu/menu-types.ts",
|
|
556
|
-
"content": "import type { SwatchPickerOption, SwatchPickerSection } from '../SwatchPicker/swatch-picker-types';\nimport type { TagContrast, TagIntent } from '../Tag/Tag';\n\nexport type { MenuAlign, MenuSide } from './menu-position';\nexport {\n PANEL_NAV_USER_MENU_PLACEMENT,\n PANEL_TOOLS_HEADER_MENU_PLACEMENT,\n type MenuPlacement,\n} from './menu-placement';\n\nexport interface MenuItemTagData {\n label: string;\n intent?: TagIntent;\n contrast?: TagContrast;\n rounded?: boolean;\n}\n\nexport interface MenuItemData {\n label: string;\n value?: string;\n subtext?: string;\n /** Leading decorative icon. Ignored when `reorderable` is true. */\n icon?: string;\n /** Show a supplemental brand notification dot. */\n dot?: boolean;\n /** Show a non-interactive tag at the trailing edge of the row. */\n tag?: MenuItemTagData;\n isSelected?: boolean;\n isInactive?: boolean;\n isDestructive?: boolean;\n /** Render a compact switch indicator; the menu row owns interaction. */\n showSwitch?: boolean;\n switchValue?: boolean;\n /** Prefix a drag handle and allow pointer and keyboard reorder within the contiguous reorderable run. */\n reorderable?: boolean;\n}\n\n/** Next section item order after a pointer or keyboard reorder. */\nexport interface MenuReorderDetail {\n item: MenuItemData;\n fromIndex: number;\n toIndex: number;\n sectionIndex: number;\n items: MenuItemData[];\n}\n\nexport interface MenuItemsSection {\n header?: string;\n items: MenuItemData[];\n}\n\nexport interface MenuSwatchPickerSection {\n header?: string;\n variant: 'swatch-picker';\n value: string;\n options?: SwatchPickerOption[];\n sections?: SwatchPickerSection[];\n groupLabel?: string;\n}\n\nexport type MenuSection
|
|
556
|
+
"content": "import type { SwatchPickerOption, SwatchPickerSection } from '../SwatchPicker/swatch-picker-types';\nimport type { TagContrast, TagIntent } from '../Tag/Tag';\n\nexport type { MenuAlign, MenuSide } from './menu-position';\nexport {\n PANEL_NAV_USER_MENU_PLACEMENT,\n PANEL_TOOLS_HEADER_MENU_PLACEMENT,\n type MenuPlacement,\n} from './menu-placement';\n\nexport interface MenuItemTagData {\n label: string;\n intent?: TagIntent;\n contrast?: TagContrast;\n rounded?: boolean;\n}\n\nexport interface MenuItemData {\n label: string;\n value?: string;\n subtext?: string;\n /** Leading decorative icon. Ignored when `reorderable` is true. */\n icon?: string;\n /** Show a supplemental brand notification dot. */\n dot?: boolean;\n /** Show a non-interactive tag at the trailing edge of the row. */\n tag?: MenuItemTagData;\n isSelected?: boolean;\n isInactive?: boolean;\n isDestructive?: boolean;\n /** Render a compact switch indicator; the menu row owns interaction. */\n showSwitch?: boolean;\n switchValue?: boolean;\n /** Prefix a drag handle and allow pointer and keyboard reorder within the contiguous reorderable run. */\n reorderable?: boolean;\n}\n\n/** Next section item order after a pointer or keyboard reorder. */\nexport interface MenuReorderDetail {\n item: MenuItemData;\n fromIndex: number;\n toIndex: number;\n sectionIndex: number;\n items: MenuItemData[];\n}\n\nexport interface MenuItemsSection {\n header?: string;\n items: MenuItemData[];\n}\n\nexport interface MenuSwatchPickerSection {\n header?: string;\n variant: 'swatch-picker';\n value: string;\n options?: SwatchPickerOption[];\n sections?: SwatchPickerSection[];\n groupLabel?: string;\n}\n\nexport type MenuSection = MenuItemsSection | MenuSwatchPickerSection;\n\nexport function isMenuSwatchPickerSection(\n section: MenuSection\n): section is MenuSwatchPickerSection {\n return 'variant' in section && section.variant === 'swatch-picker';\n}\n\nexport function isMenuPickerSection(section: MenuSection): section is MenuSwatchPickerSection {\n return isMenuSwatchPickerSection(section);\n}\n\nexport type { SwatchPickerOption, SwatchPickerSection };\n",
|
|
557
557
|
"type": "registry:ui"
|
|
558
558
|
}
|
|
559
559
|
]
|
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
},
|
|
123
123
|
{
|
|
124
124
|
"path": "src/wc/components/MessageBubble/MessageBubble.tsx",
|
|
125
|
-
"content": "import { Component, h, Host, Prop } from '@stencil/core';\n\nexport type MessageBubbleVariant = 'user' | 'received';\n\n@Component({\n tag: 'ds-message-bubble',\n styleUrl: 'MessageBubble.css',\n scoped: true,\n})\nexport class MessageBubble {\n @Prop() variant: MessageBubbleVariant = 'received';\n\n render() {\n return (\n <Host class={`message-bubble--${this.variant}`}>\n <div class=\"message-bubble\"
|
|
125
|
+
"content": "import { Component, h, Host, Prop } from '@stencil/core';\n\nexport type MessageBubbleVariant = 'user' | 'received';\n\n@Component({\n tag: 'ds-message-bubble',\n styleUrl: 'MessageBubble.css',\n scoped: true,\n})\nexport class MessageBubble {\n @Prop() variant: MessageBubbleVariant = 'received';\n\n render() {\n return (\n <Host class={`message-bubble--${this.variant}`}>\n <div class=\"message-bubble\">\n <slot />\n </div>\n </Host>\n );\n }\n}\n",
|
|
126
126
|
"type": "registry:ui"
|
|
127
127
|
}
|
|
128
128
|
]
|
|
@@ -284,12 +284,12 @@
|
|
|
284
284
|
"files": [
|
|
285
285
|
{
|
|
286
286
|
"path": "src/wc/components/MessageComposer/MessageComposer.css",
|
|
287
|
-
"content": "@import '../../utils/forced-colors.css';\n\n:host {\n display: block;\n width: 100%;\n box-sizing: border-box;\n -webkit-user-select: none;\n user-select: none;\n}\n\n.message-composer {\n display: flex;\n flex-direction: column;\n gap: var(--dimension-space-050);\n width: 100%;\n margin: 0;\n}\n\n.message-composer__attachments:empty,\n.message-composer__tools:empty,\n.message-composer__error-actions:empty {\n display: none;\n}\n\n.message-composer__error-support,\n.message-composer__error-message,\n.message-composer__error-actions {\n display: flex;\n align-items: center;\n}\n\n.message-composer__error-support {\n justify-content: space-between;\n flex-wrap: wrap;\n gap: var(--dimension-space-075) var(--dimension-space-100);\n min-width: 0;\n padding: var(--dimension-space-075) var(--dimension-space-100);\n background: var(--color-background-faint-negative);\n}\n\n.message-composer__error-message {\n flex: 1 1 var(--dimension-panel-width-xs);\n gap: var(--dimension-space-050);\n min-width: 0;\n color: var(--color-foreground-bold-negative);\n}\n\n.message-composer__error-actions {\n flex: 0 1 auto;\n flex-wrap: wrap;\n gap: var(--dimension-space-050);\n}\n\n.message-composer__stack {\n min-width: 0;\n}\n\n.message-composer--error .message-composer__stack {\n overflow: hidden;\n border: var(--dimension-stroke-width-012) solid var(--color-border-bold-negative);\n border-radius: var(--dimension-radius-125);\n background: var(--color-background-primary);\n box-shadow: var(--effect-elevation-elevated-md);\n}\n\n.message-composer__field {\n display: flex;\n flex-direction: column;\n gap: var(--dimension-space-050);\n padding: var(--dimension-space-100);\n border-radius: var(--dimension-radius-125);\n background: var(--color-background-primary);\n box-shadow: var(--effect-elevation-elevated-md);\n overflow: hidden;\n}\n\n.message-composer:not(.message-composer--error) .message-composer__field:focus-within {\n box-shadow
|
|
287
|
+
"content": "@import '../../utils/forced-colors.css';\n\n:host {\n display: block;\n width: 100%;\n box-sizing: border-box;\n -webkit-user-select: none;\n user-select: none;\n}\n\n.message-composer {\n display: flex;\n flex-direction: column;\n gap: var(--dimension-space-050);\n width: 100%;\n margin: 0;\n}\n\n.message-composer__attachments:empty,\n.message-composer__tools:empty,\n.message-composer__error-actions:empty {\n display: none;\n}\n\n.message-composer__error-support,\n.message-composer__error-message,\n.message-composer__error-actions {\n display: flex;\n align-items: center;\n}\n\n.message-composer__error-support {\n justify-content: space-between;\n flex-wrap: wrap;\n gap: var(--dimension-space-075) var(--dimension-space-100);\n min-width: 0;\n padding: var(--dimension-space-075) var(--dimension-space-100);\n background: var(--color-background-faint-negative);\n}\n\n.message-composer__error-message {\n flex: 1 1 var(--dimension-panel-width-xs);\n gap: var(--dimension-space-050);\n min-width: 0;\n color: var(--color-foreground-bold-negative);\n}\n\n.message-composer__error-actions {\n flex: 0 1 auto;\n flex-wrap: wrap;\n gap: var(--dimension-space-050);\n}\n\n.message-composer__stack {\n min-width: 0;\n}\n\n.message-composer--error .message-composer__stack {\n overflow: hidden;\n border: var(--dimension-stroke-width-012) solid var(--color-border-bold-negative);\n border-radius: var(--dimension-radius-125);\n background: var(--color-background-primary);\n box-shadow: var(--effect-elevation-elevated-md);\n}\n\n.message-composer__field {\n display: flex;\n flex-direction: column;\n gap: var(--dimension-space-050);\n padding: var(--dimension-space-100);\n border-radius: var(--dimension-radius-125);\n background: var(--color-background-primary);\n box-shadow: var(--effect-elevation-elevated-md);\n overflow: hidden;\n}\n\n.message-composer:not(.message-composer--error) .message-composer__field:focus-within {\n box-shadow:\n var(--effect-elevation-elevated-md),\n inset 0 0 0 var(--dimension-stroke-width-018) var(--color-border-bold-brand);\n}\n\n.message-composer--error .message-composer__field {\n border-radius: 0;\n box-shadow: none;\n}\n\ntextarea {\n field-sizing: content;\n width: 100%;\n min-height: calc(var(--typography-lineheight-md) * 2 + var(--dimension-space-150));\n max-height: calc(var(--typography-lineheight-md) * 6 + var(--dimension-space-150));\n padding: var(--dimension-space-075);\n border: none;\n outline: none;\n resize: none;\n overflow-y: auto;\n box-sizing: border-box;\n background: transparent;\n color: var(--color-foreground-primary);\n font-family: var(--typography-font-family-ui);\n font-size: var(--typography-fontsize-md);\n line-height: var(--typography-lineheight-md);\n font-weight: var(--typography-weight-regular);\n letter-spacing: var(--typography-letterspacing-negative-half);\n -webkit-user-select: text;\n user-select: text;\n}\n\ntextarea::placeholder {\n color: var(--color-foreground-secondary);\n}\n\ntextarea:focus::placeholder {\n color: var(--color-foreground-quaternary);\n}\n\ntextarea:focus,\ntextarea:focus-visible {\n outline: none;\n}\n\n.message-composer__tools {\n display: flex;\n align-items: center;\n gap: var(--dimension-space-050);\n min-height: var(--dimension-size-400);\n}\n\n.message-composer__controls {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--dimension-space-050);\n width: 100%;\n min-width: 0;\n}\n\n.message-composer__actions {\n display: flex;\n align-items: center;\n gap: var(--dimension-space-100);\n flex: 0 0 auto;\n margin-inline-start: auto;\n}\n\n@media (forced-colors: active) {\n .message-composer__field {\n border: var(--dimension-stroke-width-012) solid var(--ds-forced-color-control-boundary);\n box-shadow: none;\n }\n\n .message-composer--error .message-composer__field {\n border: 0;\n }\n\n .message-composer--error .message-composer__stack {\n border: var(--dimension-stroke-width-025) dashed var(--ds-forced-color-content);\n box-shadow: none;\n }\n\n .message-composer__error-support {\n border-block-end: var(--dimension-stroke-width-012) solid var(--ds-forced-color-content);\n background: var(--ds-forced-color-surface);\n }\n}\n",
|
|
288
288
|
"type": "registry:ui"
|
|
289
289
|
},
|
|
290
290
|
{
|
|
291
291
|
"path": "src/wc/components/MessageComposer/MessageComposer.tsx",
|
|
292
|
-
"content": "import {
|
|
292
|
+
"content": "import { Component, Element, Event, EventEmitter, h, Host, Method, Prop } from '@stencil/core';\nimport type { ButtonFilledIntent } from '../ButtonFilled/ButtonFilled';\nimport type { MessageComposerStatus } from '../conversation-types';\n\nlet composerId = 0;\n\n@Component({\n tag: 'ds-message-composer',\n styleUrl: 'MessageComposer.css',\n scoped: true,\n})\nexport class MessageComposer {\n @Element() el!: HTMLElement;\n\n @Prop({ mutable: true }) value: string = '';\n @Prop() placeholder: string = 'Write a message';\n @Prop() label: string = 'Message';\n @Prop() status: MessageComposerStatus = 'ready';\n /** Submission failure shown while status is error. */\n @Prop() errorMessage?: string;\n @Prop() isInactive: boolean = false;\n @Prop() submitIntent: ButtonFilledIntent = 'brand';\n\n @Event() dsInput!: EventEmitter<string>;\n @Event() dsSubmit!: EventEmitter<{ text: string }>;\n @Event() dsStop!: EventEmitter<void>;\n\n private textarea?: HTMLTextAreaElement;\n private readonly errorId = `ds-message-composer-error-${++composerId}`;\n\n @Method()\n async setFocus() {\n this.textarea?.focus();\n }\n\n private get streaming(): boolean {\n return this.status === 'submitted' || this.status === 'streaming';\n }\n\n private handleInput = (event: Event) => {\n this.value = (event.target as HTMLTextAreaElement).value;\n this.dsInput.emit(this.value);\n };\n\n private submit() {\n const text = this.value.trim();\n if (!text || this.isInactive || this.streaming) return;\n this.dsSubmit.emit({ text });\n }\n\n private handleKeyDown = (event: KeyboardEvent) => {\n if (event.key !== 'Enter' || event.shiftKey || event.isComposing) return;\n event.preventDefault();\n this.submit();\n };\n\n private handleAction = () => {\n if (this.streaming) this.dsStop.emit();\n else this.submit();\n };\n\n render() {\n const error = this.status === 'error';\n const visibleErrorMessage = this.errorMessage || 'Message could not be sent.';\n const actionInactive = !this.streaming && (this.isInactive || !this.value.trim());\n return (\n <Host>\n <form\n class={{ 'message-composer': true, 'message-composer--error': error }}\n aria-busy={this.streaming ? 'true' : undefined}\n onSubmit={(event: Event) => {\n event.preventDefault();\n this.submit();\n }}\n >\n <div class=\"message-composer__attachments\">\n <slot name=\"attachments\" />\n </div>\n <div class=\"message-composer__stack\">\n {error ? (\n <div class=\"message-composer__error-support\">\n <div id={this.errorId} class=\"message-composer__error-message\" role=\"alert\">\n <ds-icon name=\"ErrorTriangle\" size=\"xs\" color=\"inherit\" />\n <ds-text variant=\"text-body-small\" color=\"negative\">\n {visibleErrorMessage}\n </ds-text>\n </div>\n <div class=\"message-composer__error-actions\">\n <slot name=\"error-actions\" />\n </div>\n </div>\n ) : null}\n <div class=\"message-composer__field\">\n <textarea\n ref={element => {\n this.textarea = element;\n }}\n value={this.value}\n placeholder={this.placeholder}\n aria-label={this.label}\n aria-invalid={error ? 'true' : undefined}\n aria-describedby={error ? this.errorId : undefined}\n disabled={this.isInactive}\n onInput={this.handleInput}\n onKeyDown={this.handleKeyDown}\n />\n <div class=\"message-composer__controls\">\n <div class=\"message-composer__tools\">\n <slot name=\"tools\" />\n </div>\n <div class=\"message-composer__actions\">\n <slot name=\"actions\" />\n {actionInactive ? (\n <ds-tooltip label=\"Send message\" side=\"top\" size=\"sm\">\n <ds-button-unfilled\n class=\"message-composer__action\"\n variant=\"icon\"\n icon=\"ArrowUp\"\n size=\"md\"\n aria-label=\"Send message\"\n hasBorder\n isInactive\n onDsClick={this.handleAction}\n />\n </ds-tooltip>\n ) : (\n <ds-tooltip\n label={this.streaming ? 'Stop response' : 'Send message'}\n side=\"top\"\n size=\"sm\"\n >\n <ds-button-filled\n class=\"message-composer__action\"\n variant=\"icon\"\n icon={this.streaming ? 'SquareFilled' : 'ArrowUp'}\n intent={this.streaming ? 'brand' : this.submitIntent}\n contrast=\"bold\"\n size=\"md\"\n aria-label={this.streaming ? 'Stop response' : 'Send message'}\n onDsClick={this.handleAction}\n />\n </ds-tooltip>\n )}\n </div>\n </div>\n </div>\n </div>\n </form>\n </Host>\n );\n }\n}\n",
|
|
293
293
|
"type": "registry:ui"
|
|
294
294
|
}
|
|
295
295
|
]
|
|
@@ -217,17 +217,17 @@
|
|
|
217
217
|
"files": [
|
|
218
218
|
{
|
|
219
219
|
"path": "src/wc/components/MessageScroller/AgentChatReview.stories.css",
|
|
220
|
-
"content": "@import '../../utils/forced-colors.css';\n\n.agent-chat-review {\n display: grid;\n min-height: 100vh;\n place-items: center;\n padding: var(--dimension-space-200);\n background: var(--color-background-secondary);\n box-sizing: border-box;\n}\n\n.agent-chat-review__viewport {\n width: min(var(--agent-chat-review-width), 100%);\n height: min(
|
|
220
|
+
"content": "@import '../../utils/forced-colors.css';\n\n.agent-chat-review {\n display: grid;\n min-height: 100vh;\n place-items: center;\n padding: var(--dimension-space-200);\n background: var(--color-background-secondary);\n box-sizing: border-box;\n}\n\n.agent-chat-review__viewport {\n width: min(var(--agent-chat-review-width), 100%);\n height: min(var(--agent-chat-review-height), calc(100vh - var(--dimension-space-400)));\n min-height: var(--agent-chat-review-min-height);\n overflow: hidden;\n border: var(--dimension-stroke-width-012) solid var(--color-border-tertiary);\n border-radius: var(--dimension-radius-125);\n background: var(--color-background-primary);\n box-shadow: var(--effect-elevation-elevated-md);\n}\n\n.agent-chat-review__viewport--narrow {\n width: min(var(--agent-chat-review-width), 100%);\n height: min(var(--agent-chat-review-height), calc(100vh - var(--dimension-space-400)));\n}\n\n.agent-chat-review__viewport ds-message-scroller {\n --ds-message-scroller-content-max-width: var(--dimension-panel-width-xl);\n}\n\n.agent-chat-review__interaction {\n padding: var(--dimension-space-100) var(--dimension-space-100) 0;\n}\n\n.agent-chat-review__overlay {\n padding: var(--dimension-space-100);\n}\n\n.agent-chat-review__prose {\n display: grid;\n gap: var(--dimension-space-100);\n}\n\n.agent-chat-review__result-link {\n color: inherit;\n text-decoration: none;\n}\n\n@media (max-width: 480px) {\n .agent-chat-review {\n padding: var(--dimension-space-100);\n }\n\n .agent-chat-review__viewport,\n .agent-chat-review__viewport--narrow {\n width: 100%;\n height: calc(100vh - var(--dimension-space-200));\n min-height: var(--agent-chat-review-min-height);\n }\n}\n\n@media (forced-colors: active) {\n .agent-chat-review__viewport {\n border-color: var(--ds-forced-color-content);\n box-shadow: none;\n }\n}\n",
|
|
221
221
|
"type": "registry:ui"
|
|
222
222
|
},
|
|
223
223
|
{
|
|
224
224
|
"path": "src/wc/components/MessageScroller/MessageScroller.css",
|
|
225
|
-
"content": "@import '../../styles/control-elevation.css';\n\n:host {\n --ds-message-scroller-anchor-offset: var(--dimension-space-800);\n\n display: block;\n width: 100%;\n height: 100%;\n min-height: 0;\n box-sizing: border-box;\n}\n\n.message-scroller {\n position: relative;\n width: 100%;\n height: 100%;\n min-height: 0;\n}\n\n.message-scroller__viewport {\n width: 100%;\n height: 100%;\n min-height: 0;\n overflow: auto;\n overflow-anchor: none;\n overscroll-behavior: contain;\n box-sizing: border-box;\n}\n\n.message-scroller__viewport:focus-visible {\n outline: var(--dimension-stroke-width-025) solid var(--color-border-bold-brand);\n outline-offset: calc(-1 * var(--dimension-stroke-width-025));\n}\n\n.message-scroller__content {\n display: flex;\n flex-direction: column;\n gap: var(--dimension-space-300);\n width: 100%;\n max-width: var(
|
|
225
|
+
"content": "@import '../../styles/control-elevation.css';\n\n:host {\n --ds-message-scroller-anchor-offset: var(--dimension-space-800);\n\n display: block;\n width: 100%;\n height: 100%;\n min-height: 0;\n box-sizing: border-box;\n}\n\n.message-scroller {\n position: relative;\n width: 100%;\n height: 100%;\n min-height: 0;\n}\n\n.message-scroller__viewport {\n width: 100%;\n height: 100%;\n min-height: 0;\n overflow: auto;\n overflow-anchor: none;\n overscroll-behavior: contain;\n box-sizing: border-box;\n}\n\n.message-scroller__viewport:focus-visible {\n outline: var(--dimension-stroke-width-025) solid var(--color-border-bold-brand);\n outline-offset: calc(-1 * var(--dimension-stroke-width-025));\n}\n\n.message-scroller__content {\n display: flex;\n flex-direction: column;\n gap: var(--dimension-space-300);\n width: 100%;\n max-width: var(--ds-message-scroller-content-max-width, var(--dimension-panel-width-2xl));\n min-height: 100%;\n margin-inline: auto;\n padding-block-start: var(--dimension-space-200);\n padding-inline: var(--dimension-space-200);\n padding-block-end: max(\n calc(var(--dimension-space-200) + var(--ds-scroll-overlay-block-size, 0px)),\n var(--ds-message-scroller-turn-clearance, 0px)\n );\n box-sizing: border-box;\n}\n\n.message-scroller__overlay {\n position: absolute;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: var(--dimension-z-index-floating);\n}\n\n.message-scroller__overlay-stack {\n display: grid;\n width: 100%;\n}\n\n.message-scroller__interaction,\n.message-scroller__overlay-content {\n min-width: 0;\n}\n\n.message-scroller__control {\n position: absolute;\n bottom: calc(var(--ds-scroll-overlay-content-block-size, 0px) + var(--dimension-space-200));\n left: 50%;\n z-index: var(--dimension-z-index-floating);\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: var(--dimension-radius-half);\n background: var(--color-background-transparent);\n transform: translateX(-50%);\n -webkit-backdrop-filter: blur(var(--effect-blur-sm));\n backdrop-filter: blur(var(--effect-blur-sm));\n}\n",
|
|
226
226
|
"type": "registry:ui"
|
|
227
227
|
},
|
|
228
228
|
{
|
|
229
229
|
"path": "src/wc/components/MessageScroller/MessageScroller.tsx",
|
|
230
|
-
"content": "import {\n Component,\n Element,\n Event,\n EventEmitter,\n h,\n Host,\n Method,\n Prop,\n State,\n} from '@stencil/core';\nimport type { MessageScrollerPosition } from '../conversation-types';\nimport { ScrollOverlayController } from '../../utils/scroll-overlay-controller';\nimport { resolveCssLengthPx } from '../../utils/resolve-css-length-px';\n\nconst LIVE_EDGE_THRESHOLD = 24;\n\n@Component({\n tag: 'ds-message-scroller',\n styleUrl: 'MessageScroller.css',\n styleUrls: ['../../utils/scroll-edge-fade.css'],\n scoped: true,\n})\nexport class MessageScroller {\n @Element() el!: HTMLElement;\n\n @Prop() busy: boolean = false;\n @Prop() autoFollow: boolean = true;\n @Prop() defaultPosition: MessageScrollerPosition = 'last-anchor';\n @Prop() messagesLabel: string = 'Messages';\n\n @Event() dsReachStart!: EventEmitter<void>;\n\n @State() private showScrollToLatest = false;\n\n private viewport?: HTMLElement;\n private content?: HTMLElement;\n private overlay?: HTMLElement;\n private transcriptSlot?: HTMLSlotElement;\n private scrollOverlayController?: ScrollOverlayController;\n private mutationObserver?: MutationObserver;\n private following = true;\n private followReleased = false;\n private releasePendingAtLiveEdge = false;\n private atStart = false;\n private programmatic = false;\n private programmaticTimer?: ReturnType<typeof setTimeout>;\n private transcriptChildren: HTMLElement[] = [];\n private knownTranscriptElements = new Set<HTMLElement>();\n private knownTurnAnchors = new Set<HTMLElement>();\n private firstChildViewportTop?: number;\n private activeTurnAnchor?: HTMLElement;\n private activeTurnAnchorViewportTop?: number;\n private turnClearance = 0;\n private prependReconciliation = 0;\n\n componentDidLoad() {\n this.transcriptChildren = this.getTranscriptChildren();\n this.transcriptChildren.forEach(element => {\n this.knownTranscriptElements.add(element);\n if (element.matches('ds-message[scroll-anchor]')) {\n this.knownTurnAnchors.add(element);\n }\n });\n this.rememberFirstChildTop();\n this.connectObservers();\n requestAnimationFrame(() => this.applyDefaultPosition());\n document.addEventListener('selectionchange', this.handleSelectionChange);\n }\n\n disconnectedCallback() {\n this.prependReconciliation += 1;\n this.scrollOverlayController?.disconnect();\n this.mutationObserver?.disconnect();\n if (this.programmaticTimer) clearTimeout(this.programmaticTimer);\n document.removeEventListener('selectionchange', this.handleSelectionChange);\n }\n\n @Method()\n async scrollToMessage(id: string): Promise<boolean> {\n const message = Array.from(\n this.el.querySelectorAll<HTMLElement & { messageId?: string }>('ds-message')\n ).find(item => item.messageId === id || item.getAttribute('message-id') === id);\n if (!message) return false;\n this.releasePendingAtLiveEdge = false;\n this.following = false;\n this.followReleased = true;\n this.showScrollToLatest = true;\n message.scrollIntoView({ block: 'start', behavior: this.motionBehavior() });\n return true;\n }\n\n @Method()\n async scrollToStart() {\n this.releasePendingAtLiveEdge = false;\n this.scrollTo(0);\n this.following = false;\n this.followReleased = true;\n this.showScrollToLatest = Boolean(\n this.viewport && this.viewport.scrollHeight > this.viewport.clientHeight\n );\n }\n\n @Method()\n async scrollToEnd() {\n this.releasePendingAtLiveEdge = false;\n this.following = true;\n this.followReleased = false;\n this.showScrollToLatest = false;\n this.scrollTo(this.viewport?.scrollHeight ?? 0);\n }\n\n private motionBehavior(): ScrollBehavior {\n return matchMedia('(prefers-reduced-motion: reduce)').matches ? 'auto' : 'smooth';\n }\n\n private scrollTo(top: number) {\n if (!this.viewport) return;\n this.programmatic = true;\n const behavior = this.motionBehavior();\n this.viewport.scrollTo({ top, behavior });\n if (this.programmaticTimer) clearTimeout(this.programmaticTimer);\n this.programmaticTimer = setTimeout(() => {\n this.programmatic = false;\n this.programmaticTimer = undefined;\n if (this.following) {\n this.viewport!.scrollTop = top;\n this.showScrollToLatest = false;\n }\n }, behavior === 'smooth' ? 600 : 0);\n }\n\n private connectObservers() {\n if (!this.viewport || !this.content || !this.overlay) return;\n this.scrollOverlayController = new ScrollOverlayController({\n host: this.el,\n viewport: this.viewport,\n content: this.content,\n overlay: this.overlay,\n overlayContentSelector: '.message-scroller__overlay-stack',\n onGeometryChange: () => this.handleContentGrowth(),\n });\n this.scrollOverlayController.connect();\n this.mutationObserver = new MutationObserver(() => {\n this.handleTranscriptChange();\n this.handleContentGrowth();\n });\n this.mutationObserver.observe(this.el, {\n childList: true,\n subtree: true,\n characterData: true,\n });\n }\n\n private handleContentGrowth() {\n if (!this.viewport) return;\n this.syncTurnClearance();\n const nextHeight = this.viewport.scrollHeight;\n if (this.autoFollow && this.following && !this.followReleased) {\n this.viewport.scrollTop = nextHeight;\n }\n if (this.releasePendingAtLiveEdge && !this.isAtLiveEdge()) {\n this.releasePendingAtLiveEdge = false;\n }\n this.showScrollToLatest = !this.following && !this.isAtLiveEdge();\n this.scrollOverlayController?.sync();\n }\n\n private rememberFirstChildTop() {\n if (!this.viewport) return;\n const first = this.getTranscriptChildren()[0];\n this.firstChildViewportTop = first\n ? first.getBoundingClientRect().top - this.viewport.getBoundingClientRect().top\n : undefined;\n }\n\n private getTranscriptChildren(): HTMLElement[] {\n const assigned = this.transcriptSlot?.assignedElements() ?? [];\n const assignedTranscript = assigned.filter(\n (element): element is HTMLElement =>\n element instanceof HTMLElement &&\n (element.matches('ds-message') || element.matches('ds-agent-response')),\n );\n const queriedTranscript = Array.from(\n this.el.querySelectorAll<HTMLElement>('ds-message, ds-agent-response'),\n ).filter(element => {\n if (\n element.closest(\n '.message-scroller__interaction, .message-scroller__overlay-content',\n )\n ) {\n return false;\n }\n if (element.matches('ds-agent-response')) return true;\n return !element.closest('ds-agent-response');\n });\n return queriedTranscript.length > assignedTranscript.length\n ? queriedTranscript\n : assignedTranscript;\n }\n\n private handleTranscriptChange = () => {\n const current = this.getTranscriptChildren();\n const previous = this.transcriptChildren;\n const hadKnownTranscript = this.knownTranscriptElements.size > 0;\n const previouslyKnown = new Set(this.knownTranscriptElements);\n const appendedAnchor = [...current].reverse().find((element, reverseIndex) => {\n if (\n !element.matches('ds-message[scroll-anchor]') ||\n this.knownTurnAnchors.has(element)\n ) {\n return false;\n }\n const index = current.length - reverseIndex - 1;\n return current\n .slice(index + 1)\n .every(next => !this.knownTranscriptElements.has(next));\n });\n current.forEach(element => {\n this.knownTranscriptElements.add(element);\n if (element.matches('ds-message[scroll-anchor]')) {\n this.knownTurnAnchors.add(element);\n }\n });\n if (!previous.length) {\n this.transcriptChildren = current;\n if (appendedAnchor && hadKnownTranscript) {\n requestAnimationFrame(() => this.positionNewTurn(appendedAnchor));\n }\n return;\n }\n\n const firstKnownIndex = current.findIndex(element => previouslyKnown.has(element));\n const newTranscript = current.filter(element => !previouslyKnown.has(element));\n const prepended =\n (current.length > previous.length && previous.every(\n (element, index) => current[current.length - previous.length + index] === element,\n )) ||\n (!appendedAnchor &&\n newTranscript.length > 0 &&\n firstKnownIndex > 0 &&\n newTranscript.every(element => current.indexOf(element) < firstKnownIndex));\n\n this.transcriptChildren = current;\n if (prepended) {\n if (this.viewport) {\n const preservedElement = this.activeTurnAnchor?.isConnected\n ? this.activeTurnAnchor\n : previous[0];\n const preservedTop = this.activeTurnAnchor?.isConnected\n ? this.activeTurnAnchorViewportTop\n : this.firstChildViewportTop;\n if (preservedElement && preservedTop !== undefined) {\n this.reconcilePrependPosition(preservedElement, preservedTop);\n this.schedulePrependReconciliation(\n preservedElement,\n preservedTop,\n newTranscript,\n );\n }\n }\n this.rememberFirstChildTop();\n this.rememberActiveTurnAnchorTop();\n this.scrollOverlayController?.sync();\n return;\n }\n if (appendedAnchor) {\n requestAnimationFrame(() => this.positionNewTurn(appendedAnchor));\n }\n };\n\n private reconcilePrependPosition(element: HTMLElement, viewportTop: number) {\n if (!this.viewport || !element.isConnected) return;\n const nextTop =\n element.getBoundingClientRect().top - this.viewport.getBoundingClientRect().top;\n this.viewport.scrollTop += nextTop - viewportTop;\n }\n\n private schedulePrependReconciliation(\n element: HTMLElement,\n viewportTop: number,\n prepended: HTMLElement[],\n ) {\n const generation = ++this.prependReconciliation;\n // Prepended custom elements can gain height after their child-list mutation.\n // Reconcile against the original coordinate after their layouts settle.\n const pending = prepended\n .flatMap(item => [item, ...Array.from(item.querySelectorAll<HTMLElement>('*'))])\n .map(item => {\n const componentOnReady = (\n item as HTMLElement & { componentOnReady?: () => Promise<unknown> }\n ).componentOnReady;\n return componentOnReady ? componentOnReady.call(item).catch(() => undefined) : null;\n })\n .filter((promise): promise is Promise<unknown> => Boolean(promise));\n\n void Promise.all(pending).then(() => {\n requestAnimationFrame(() => {\n if (generation !== this.prependReconciliation) return;\n this.reconcilePrependPosition(element, viewportTop);\n requestAnimationFrame(() => {\n if (generation !== this.prependReconciliation) return;\n this.reconcilePrependPosition(element, viewportTop);\n this.rememberFirstChildTop();\n this.rememberActiveTurnAnchorTop();\n this.scrollOverlayController?.sync();\n });\n });\n });\n }\n\n private handleTranscriptSlotChange = () => this.handleTranscriptChange();\n\n private positionNewTurn(anchor: HTMLElement) {\n if (!this.viewport) return;\n this.activeTurnAnchor = anchor;\n this.syncTurnClearance();\n requestAnimationFrame(() => this.finishPositionNewTurn(anchor));\n }\n\n private finishPositionNewTurn(anchor: HTMLElement) {\n if (!this.viewport || !anchor.isConnected) return;\n const viewportRect = this.viewport.getBoundingClientRect();\n const anchorTop =\n anchor.getBoundingClientRect().top - viewportRect.top + this.viewport.scrollTop;\n const customOffset = getComputedStyle(this.el)\n .getPropertyValue('--ds-message-scroller-anchor-offset')\n .trim();\n const offset = resolveCssLengthPx(customOffset, 'var(--dimension-space-800)');\n const max = Math.max(this.viewport.scrollHeight - this.viewport.clientHeight, 0);\n const target = Math.min(Math.max(anchorTop - offset, 0), max);\n this.following = max - target <= LIVE_EDGE_THRESHOLD;\n this.followReleased = !this.following;\n this.showScrollToLatest = !this.following;\n this.programmatic = true;\n this.viewport.scrollTop = target;\n requestAnimationFrame(() => {\n this.programmatic = false;\n this.following = this.isAtLiveEdge();\n this.followReleased = !this.following;\n this.showScrollToLatest = !this.following;\n this.rememberFirstChildTop();\n this.rememberActiveTurnAnchorTop();\n });\n this.scrollOverlayController?.sync();\n }\n\n private rememberActiveTurnAnchorTop() {\n if (!this.viewport || !this.activeTurnAnchor?.isConnected) return;\n this.activeTurnAnchorViewportTop =\n this.activeTurnAnchor.getBoundingClientRect().top -\n this.viewport.getBoundingClientRect().top;\n }\n\n private syncTurnClearance() {\n if (!this.viewport || !this.activeTurnAnchor?.isConnected) return;\n const children = this.getTranscriptChildren();\n const last = children[children.length - 1];\n if (!last) return;\n const anchorRect = this.activeTurnAnchor.getBoundingClientRect();\n const lastRect = last.getBoundingClientRect();\n const customOffset = getComputedStyle(this.el)\n .getPropertyValue('--ds-message-scroller-anchor-offset')\n .trim();\n const offset = resolveCssLengthPx(customOffset, 'var(--dimension-space-800)');\n const contentAfterAnchor = Math.max(lastRect.bottom - anchorRect.top, 0);\n const clearance = Math.max(\n Math.ceil(this.viewport.clientHeight - offset - contentAfterAnchor),\n 0,\n );\n if (clearance === this.turnClearance) return;\n this.turnClearance = clearance;\n this.el.style.setProperty('--ds-message-scroller-turn-clearance', `${clearance}px`);\n }\n\n private applyDefaultPosition() {\n if (!this.viewport) return;\n this.releasePendingAtLiveEdge = false;\n if (this.defaultPosition === 'start') {\n this.viewport.scrollTop = 0;\n this.following = false;\n this.followReleased = true;\n this.showScrollToLatest = this.viewport.scrollHeight > this.viewport.clientHeight;\n this.scrollOverlayController?.sync();\n return;\n }\n if (this.defaultPosition === 'last-anchor') {\n const anchors = this.el.querySelectorAll<HTMLElement>('ds-message[scroll-anchor]');\n const last = anchors.item(anchors.length - 1);\n if (last) {\n last.scrollIntoView({ block: 'start' });\n this.following = this.isAtLiveEdge();\n this.followReleased = !this.following;\n this.showScrollToLatest = !this.following;\n this.scrollOverlayController?.sync();\n return;\n }\n }\n this.viewport.scrollTop = this.viewport.scrollHeight;\n this.following = true;\n this.followReleased = false;\n this.showScrollToLatest = false;\n this.scrollOverlayController?.sync();\n }\n\n private isAtLiveEdge(): boolean {\n if (!this.viewport) return true;\n return (\n this.viewport.scrollHeight - this.viewport.clientHeight - this.viewport.scrollTop <=\n LIVE_EDGE_THRESHOLD\n );\n }\n\n private handleScroll = () => {\n if (!this.viewport) return;\n this.scrollOverlayController?.sync();\n this.rememberFirstChildTop();\n this.rememberActiveTurnAnchorTop();\n if (this.programmatic) return;\n const atLiveEdge = this.isAtLiveEdge();\n if (this.releasePendingAtLiveEdge && atLiveEdge) {\n // A scroll event queued by the previous auto-follow can arrive after\n // reader input. Keep that stale event from restoring follow mode.\n this.following = false;\n this.followReleased = true;\n this.showScrollToLatest = false;\n return;\n }\n this.releasePendingAtLiveEdge = false;\n this.following = atLiveEdge;\n this.followReleased = !this.following;\n this.showScrollToLatest = !this.following;\n const nextAtStart = this.viewport.scrollTop <= LIVE_EDGE_THRESHOLD;\n if (nextAtStart && !this.atStart) this.dsReachStart.emit();\n this.atStart = nextAtStart;\n };\n\n private releaseFollow = () => {\n this.prependReconciliation += 1;\n this.releasePendingAtLiveEdge = this.isAtLiveEdge();\n this.following = false;\n this.followReleased = true;\n this.showScrollToLatest = !this.releasePendingAtLiveEdge;\n };\n\n private handleSelectionChange = () => {\n const selection = document.getSelection();\n if (\n selection &&\n !selection.isCollapsed &&\n selection.anchorNode &&\n this.el.contains(selection.anchorNode)\n ) {\n this.releaseFollow();\n }\n };\n\n render() {\n return (\n <Host>\n <div class=\"message-scroller\">\n <div\n class=\"message-scroller__viewport\"\n ref={element => {\n this.viewport = element;\n }}\n role=\"region\"\n aria-label={this.messagesLabel}\n tabIndex={0}\n onScroll={this.handleScroll}\n onPointerDown={(event: PointerEvent) => {\n if (event.pointerType === 'touch') this.releaseFollow();\n }}\n onWheel={this.releaseFollow}\n onKeyDown={(event: KeyboardEvent) => {\n if (\n ['ArrowUp', 'ArrowDown', 'PageUp', 'PageDown', 'Home', 'End', ' '].includes(\n event.key,\n )\n ) {\n this.releaseFollow();\n }\n }}\n >\n <div\n class=\"message-scroller__content scroll-edge-fade--block-window\"\n ref={element => {\n this.content = element;\n }}\n role=\"log\"\n aria-live=\"polite\"\n aria-relevant=\"additions\"\n aria-busy={this.busy ? 'true' : undefined}\n >\n <slot\n ref={element => {\n this.transcriptSlot = element;\n }}\n onSlotchange={this.handleTranscriptSlotChange}\n />\n </div>\n </div>\n <div\n class=\"message-scroller__overlay\"\n ref={element => {\n this.overlay = element;\n }}\n >\n <div class=\"message-scroller__overlay-stack\">\n <div class=\"message-scroller__interaction\">\n <slot name=\"interaction\" />\n </div>\n <div class=\"message-scroller__overlay-content\">\n <slot name=\"overlay\" />\n </div>\n </div>\n </div>\n {this.showScrollToLatest ? (\n <div class=\"message-scroller__control ds-control-elevation ds-control-elevation--md ds-control-elevation--press-scale\">\n <ds-tooltip label=\"Scroll to latest message\" side=\"top\" size=\"sm\">\n <ds-button-unfilled\n variant=\"icon\"\n icon=\"ChevronDown\"\n size=\"md\"\n rounded\n hasBorder={false}\n aria-label=\"Scroll to latest message\"\n onDsClick={() => void this.scrollToEnd()}\n />\n </ds-tooltip>\n </div>\n ) : null}\n </div>\n </Host>\n );\n }\n}\n",
|
|
230
|
+
"content": "import {\n Component,\n Element,\n Event,\n EventEmitter,\n h,\n Host,\n Method,\n Prop,\n State,\n} from '@stencil/core';\nimport type { MessageScrollerPosition } from '../conversation-types';\nimport { ScrollOverlayController } from '../../utils/scroll-overlay-controller';\nimport { resolveCssLengthPx } from '../../utils/resolve-css-length-px';\n\nconst LIVE_EDGE_THRESHOLD = 24;\n\n@Component({\n tag: 'ds-message-scroller',\n styleUrl: 'MessageScroller.css',\n styleUrls: ['../../utils/scroll-edge-fade.css'],\n scoped: true,\n})\nexport class MessageScroller {\n @Element() el!: HTMLElement;\n\n @Prop() busy: boolean = false;\n @Prop() autoFollow: boolean = true;\n @Prop() defaultPosition: MessageScrollerPosition = 'last-anchor';\n @Prop() messagesLabel: string = 'Messages';\n\n @Event() dsReachStart!: EventEmitter<void>;\n\n @State() private showScrollToLatest = false;\n\n private viewport?: HTMLElement;\n private content?: HTMLElement;\n private overlay?: HTMLElement;\n private transcriptSlot?: HTMLSlotElement;\n private scrollOverlayController?: ScrollOverlayController;\n private mutationObserver?: MutationObserver;\n private following = true;\n private followReleased = false;\n private releasePendingAtLiveEdge = false;\n private atStart = false;\n private programmatic = false;\n private programmaticTimer?: ReturnType<typeof setTimeout>;\n private transcriptChildren: HTMLElement[] = [];\n private knownTranscriptElements = new Set<HTMLElement>();\n private knownTurnAnchors = new Set<HTMLElement>();\n private firstChildViewportTop?: number;\n private activeTurnAnchor?: HTMLElement;\n private activeTurnAnchorViewportTop?: number;\n private turnClearance = 0;\n private prependReconciliation = 0;\n\n componentDidLoad() {\n this.transcriptChildren = this.getTranscriptChildren();\n this.transcriptChildren.forEach(element => {\n this.knownTranscriptElements.add(element);\n if (element.matches('ds-message[scroll-anchor]')) {\n this.knownTurnAnchors.add(element);\n }\n });\n this.rememberFirstChildTop();\n this.connectObservers();\n requestAnimationFrame(() => this.applyDefaultPosition());\n document.addEventListener('selectionchange', this.handleSelectionChange);\n }\n\n disconnectedCallback() {\n this.prependReconciliation += 1;\n this.scrollOverlayController?.disconnect();\n this.mutationObserver?.disconnect();\n if (this.programmaticTimer) clearTimeout(this.programmaticTimer);\n document.removeEventListener('selectionchange', this.handleSelectionChange);\n }\n\n @Method()\n async scrollToMessage(id: string): Promise<boolean> {\n const message = Array.from(\n this.el.querySelectorAll<HTMLElement & { messageId?: string }>('ds-message')\n ).find(item => item.messageId === id || item.getAttribute('message-id') === id);\n if (!message) return false;\n this.releasePendingAtLiveEdge = false;\n this.following = false;\n this.followReleased = true;\n this.showScrollToLatest = true;\n message.scrollIntoView({ block: 'start', behavior: this.motionBehavior() });\n return true;\n }\n\n @Method()\n async scrollToStart() {\n this.releasePendingAtLiveEdge = false;\n this.scrollTo(0);\n this.following = false;\n this.followReleased = true;\n this.showScrollToLatest = Boolean(\n this.viewport && this.viewport.scrollHeight > this.viewport.clientHeight\n );\n }\n\n @Method()\n async scrollToEnd() {\n this.releasePendingAtLiveEdge = false;\n this.following = true;\n this.followReleased = false;\n this.showScrollToLatest = false;\n this.scrollTo(this.viewport?.scrollHeight ?? 0);\n }\n\n private motionBehavior(): ScrollBehavior {\n return matchMedia('(prefers-reduced-motion: reduce)').matches ? 'auto' : 'smooth';\n }\n\n private scrollTo(top: number) {\n if (!this.viewport) return;\n this.programmatic = true;\n const behavior = this.motionBehavior();\n this.viewport.scrollTo({ top, behavior });\n if (this.programmaticTimer) clearTimeout(this.programmaticTimer);\n this.programmaticTimer = setTimeout(\n () => {\n this.programmatic = false;\n this.programmaticTimer = undefined;\n if (this.following) {\n this.viewport!.scrollTop = top;\n this.showScrollToLatest = false;\n }\n },\n behavior === 'smooth' ? 600 : 0\n );\n }\n\n private connectObservers() {\n if (!this.viewport || !this.content || !this.overlay) return;\n this.scrollOverlayController = new ScrollOverlayController({\n host: this.el,\n viewport: this.viewport,\n content: this.content,\n overlay: this.overlay,\n overlayContentSelector: '.message-scroller__overlay-stack',\n onGeometryChange: () => this.handleContentGrowth(),\n });\n this.scrollOverlayController.connect();\n this.mutationObserver = new MutationObserver(() => {\n this.handleTranscriptChange();\n this.handleContentGrowth();\n });\n this.mutationObserver.observe(this.el, {\n childList: true,\n subtree: true,\n characterData: true,\n });\n }\n\n private handleContentGrowth() {\n if (!this.viewport) return;\n this.syncTurnClearance();\n const nextHeight = this.viewport.scrollHeight;\n if (this.autoFollow && this.following && !this.followReleased) {\n this.viewport.scrollTop = nextHeight;\n }\n if (this.releasePendingAtLiveEdge && !this.isAtLiveEdge()) {\n this.releasePendingAtLiveEdge = false;\n }\n this.showScrollToLatest = !this.following && !this.isAtLiveEdge();\n this.scrollOverlayController?.sync();\n }\n\n private rememberFirstChildTop() {\n if (!this.viewport) return;\n const first = this.getTranscriptChildren()[0];\n this.firstChildViewportTop = first\n ? first.getBoundingClientRect().top - this.viewport.getBoundingClientRect().top\n : undefined;\n }\n\n private getTranscriptChildren(): HTMLElement[] {\n const assigned = this.transcriptSlot?.assignedElements() ?? [];\n const assignedTranscript = assigned.filter(\n (element): element is HTMLElement =>\n element instanceof HTMLElement &&\n (element.matches('ds-message') || element.matches('ds-agent-response'))\n );\n const queriedTranscript = Array.from(\n this.el.querySelectorAll<HTMLElement>('ds-message, ds-agent-response')\n ).filter(element => {\n if (element.closest('.message-scroller__interaction, .message-scroller__overlay-content')) {\n return false;\n }\n if (element.matches('ds-agent-response')) return true;\n return !element.closest('ds-agent-response');\n });\n return queriedTranscript.length > assignedTranscript.length\n ? queriedTranscript\n : assignedTranscript;\n }\n\n private handleTranscriptChange = () => {\n const current = this.getTranscriptChildren();\n const previous = this.transcriptChildren;\n const hadKnownTranscript = this.knownTranscriptElements.size > 0;\n const previouslyKnown = new Set(this.knownTranscriptElements);\n const appendedAnchor = [...current].reverse().find((element, reverseIndex) => {\n if (!element.matches('ds-message[scroll-anchor]') || this.knownTurnAnchors.has(element)) {\n return false;\n }\n const index = current.length - reverseIndex - 1;\n return current.slice(index + 1).every(next => !this.knownTranscriptElements.has(next));\n });\n current.forEach(element => {\n this.knownTranscriptElements.add(element);\n if (element.matches('ds-message[scroll-anchor]')) {\n this.knownTurnAnchors.add(element);\n }\n });\n if (!previous.length) {\n this.transcriptChildren = current;\n if (appendedAnchor && hadKnownTranscript) {\n requestAnimationFrame(() => this.positionNewTurn(appendedAnchor));\n }\n return;\n }\n\n const firstKnownIndex = current.findIndex(element => previouslyKnown.has(element));\n const newTranscript = current.filter(element => !previouslyKnown.has(element));\n const prepended =\n (current.length > previous.length &&\n previous.every(\n (element, index) => current[current.length - previous.length + index] === element\n )) ||\n (!appendedAnchor &&\n newTranscript.length > 0 &&\n firstKnownIndex > 0 &&\n newTranscript.every(element => current.indexOf(element) < firstKnownIndex));\n\n this.transcriptChildren = current;\n if (prepended) {\n if (this.viewport) {\n const preservedElement = this.activeTurnAnchor?.isConnected\n ? this.activeTurnAnchor\n : previous[0];\n const preservedTop = this.activeTurnAnchor?.isConnected\n ? this.activeTurnAnchorViewportTop\n : this.firstChildViewportTop;\n if (preservedElement && preservedTop !== undefined) {\n this.reconcilePrependPosition(preservedElement, preservedTop);\n this.schedulePrependReconciliation(preservedElement, preservedTop, newTranscript);\n }\n }\n this.rememberFirstChildTop();\n this.rememberActiveTurnAnchorTop();\n this.scrollOverlayController?.sync();\n return;\n }\n if (appendedAnchor) {\n requestAnimationFrame(() => this.positionNewTurn(appendedAnchor));\n }\n };\n\n private reconcilePrependPosition(element: HTMLElement, viewportTop: number) {\n if (!this.viewport || !element.isConnected) return;\n const nextTop = element.getBoundingClientRect().top - this.viewport.getBoundingClientRect().top;\n this.viewport.scrollTop += nextTop - viewportTop;\n }\n\n private schedulePrependReconciliation(\n element: HTMLElement,\n viewportTop: number,\n prepended: HTMLElement[]\n ) {\n const generation = ++this.prependReconciliation;\n // Prepended custom elements can gain height after their child-list mutation.\n // Reconcile against the original coordinate after their layouts settle.\n const pending = prepended\n .flatMap(item => [item, ...Array.from(item.querySelectorAll<HTMLElement>('*'))])\n .map(item => {\n const componentOnReady = (\n item as HTMLElement & { componentOnReady?: () => Promise<unknown> }\n ).componentOnReady;\n return componentOnReady ? componentOnReady.call(item).catch(() => undefined) : null;\n })\n .filter((promise): promise is Promise<unknown> => Boolean(promise));\n\n void Promise.all(pending).then(() => {\n requestAnimationFrame(() => {\n if (generation !== this.prependReconciliation) return;\n this.reconcilePrependPosition(element, viewportTop);\n requestAnimationFrame(() => {\n if (generation !== this.prependReconciliation) return;\n this.reconcilePrependPosition(element, viewportTop);\n this.rememberFirstChildTop();\n this.rememberActiveTurnAnchorTop();\n this.scrollOverlayController?.sync();\n });\n });\n });\n }\n\n private handleTranscriptSlotChange = () => this.handleTranscriptChange();\n\n private positionNewTurn(anchor: HTMLElement) {\n if (!this.viewport) return;\n this.activeTurnAnchor = anchor;\n this.syncTurnClearance();\n requestAnimationFrame(() => this.finishPositionNewTurn(anchor));\n }\n\n private finishPositionNewTurn(anchor: HTMLElement) {\n if (!this.viewport || !anchor.isConnected) return;\n const viewportRect = this.viewport.getBoundingClientRect();\n const anchorTop =\n anchor.getBoundingClientRect().top - viewportRect.top + this.viewport.scrollTop;\n const customOffset = getComputedStyle(this.el)\n .getPropertyValue('--ds-message-scroller-anchor-offset')\n .trim();\n const offset = resolveCssLengthPx(customOffset, 'var(--dimension-space-800)');\n const max = Math.max(this.viewport.scrollHeight - this.viewport.clientHeight, 0);\n const target = Math.min(Math.max(anchorTop - offset, 0), max);\n this.following = max - target <= LIVE_EDGE_THRESHOLD;\n this.followReleased = !this.following;\n this.showScrollToLatest = !this.following;\n this.programmatic = true;\n this.viewport.scrollTop = target;\n requestAnimationFrame(() => {\n this.programmatic = false;\n this.following = this.isAtLiveEdge();\n this.followReleased = !this.following;\n this.showScrollToLatest = !this.following;\n this.rememberFirstChildTop();\n this.rememberActiveTurnAnchorTop();\n });\n this.scrollOverlayController?.sync();\n }\n\n private rememberActiveTurnAnchorTop() {\n if (!this.viewport || !this.activeTurnAnchor?.isConnected) return;\n this.activeTurnAnchorViewportTop =\n this.activeTurnAnchor.getBoundingClientRect().top - this.viewport.getBoundingClientRect().top;\n }\n\n private syncTurnClearance() {\n if (!this.viewport || !this.activeTurnAnchor?.isConnected) return;\n const children = this.getTranscriptChildren();\n const last = children[children.length - 1];\n if (!last) return;\n const anchorRect = this.activeTurnAnchor.getBoundingClientRect();\n const lastRect = last.getBoundingClientRect();\n const customOffset = getComputedStyle(this.el)\n .getPropertyValue('--ds-message-scroller-anchor-offset')\n .trim();\n const offset = resolveCssLengthPx(customOffset, 'var(--dimension-space-800)');\n const contentAfterAnchor = Math.max(lastRect.bottom - anchorRect.top, 0);\n const clearance = Math.max(\n Math.ceil(this.viewport.clientHeight - offset - contentAfterAnchor),\n 0\n );\n if (clearance === this.turnClearance) return;\n this.turnClearance = clearance;\n this.el.style.setProperty('--ds-message-scroller-turn-clearance', `${clearance}px`);\n }\n\n private applyDefaultPosition() {\n if (!this.viewport) return;\n this.releasePendingAtLiveEdge = false;\n if (this.defaultPosition === 'start') {\n this.viewport.scrollTop = 0;\n this.following = false;\n this.followReleased = true;\n this.showScrollToLatest = this.viewport.scrollHeight > this.viewport.clientHeight;\n this.scrollOverlayController?.sync();\n return;\n }\n if (this.defaultPosition === 'last-anchor') {\n const anchors = this.el.querySelectorAll<HTMLElement>('ds-message[scroll-anchor]');\n const last = anchors.item(anchors.length - 1);\n if (last) {\n last.scrollIntoView({ block: 'start' });\n this.following = this.isAtLiveEdge();\n this.followReleased = !this.following;\n this.showScrollToLatest = !this.following;\n this.scrollOverlayController?.sync();\n return;\n }\n }\n this.viewport.scrollTop = this.viewport.scrollHeight;\n this.following = true;\n this.followReleased = false;\n this.showScrollToLatest = false;\n this.scrollOverlayController?.sync();\n }\n\n private isAtLiveEdge(): boolean {\n if (!this.viewport) return true;\n return (\n this.viewport.scrollHeight - this.viewport.clientHeight - this.viewport.scrollTop <=\n LIVE_EDGE_THRESHOLD\n );\n }\n\n private handleScroll = () => {\n if (!this.viewport) return;\n this.scrollOverlayController?.sync();\n this.rememberFirstChildTop();\n this.rememberActiveTurnAnchorTop();\n if (this.programmatic) return;\n const atLiveEdge = this.isAtLiveEdge();\n if (this.releasePendingAtLiveEdge && atLiveEdge) {\n // A scroll event queued by the previous auto-follow can arrive after\n // reader input. Keep that stale event from restoring follow mode.\n this.following = false;\n this.followReleased = true;\n this.showScrollToLatest = false;\n return;\n }\n this.releasePendingAtLiveEdge = false;\n this.following = atLiveEdge;\n this.followReleased = !this.following;\n this.showScrollToLatest = !this.following;\n const nextAtStart = this.viewport.scrollTop <= LIVE_EDGE_THRESHOLD;\n if (nextAtStart && !this.atStart) this.dsReachStart.emit();\n this.atStart = nextAtStart;\n };\n\n private releaseFollow = () => {\n this.prependReconciliation += 1;\n this.releasePendingAtLiveEdge = this.isAtLiveEdge();\n this.following = false;\n this.followReleased = true;\n this.showScrollToLatest = !this.releasePendingAtLiveEdge;\n };\n\n private handleSelectionChange = () => {\n const selection = document.getSelection();\n if (\n selection &&\n !selection.isCollapsed &&\n selection.anchorNode &&\n this.el.contains(selection.anchorNode)\n ) {\n this.releaseFollow();\n }\n };\n\n render() {\n return (\n <Host>\n <div class=\"message-scroller\">\n <div\n class=\"message-scroller__viewport\"\n ref={element => {\n this.viewport = element;\n }}\n role=\"region\"\n aria-label={this.messagesLabel}\n tabIndex={0}\n onScroll={this.handleScroll}\n onPointerDown={(event: PointerEvent) => {\n if (event.pointerType === 'touch') this.releaseFollow();\n }}\n onWheel={this.releaseFollow}\n onKeyDown={(event: KeyboardEvent) => {\n if (\n ['ArrowUp', 'ArrowDown', 'PageUp', 'PageDown', 'Home', 'End', ' '].includes(\n event.key\n )\n ) {\n this.releaseFollow();\n }\n }}\n >\n <div\n class=\"message-scroller__content scroll-edge-fade--block-window\"\n ref={element => {\n this.content = element;\n }}\n role=\"log\"\n aria-live=\"polite\"\n aria-relevant=\"additions\"\n aria-busy={this.busy ? 'true' : undefined}\n >\n <slot\n ref={element => {\n this.transcriptSlot = element;\n }}\n onSlotchange={this.handleTranscriptSlotChange}\n />\n </div>\n </div>\n <div\n class=\"message-scroller__overlay\"\n ref={element => {\n this.overlay = element;\n }}\n >\n <div class=\"message-scroller__overlay-stack\">\n <div class=\"message-scroller__interaction\">\n <slot name=\"interaction\" />\n </div>\n <div class=\"message-scroller__overlay-content\">\n <slot name=\"overlay\" />\n </div>\n </div>\n </div>\n {this.showScrollToLatest ? (\n <div class=\"message-scroller__control ds-control-elevation ds-control-elevation--md ds-control-elevation--press-scale\">\n <ds-tooltip label=\"Scroll to latest message\" side=\"top\" size=\"sm\">\n <ds-button-unfilled\n variant=\"icon\"\n icon=\"ChevronDown\"\n size=\"md\"\n rounded\n hasBorder={false}\n aria-label=\"Scroll to latest message\"\n onDsClick={() => void this.scrollToEnd()}\n />\n </ds-tooltip>\n </div>\n ) : null}\n </div>\n </Host>\n );\n }\n}\n",
|
|
231
231
|
"type": "registry:ui"
|
|
232
232
|
}
|
|
233
233
|
]
|
|
@@ -411,7 +411,7 @@
|
|
|
411
411
|
},
|
|
412
412
|
{
|
|
413
413
|
"path": "src/wc/components/MobileBarNav/MobileBarNav.tsx",
|
|
414
|
-
"content": "import {\n Component,\n Element,\n Event,\n EventEmitter,\n h,\n Host,\n Method,\n Prop,\n State,\n} from '@stencil/core';\nimport {\n shouldEmitMobileDestinationChange,\n type MobileDestination,\n} from '../../shell/shell-responsive';\nimport type { PanelNavItem } from '../PanelNav/panel-nav-types';\nimport type { MobileBarNavDestinationDetail } from './mobile-bar-nav-types';\n\ninterface DestinationConfig {\n id: MobileDestination;\n icon: string;\n label: string;\n dot: boolean;\n}\n\nconst MINIMUM_DIRECT_PRESS_MS = 120;\n\n@Component({\n tag: 'ds-mobile-bar-nav',\n styleUrl: 'MobileBarNav.css',\n scoped: true,\n})\nexport class MobileBarNav {\n @Element() el!: HTMLElement;\n\n @Prop() activeDestination: MobileDestination = 'area';\n @Prop() currentArea: PanelNavItem = {\n id: 'tracking',\n icon: 'MapPage',\n label: 'Tracking',\n };\n @Prop() sheetNavExpanded: boolean = false;\n @Prop() menuLabel: string = 'Menu';\n @Prop() searchLabel: string = 'Search';\n /** Render Activity directly, or retain the optional grouped Inbox destination. */\n @Prop() activityMode: 'direct' | 'inbox' = 'direct';\n @Prop() activityLabel: string = 'Activity';\n @Prop() inboxLabel: string = 'Inbox';\n @Prop() messagesLabel: string = 'Messages';\n @Prop() agentsLabel: string = 'Agents';\n @Prop() helpLabel: string = 'Help & Support';\n @Prop() searchDot: boolean = false;\n @Prop() activityDot: boolean = false;\n @Prop() inboxDot: boolean = false;\n @Prop() messagesDot: boolean = false;\n @Prop() agentsDot: boolean = false;\n\n @State() private pressedItemId: string | null = null;\n\n @Event() dsSheetNavToggle!: EventEmitter<boolean>;\n @Event() dsDestinationChange!: EventEmitter<MobileBarNavDestinationDetail>;\n\n private activePointerId: number | null = null;\n private pressedAt = 0;\n private pressClearTimer: ReturnType<typeof setTimeout> | null = null;\n\n disconnectedCallback() {\n this.clearPressedItem();\n }\n\n private clearPressTimer() {\n if (this.pressClearTimer === null) return;\n clearTimeout(this.pressClearTimer);\n this.pressClearTimer = null;\n }\n\n private clearPressedItem() {\n this.clearPressTimer();\n this.activePointerId = null;\n this.pressedItemId = null;\n }\n\n private beginDirectPress(itemId: string, event: PointerEvent) {\n if (!event.isPrimary || event.pointerType === 'mouse' || event.button !== 0) return;\n\n this.clearPressTimer();\n this.activePointerId = event.pointerId;\n this.pressedAt = performance.now();\n this.pressedItemId = itemId;\n\n const target = event.currentTarget as HTMLElement;\n try {\n target.setPointerCapture(event.pointerId);\n } catch {\n // Pointer capture can fail if Safari has already cancelled the stream.\n }\n }\n\n private finishDirectPress(event: PointerEvent) {\n if (event.pointerId !== this.activePointerId) return;\n\n this.activePointerId = null;\n const remaining = Math.max(
|
|
414
|
+
"content": "import {\n Component,\n Element,\n Event,\n EventEmitter,\n h,\n Host,\n Method,\n Prop,\n State,\n} from '@stencil/core';\nimport {\n shouldEmitMobileDestinationChange,\n type MobileDestination,\n} from '../../shell/shell-responsive';\nimport type { PanelNavItem } from '../PanelNav/panel-nav-types';\nimport type { MobileBarNavDestinationDetail } from './mobile-bar-nav-types';\n\ninterface DestinationConfig {\n id: MobileDestination;\n icon: string;\n label: string;\n dot: boolean;\n}\n\nconst MINIMUM_DIRECT_PRESS_MS = 120;\n\n@Component({\n tag: 'ds-mobile-bar-nav',\n styleUrl: 'MobileBarNav.css',\n scoped: true,\n})\nexport class MobileBarNav {\n @Element() el!: HTMLElement;\n\n @Prop() activeDestination: MobileDestination = 'area';\n @Prop() currentArea: PanelNavItem = {\n id: 'tracking',\n icon: 'MapPage',\n label: 'Tracking',\n };\n @Prop() sheetNavExpanded: boolean = false;\n @Prop() menuLabel: string = 'Menu';\n @Prop() searchLabel: string = 'Search';\n /** Render Activity directly, or retain the optional grouped Inbox destination. */\n @Prop() activityMode: 'direct' | 'inbox' = 'direct';\n @Prop() activityLabel: string = 'Activity';\n @Prop() inboxLabel: string = 'Inbox';\n @Prop() messagesLabel: string = 'Messages';\n @Prop() agentsLabel: string = 'Agents';\n @Prop() helpLabel: string = 'Help & Support';\n @Prop() searchDot: boolean = false;\n @Prop() activityDot: boolean = false;\n @Prop() inboxDot: boolean = false;\n @Prop() messagesDot: boolean = false;\n @Prop() agentsDot: boolean = false;\n\n @State() private pressedItemId: string | null = null;\n\n @Event() dsSheetNavToggle!: EventEmitter<boolean>;\n @Event() dsDestinationChange!: EventEmitter<MobileBarNavDestinationDetail>;\n\n private activePointerId: number | null = null;\n private pressedAt = 0;\n private pressClearTimer: ReturnType<typeof setTimeout> | null = null;\n\n disconnectedCallback() {\n this.clearPressedItem();\n }\n\n private clearPressTimer() {\n if (this.pressClearTimer === null) return;\n clearTimeout(this.pressClearTimer);\n this.pressClearTimer = null;\n }\n\n private clearPressedItem() {\n this.clearPressTimer();\n this.activePointerId = null;\n this.pressedItemId = null;\n }\n\n private beginDirectPress(itemId: string, event: PointerEvent) {\n if (!event.isPrimary || event.pointerType === 'mouse' || event.button !== 0) return;\n\n this.clearPressTimer();\n this.activePointerId = event.pointerId;\n this.pressedAt = performance.now();\n this.pressedItemId = itemId;\n\n const target = event.currentTarget as HTMLElement;\n try {\n target.setPointerCapture(event.pointerId);\n } catch {\n // Pointer capture can fail if Safari has already cancelled the stream.\n }\n }\n\n private finishDirectPress(event: PointerEvent) {\n if (event.pointerId !== this.activePointerId) return;\n\n this.activePointerId = null;\n const remaining = Math.max(0, MINIMUM_DIRECT_PRESS_MS - (performance.now() - this.pressedAt));\n\n this.clearPressTimer();\n this.pressClearTimer = setTimeout(() => {\n this.pressClearTimer = null;\n this.pressedItemId = null;\n }, remaining);\n }\n\n private cancelDirectPress(event: PointerEvent) {\n if (event.pointerId !== this.activePointerId) return;\n this.clearPressedItem();\n }\n\n private pressHandlers(itemId: string) {\n return {\n onPointerDown: (event: PointerEvent) => this.beginDirectPress(itemId, event),\n onPointerUp: (event: PointerEvent) => this.finishDirectPress(event),\n onPointerCancel: (event: PointerEvent) => this.cancelDirectPress(event),\n onLostPointerCapture: (event: PointerEvent) => this.finishDirectPress(event),\n };\n }\n\n private destinationConfig(): DestinationConfig[] {\n const currentArea =\n this.activeDestination === 'help'\n ? {\n id: 'help' as const,\n icon: 'CircleQuestion',\n label: this.helpLabel,\n dot: false,\n }\n : {\n id: 'area' as const,\n icon: this.currentArea.icon || 'MapPage',\n label: this.currentArea.label || 'Area',\n dot: false,\n };\n\n const activity =\n this.activityMode === 'direct'\n ? {\n id: 'activity' as const,\n icon: 'Bell',\n label: this.activityLabel,\n dot: this.activityDot,\n }\n : {\n id: 'inbox' as const,\n icon: 'Inbox',\n label: this.inboxLabel,\n dot: this.inboxDot,\n };\n\n return [\n currentArea,\n { id: 'search', icon: 'MagnifyingGlass', label: this.searchLabel, dot: this.searchDot },\n activity,\n {\n id: 'messages',\n icon: 'MessageBubbleStack',\n label: this.messagesLabel,\n dot: this.messagesDot,\n },\n { id: 'agents', icon: 'AI', label: this.agentsLabel, dot: this.agentsDot },\n ];\n }\n\n private selectDestination(destination: MobileDestination) {\n if (\n !shouldEmitMobileDestinationChange(this.activeDestination, destination, this.sheetNavExpanded)\n ) {\n return;\n }\n this.dsDestinationChange.emit({ destination });\n // Owners may replace the visible mobile stage (and transiently drop focus\n // in WebKit) while handling the controlled destination change.\n requestAnimationFrame(() => {\n void this.focusDestination(destination);\n });\n }\n\n @Method()\n async focusDestination(destination: MobileDestination | 'sheet-nav') {\n const target = this.el.querySelector<HTMLElement>(\n `#${\n destination === 'sheet-nav'\n ? 'ds-mobile-sheet-nav-trigger'\n : `ds-mobile-bar-${destination}-trigger`\n }`\n );\n target?.focus({ preventScroll: true });\n }\n\n private renderDestination(item: DestinationConfig) {\n const selected = !this.sheetNavExpanded && item.id === this.activeDestination;\n return (\n <button\n id={`ds-mobile-bar-${item.id}-trigger`}\n type=\"button\"\n class={{\n 'mobile-bar-nav__item': true,\n 'mobile-bar-nav__item--selected': selected,\n 'mobile-bar-nav__item--pressed': this.pressedItemId === item.id,\n 'ds-focus-ring-inset': true,\n 'ds-interaction-fill': true,\n }}\n aria-label={item.label}\n aria-current={selected ? 'page' : undefined}\n onClick={() => this.selectDestination(item.id)}\n {...this.pressHandlers(item.id)}\n >\n <span class=\"mobile-bar-nav__icon ds-interaction-fill__content\">\n <ds-icon name={item.icon} size=\"lg\" color=\"inherit\" />\n {item.dot && (\n <ds-badge class=\"mobile-bar-nav__dot\" variant=\"dot\" label=\"\" aria-hidden=\"true\" />\n )}\n </span>\n </button>\n );\n }\n\n render() {\n const [currentArea, ...tools] = this.destinationConfig();\n\n return (\n <Host>\n <nav class=\"mobile-bar-nav ds-chrome-row ds-chrome-space--md\" aria-label=\"Primary\">\n <div class=\"mobile-bar-nav__group mobile-bar-nav__group--context\">\n <button\n id=\"ds-mobile-sheet-nav-trigger\"\n type=\"button\"\n class={{\n 'mobile-bar-nav__item': true,\n 'mobile-bar-nav__item--selected': this.sheetNavExpanded,\n 'mobile-bar-nav__item--pressed': this.pressedItemId === 'sheet-nav',\n 'ds-focus-ring-inset': true,\n 'ds-interaction-fill': true,\n }}\n aria-label={this.menuLabel}\n aria-expanded={String(this.sheetNavExpanded)}\n aria-controls=\"ds-mobile-sheet-nav\"\n onClick={() => this.dsSheetNavToggle.emit(!this.sheetNavExpanded)}\n {...this.pressHandlers('sheet-nav')}\n >\n <span class=\"mobile-bar-nav__icon ds-interaction-fill__content\">\n <ds-icon\n name={this.sheetNavExpanded ? 'Cross' : 'Hamburger'}\n size=\"lg\"\n color=\"inherit\"\n />\n </span>\n </button>\n <span class=\"mobile-bar-nav__divider\" aria-hidden=\"true\" />\n {this.renderDestination(currentArea)}\n </div>\n <div class=\"mobile-bar-nav__group mobile-bar-nav__group--tools\">\n {tools.map(item => this.renderDestination(item))}\n </div>\n </nav>\n </Host>\n );\n }\n}\n",
|
|
415
415
|
"type": "registry:ui"
|
|
416
416
|
},
|
|
417
417
|
{
|
|
@@ -342,12 +342,12 @@
|
|
|
342
342
|
"files": [
|
|
343
343
|
{
|
|
344
344
|
"path": "src/wc/components/MobileHeader/MobileHeader.css",
|
|
345
|
-
"content": "@import '../../utils/chrome-layout.css';\n\n:host {\n display: block;\n min-width: 0;\n box-sizing: border-box;\n background: var(--color-background-primary);\n color: var(--color-foreground-primary);\n}\n\n:host([tone='brand']) {\n background: var(--color-background-bold-brand);\n color: var(--color-foreground-on-bold);\n}\n\n.mobile-header {\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n min-width: 0;\n box-sizing: border-box;\n background: inherit;\n color: inherit;\n}\n\n.mobile-header__primary {\n grid-template-columns: minmax(var(--dimension-size-400), 1fr) minmax(0, auto) minmax(var(--dimension-size-400)
|
|
345
|
+
"content": "@import '../../utils/chrome-layout.css';\n\n:host {\n display: block;\n min-width: 0;\n box-sizing: border-box;\n background: var(--color-background-primary);\n color: var(--color-foreground-primary);\n}\n\n:host([tone='brand']) {\n background: var(--color-background-bold-brand);\n color: var(--color-foreground-on-bold);\n}\n\n.mobile-header {\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n min-width: 0;\n box-sizing: border-box;\n background: inherit;\n color: inherit;\n}\n\n.mobile-header__primary {\n grid-template-columns: minmax(var(--dimension-size-400), 1fr) minmax(0, auto) minmax(\n var(--dimension-size-400),\n 1fr\n );\n width: 100%;\n height: calc(var(--dimension-size-500) + var(--dimension-space-200));\n min-width: 0;\n background: inherit;\n color: inherit;\n}\n\n.mobile-header__primary--segmented {\n grid-template-columns:\n minmax(var(--dimension-size-500), 1fr)\n minmax(0, 4fr)\n minmax(var(--dimension-size-500), 1fr);\n}\n\n.mobile-header__primary--segmented .mobile-header__center,\n.mobile-header__primary--segmented ds-tab-group {\n width: 100%;\n}\n\n:host(:not([tone='brand'])) .mobile-header::after {\n content: '';\n position: absolute;\n inset-inline: 0;\n inset-block-end: 0;\n height: var(--dimension-stroke-width-012);\n background: var(--color-border-tertiary);\n pointer-events: none;\n}\n\n.mobile-header__subsections {\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 0;\n height: var(--dimension-size-600);\n padding: var(--dimension-space-100) var(--dimension-space-200);\n box-sizing: border-box;\n}\n\n.mobile-header__subsections ds-mobile-section-switcher {\n min-width: 0;\n max-width: 100%;\n}\n\n.mobile-header__lane {\n display: flex;\n align-items: center;\n gap: var(--dimension-space-050);\n min-width: 0;\n}\n\n.mobile-header__lane--leading {\n justify-content: flex-start;\n}\n\n.mobile-header__lane--trailing {\n justify-content: flex-end;\n}\n\n.mobile-header__center {\n position: relative;\n min-width: 0;\n max-width: 100%;\n justify-self: center;\n}\n\n.mobile-header__heading {\n display: block;\n min-width: 0;\n max-width: 100%;\n text-align: center;\n}\n\n.mobile-header__semantic-heading {\n position: absolute;\n width: var(--dimension-stroke-width-012);\n height: var(--dimension-stroke-width-012);\n padding: 0;\n margin: calc(var(--dimension-stroke-width-012) * -1);\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n\n::slotted([slot='leading']),\n::slotted([slot='trailing']) {\n flex: 0 0 auto;\n}\n",
|
|
346
346
|
"type": "registry:ui"
|
|
347
347
|
},
|
|
348
348
|
{
|
|
349
349
|
"path": "src/wc/components/MobileHeader/MobileHeader.tsx",
|
|
350
|
-
"content": "import { Component, Event, EventEmitter, h, Host, Prop } from '@stencil/core';\nimport {
|
|
350
|
+
"content": "import { Component, Event, EventEmitter, h, Host, Prop } from '@stencil/core';\nimport { getSelectableTabs, type TabGroupItem, type TabItem } from '../TabGroup/tab-item-utils';\nimport type { TabGroupSize } from '../TabGroup/TabGroup';\nimport type {\n MobileHeaderHeadingLevel,\n MobileHeaderSectionsPresentation,\n MobileHeaderTone,\n} from './mobile-header-types';\n\n@Component({\n tag: 'ds-mobile-header',\n styleUrl: 'MobileHeader.css',\n scoped: true,\n})\nexport class MobileHeader {\n /** Static title used when no peer sections are supplied. */\n @Prop() heading: string = '';\n /** Semantic heading level for the active mobile screen. */\n @Prop() headingLevel: MobileHeaderHeadingLevel = 'h1';\n /** Controlled peer sections. Their selected label replaces the static title. */\n @Prop() sections: TabGroupItem[] = [];\n /** Controlled selected section id. */\n @Prop() value: string = '';\n /** Accessible name for the section chooser. */\n @Prop() sectionsAriaLabel: string = 'Change page section';\n /** Popup switcher or an inline segmented TabGroup for peer sections. */\n @Prop() sectionsPresentation: MobileHeaderSectionsPresentation = 'switcher';\n /** Density for the segmented sections presentation. */\n @Prop() sectionsSize: TabGroupSize = 'md';\n /** Controlled child sections within the selected page or detail screen. */\n @Prop() subsections: TabItem[] = [];\n /** Controlled selected child-section id. */\n @Prop() subvalue: string = '';\n /** Accessible name for the child-section chooser. */\n @Prop() subsectionsAriaLabel: string = 'Change page subsection';\n /** Default page chrome or bold-brand workflow chrome. */\n @Prop({ reflect: true }) tone: MobileHeaderTone = 'default';\n\n /** Section selection intent. */\n @Event() dsSectionChange!: EventEmitter<string>;\n /** Child-section selection intent. */\n @Event() dsSubsectionChange!: EventEmitter<string>;\n\n private get resolvedSections(): TabGroupItem[] {\n return this.sections ?? [];\n }\n\n private get resolvedSubsections(): TabItem[] {\n return this.subsections ?? [];\n }\n\n private get selectedLabel(): string {\n const selectable = getSelectableTabs(this.resolvedSections);\n return (\n selectable.find(section => section.id === this.value)?.label ??\n selectable[0]?.label ??\n this.heading\n );\n }\n\n private handleSectionChange = (event: CustomEvent<string>) => {\n event.stopPropagation();\n this.dsSectionChange.emit(event.detail);\n };\n\n private handleSubsectionChange = (event: CustomEvent<string>) => {\n event.stopPropagation();\n this.dsSubsectionChange.emit(event.detail);\n };\n\n render() {\n const Heading = this.headingLevel;\n const selectable = getSelectableTabs(this.resolvedSections);\n const hasSwitcher = selectable.length > 1;\n const hasSubsections = getSelectableTabs(this.resolvedSubsections).length > 1;\n const hasSegmentedSections = hasSwitcher && this.sectionsPresentation === 'segmented';\n const foreground = this.tone === 'brand' ? 'on-bold' : 'primary';\n\n return (\n <Host>\n <header class=\"mobile-header\">\n <div\n class={{\n 'mobile-header__primary': true,\n 'mobile-header__primary--segmented': hasSegmentedSections,\n 'ds-chrome-grid': true,\n 'ds-chrome-space--md': true,\n }}\n >\n <div class=\"mobile-header__lane mobile-header__lane--leading\">\n <slot name=\"leading\" />\n </div>\n <div class=\"mobile-header__center\">\n {hasSwitcher ? (\n [\n <span\n class=\"mobile-header__semantic-heading\"\n role=\"heading\"\n aria-level={this.headingLevel === 'h1' ? '1' : '2'}\n >\n {this.selectedLabel}\n </span>,\n this.sectionsPresentation === 'segmented' ? (\n <ds-tab-group\n tabs={this.resolvedSections}\n value={this.value}\n size={this.sectionsSize}\n width=\"fill\"\n ariaLabel={this.sectionsAriaLabel}\n onDsChange={this.handleSectionChange}\n />\n ) : (\n <ds-mobile-section-switcher\n sections={this.resolvedSections}\n value={this.value}\n navigationLabel={this.sectionsAriaLabel}\n onDsChange={this.handleSectionChange}\n />\n ),\n ]\n ) : (\n <ds-text\n class=\"mobile-header__heading\"\n as={Heading}\n variant=\"text-body-medium\"\n emphasis\n color={foreground}\n lineTruncation={1}\n >\n {this.selectedLabel}\n </ds-text>\n )}\n </div>\n <div class=\"mobile-header__lane mobile-header__lane--trailing\">\n <slot name=\"trailing\" />\n </div>\n </div>\n {hasSubsections ? (\n <nav class=\"mobile-header__subsections\" aria-label={this.subsectionsAriaLabel}>\n <ds-mobile-section-switcher\n sections={this.resolvedSubsections}\n value={this.subvalue}\n navigationLabel={this.subsectionsAriaLabel}\n onDsChange={this.handleSubsectionChange}\n />\n </nav>\n ) : null}\n </header>\n </Host>\n );\n }\n}\n",
|
|
351
351
|
"type": "registry:ui"
|
|
352
352
|
},
|
|
353
353
|
{
|
|
@@ -186,12 +186,12 @@
|
|
|
186
186
|
},
|
|
187
187
|
{
|
|
188
188
|
"path": "src/wc/components/MobileSectionSwitcher/mobile-section-switcher-types.ts",
|
|
189
|
-
"content": "import type { TabItem } from '../TabGroup/tab-item-utils';\n\n/** Ordered mobile sections. Dividers group the tap menu but do not participate in selection. */\nexport type MobileSectionItem = TabItem;\n
|
|
189
|
+
"content": "import type { TabItem } from '../TabGroup/tab-item-utils';\n\n/** Ordered mobile sections. Dividers group the tap menu but do not participate in selection. */\nexport type MobileSectionItem = TabItem;\n",
|
|
190
190
|
"type": "registry:ui"
|
|
191
191
|
},
|
|
192
192
|
{
|
|
193
193
|
"path": "src/wc/components/MobileSectionSwitcher/mobile-section-switcher-utils.ts",
|
|
194
|
-
"content": "import { getSelectableTabs, type TabItem, type TabItemTab } from '../TabGroup/tab-item-utils';\n\nexport interface MobileSectionPosition {\n selectedIndex: number;\n selected?: TabItemTab;\n hasPrevious: boolean;\n hasNext: boolean;\n}\n\n/** Resolve ordered selectable neighbors while ignoring visual divider entries. */\nexport function resolveMobileSectionPosition(\n sections: TabItem[],\n value: string\n): MobileSectionPosition {\n const selectable = getSelectableTabs(sections);\n const matchedIndex = selectable.findIndex(section => section.id === value);\n const selectedIndex = matchedIndex >= 0 ? matchedIndex : 0;\n return {\n selectedIndex,\n selected: selectable[selectedIndex],\n hasPrevious: selectedIndex > 0,\n hasNext: selectedIndex < selectable.length - 1,\n };\n}\n
|
|
194
|
+
"content": "import { getSelectableTabs, type TabItem, type TabItemTab } from '../TabGroup/tab-item-utils';\n\nexport interface MobileSectionPosition {\n selectedIndex: number;\n selected?: TabItemTab;\n hasPrevious: boolean;\n hasNext: boolean;\n}\n\n/** Resolve ordered selectable neighbors while ignoring visual divider entries. */\nexport function resolveMobileSectionPosition(\n sections: TabItem[],\n value: string\n): MobileSectionPosition {\n const selectable = getSelectableTabs(sections);\n const matchedIndex = selectable.findIndex(section => section.id === value);\n const selectedIndex = matchedIndex >= 0 ? matchedIndex : 0;\n return {\n selectedIndex,\n selected: selectable[selectedIndex],\n hasPrevious: selectedIndex > 0,\n hasNext: selectedIndex < selectable.length - 1,\n };\n}\n",
|
|
195
195
|
"type": "registry:ui"
|
|
196
196
|
}
|
|
197
197
|
]
|
|
@@ -356,7 +356,7 @@
|
|
|
356
356
|
},
|
|
357
357
|
{
|
|
358
358
|
"path": "src/wc/components/MobileSheetNav/MobileSheetNav.tsx",
|
|
359
|
-
"content": "import {\n Component,\n Element,\n Event,\n EventEmitter,\n h,\n Host,\n Listen,\n Prop,\n Watch,\n} from '@stencil/core';\nimport type { NavChromeStyle } from '../../shell/nav-chrome';\nimport { deriveActiveIdFromUrl } from '../PanelNav/panel-nav-utils';\nimport type { PanelNavGroup, PanelNavItem } from '../PanelNav/panel-nav-types';\n\ntype MobileSheetNavHeaderDestinationId = 'account' | 'help';\n\nconst CONTEXT_TABS = [\n { id: 'dashboard', label: 'Dashboard', icon: 'Dashboard', variant: 'icon' as const },\n { id: 'settings', label: 'Settings', icon: 'Gear', variant: 'icon' as const },\n];\n\n@Component({\n tag: 'ds-mobile-sheet-nav',\n styleUrl: 'MobileSheetNav.css',\n scoped: true,\n})\nexport class MobileSheetNav {\n @Element() el!: HTMLElement;\n\n @Prop({ reflect: true }) open: boolean = false;\n @Prop() browseContext: NavChromeStyle = 'dashboard';\n @Prop() dashboardGroups: PanelNavGroup[] = [];\n @Prop() settingsGroups: PanelNavGroup[] = [];\n @Prop() currentUrl: string = '';\n /** Whether the router-derived current area owns the active mobile stage. */\n @Prop() routeSelectionActive: boolean = true;\n @Prop() navigationLabel: string = 'Application navigation';\n @Prop() dashboardLabel: string = 'Dashboard';\n @Prop() settingsLabel: string = 'Settings';\n @Prop() accountLabel: string = 'Account';\n @Prop() helpLabel: string = 'Help & Support';\n /** Show the optional Account shortcut in the sheet header. */\n @Prop() showAccount: boolean = true;\n\n @Event() dsAreaSelect!: EventEmitter<string>;\n @Event() dsBrowseContextChange!: EventEmitter<NavChromeStyle>;\n @Event() dsClose!: EventEmitter<void>;\n\n private get groups(): PanelNavGroup[] {\n return this.browseContext === 'settings' ? this.settingsGroups : this.dashboardGroups;\n }\n\n private get activeId(): string {\n if (!this.routeSelectionActive) return '';\n return deriveActiveIdFromUrl(this.currentUrl, [\n ...this.dashboardGroups.flatMap(group => group.items),\n ...this.settingsGroups.flatMap(group => group.items),\n ]);\n }\n\n @Watch('open')\n handleOpenChange(open: boolean) {\n if (!open) return;\n requestAnimationFrame(() => {\n const selected = this.el.querySelector<HTMLElement>('[aria-current=\"page\"]');\n const first = this.el.querySelector<HTMLElement>('.mobile-sheet-nav__item');\n (selected ?? first)?.focus({ preventScroll: true });\n });\n }\n\n @Listen('keydown')\n handleKeyDown(event: KeyboardEvent) {\n if (!this.open || event.key !== 'Escape') return;\n event.preventDefault();\n this.dsClose.emit();\n }\n\n private handleContextChange = (event: CustomEvent<string>) => {\n const next = event.detail === 'settings' ? 'settings' : 'dashboard';\n if (next === this.browseContext) return;\n this.dsBrowseContextChange.emit(next);\n };\n\n private renderItem(item: PanelNavItem) {\n const selected = item.id === this.activeId;\n return (\n <button\n type=\"button\"\n class={{\n 'mobile-sheet-nav__item': true,\n 'mobile-sheet-nav__item--selected': selected,\n 'ds-control--lg': true,\n 'ds-focus-ring-inset': true,\n 'ds-interaction-fill': true,\n }}\n aria-current={selected ? 'page' : undefined}\n onClick={() => this.dsAreaSelect.emit(item.id)}\n >\n <ds-icon
|
|
359
|
+
"content": "import {\n Component,\n Element,\n Event,\n EventEmitter,\n h,\n Host,\n Listen,\n Prop,\n Watch,\n} from '@stencil/core';\nimport type { NavChromeStyle } from '../../shell/nav-chrome';\nimport { deriveActiveIdFromUrl } from '../PanelNav/panel-nav-utils';\nimport type { PanelNavGroup, PanelNavItem } from '../PanelNav/panel-nav-types';\n\ntype MobileSheetNavHeaderDestinationId = 'account' | 'help';\n\nconst CONTEXT_TABS = [\n { id: 'dashboard', label: 'Dashboard', icon: 'Dashboard', variant: 'icon' as const },\n { id: 'settings', label: 'Settings', icon: 'Gear', variant: 'icon' as const },\n];\n\n@Component({\n tag: 'ds-mobile-sheet-nav',\n styleUrl: 'MobileSheetNav.css',\n scoped: true,\n})\nexport class MobileSheetNav {\n @Element() el!: HTMLElement;\n\n @Prop({ reflect: true }) open: boolean = false;\n @Prop() browseContext: NavChromeStyle = 'dashboard';\n @Prop() dashboardGroups: PanelNavGroup[] = [];\n @Prop() settingsGroups: PanelNavGroup[] = [];\n @Prop() currentUrl: string = '';\n /** Whether the router-derived current area owns the active mobile stage. */\n @Prop() routeSelectionActive: boolean = true;\n @Prop() navigationLabel: string = 'Application navigation';\n @Prop() dashboardLabel: string = 'Dashboard';\n @Prop() settingsLabel: string = 'Settings';\n @Prop() accountLabel: string = 'Account';\n @Prop() helpLabel: string = 'Help & Support';\n /** Show the optional Account shortcut in the sheet header. */\n @Prop() showAccount: boolean = true;\n\n @Event() dsAreaSelect!: EventEmitter<string>;\n @Event() dsBrowseContextChange!: EventEmitter<NavChromeStyle>;\n @Event() dsClose!: EventEmitter<void>;\n\n private get groups(): PanelNavGroup[] {\n return this.browseContext === 'settings' ? this.settingsGroups : this.dashboardGroups;\n }\n\n private get activeId(): string {\n if (!this.routeSelectionActive) return '';\n return deriveActiveIdFromUrl(this.currentUrl, [\n ...this.dashboardGroups.flatMap(group => group.items),\n ...this.settingsGroups.flatMap(group => group.items),\n ]);\n }\n\n @Watch('open')\n handleOpenChange(open: boolean) {\n if (!open) return;\n requestAnimationFrame(() => {\n const selected = this.el.querySelector<HTMLElement>('[aria-current=\"page\"]');\n const first = this.el.querySelector<HTMLElement>('.mobile-sheet-nav__item');\n (selected ?? first)?.focus({ preventScroll: true });\n });\n }\n\n @Listen('keydown')\n handleKeyDown(event: KeyboardEvent) {\n if (!this.open || event.key !== 'Escape') return;\n event.preventDefault();\n this.dsClose.emit();\n }\n\n private handleContextChange = (event: CustomEvent<string>) => {\n const next = event.detail === 'settings' ? 'settings' : 'dashboard';\n if (next === this.browseContext) return;\n this.dsBrowseContextChange.emit(next);\n };\n\n private renderItem(item: PanelNavItem) {\n const selected = item.id === this.activeId;\n return (\n <button\n type=\"button\"\n class={{\n 'mobile-sheet-nav__item': true,\n 'mobile-sheet-nav__item--selected': selected,\n 'ds-control--lg': true,\n 'ds-focus-ring-inset': true,\n 'ds-interaction-fill': true,\n }}\n aria-current={selected ? 'page' : undefined}\n onClick={() => this.dsAreaSelect.emit(item.id)}\n >\n <ds-icon class=\"ds-interaction-fill__content\" name={item.icon} size=\"lg\" color=\"inherit\" />\n <ds-text\n class=\"mobile-sheet-nav__item-label ds-interaction-fill__content\"\n as=\"span\"\n variant=\"text-body-large\"\n emphasis={selected}\n color=\"inherit\"\n >\n {item.label}\n </ds-text>\n {item.dot && (\n <ds-badge\n class=\"mobile-sheet-nav__dot ds-interaction-fill__content\"\n variant=\"dot\"\n hasRing={false}\n label=\"\"\n aria-hidden=\"true\"\n />\n )}\n </button>\n );\n }\n\n private renderLogo() {\n return (\n <span class=\"mobile-sheet-nav__logo\" aria-hidden=\"true\">\n {/* eslint-disable-next-line local/prefer-ds-icon -- Motive brand mark is not part of the IcoMo runtime catalog. */}\n <svg\n class=\"mobile-sheet-nav__logo-mark\"\n viewBox=\"0 0 20 20\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n focusable=\"false\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M11.1159 4.31537H7.67021L2.53401 13.0703H0V15.6875H4.02716L8.24319 8.49978V15.6846H11.6342L15.8289 8.47829V15.6846H18.7122V4.3125H15.2559L11.1159 11.3648V4.31537Z\"\n fill=\"currentColor\"\n />\n </svg>\n </span>\n );\n }\n\n private renderHeaderDestination(\n id: MobileSheetNavHeaderDestinationId,\n icon: string,\n label: string\n ) {\n return (\n <ds-tooltip label={label} side=\"bottom\" size=\"sm\">\n <ds-button-unfilled\n variant=\"icon\"\n size=\"lg\"\n icon={icon}\n aria-label={label}\n activeFill={false}\n hasBorder={false}\n onDsClick={() => this.dsAreaSelect.emit(id)}\n />\n </ds-tooltip>\n );\n }\n\n render() {\n const contextTabs = [\n { ...CONTEXT_TABS[0], label: this.dashboardLabel },\n { ...CONTEXT_TABS[1], label: this.settingsLabel },\n ];\n\n return (\n <Host aria-hidden={this.open ? undefined : 'true'} inert={this.open ? undefined : true}>\n <section id=\"ds-mobile-sheet-nav\" class=\"mobile-sheet-nav\">\n <header\n class={{\n 'mobile-sheet-nav__header': true,\n 'mobile-sheet-nav__header--expanded-context': !this.showAccount,\n 'ds-chrome-grid': true,\n 'ds-chrome-space--md': true,\n }}\n >\n <div class=\"mobile-sheet-nav__brand\">{this.renderLogo()}</div>\n <ds-tab-group\n class=\"mobile-sheet-nav__context\"\n tabs={contextTabs}\n value={this.browseContext}\n size=\"lg\"\n width={this.showAccount ? 'hug' : 'fill'}\n aria-label=\"Browse context\"\n onDsChange={this.handleContextChange}\n />\n <div class=\"mobile-sheet-nav__actions\">\n {this.renderHeaderDestination('help', 'CircleQuestion', this.helpLabel)}\n {this.showAccount\n ? this.renderHeaderDestination('account', 'Avatar', this.accountLabel)\n : null}\n </div>\n </header>\n\n <nav\n class=\"mobile-sheet-nav__body ds-chrome-column ds-chrome-space--md\"\n aria-label={this.navigationLabel}\n >\n <div class=\"mobile-sheet-nav__sections\">\n {this.groups\n .filter(group => group.items.length > 0)\n .map(group => (\n <div class=\"mobile-sheet-nav__items\">\n {group.items.map(item => this.renderItem(item))}\n </div>\n ))}\n </div>\n </nav>\n </section>\n </Host>\n );\n }\n}\n",
|
|
360
360
|
"type": "registry:ui"
|
|
361
361
|
}
|
|
362
362
|
]
|
|
@@ -294,12 +294,12 @@
|
|
|
294
294
|
"files": [
|
|
295
295
|
{
|
|
296
296
|
"path": "src/wc/components/Modal/Modal.css",
|
|
297
|
-
"content": "@import '../../utils/chrome-header.css';\n@import '../../utils/chrome-layout.css';\n@import '../../utils/forced-colors.css';\n\n:host {\n display: contents;\n}\n\n.modal-dialog {\n display: none;\n flex-direction: column;\n max-width: none;\n max-height: calc(100vh - 2 * var(--dimension-space-200));\n margin: auto;\n padding: 0;\n overflow: hidden;\n color: var(--color-foreground-primary);\n background-color: var(--color-background-primary);\n border: 0;\n border-radius: var(--dimension-radius-050);\n box-shadow: var(--effect-elevation-elevated-floating);\n outline: none;\n box-sizing: border-box;\n animation: modalDialogIn var(--effect-motion-short-2) forwards;\n}\n\n.modal-dialog[open] {\n display: flex;\n}\n\n.modal-dialog::backdrop {\n background-color: var(--color-background-shade);\n animation: modalBackdropIn var(--effect-motion-short-2) forwards;\n}\n\n.modal-dialog--closing {\n animation: modalDialogOut var(--effect-motion-short-2) forwards;\n}\n\n.modal-dialog--closing::backdrop {\n animation: modalBackdropOut var(--effect-motion-short-2) forwards;\n}\n\n@keyframes modalBackdropIn {\n from {
|
|
297
|
+
"content": "@import '../../utils/chrome-header.css';\n@import '../../utils/chrome-layout.css';\n@import '../../utils/forced-colors.css';\n\n:host {\n display: contents;\n}\n\n.modal-dialog {\n display: none;\n flex-direction: column;\n max-width: none;\n max-height: calc(100vh - 2 * var(--dimension-space-200));\n margin: auto;\n padding: 0;\n overflow: hidden;\n color: var(--color-foreground-primary);\n background-color: var(--color-background-primary);\n border: 0;\n border-radius: var(--dimension-radius-050);\n box-shadow: var(--effect-elevation-elevated-floating);\n outline: none;\n box-sizing: border-box;\n animation: modalDialogIn var(--effect-motion-short-2) forwards;\n}\n\n.modal-dialog[open] {\n display: flex;\n}\n\n.modal-dialog::backdrop {\n background-color: var(--color-background-shade);\n animation: modalBackdropIn var(--effect-motion-short-2) forwards;\n}\n\n.modal-dialog--closing {\n animation: modalDialogOut var(--effect-motion-short-2) forwards;\n}\n\n.modal-dialog--closing::backdrop {\n animation: modalBackdropOut var(--effect-motion-short-2) forwards;\n}\n\n@keyframes modalBackdropIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n@keyframes modalBackdropOut {\n from {\n opacity: 1;\n }\n to {\n opacity: 0;\n }\n}\n\n@keyframes modalDialogIn {\n from {\n opacity: 0;\n transform: scale(0.97) translateY(4px);\n }\n to {\n opacity: 1;\n transform: scale(1) translateY(0);\n }\n}\n\n@keyframes modalDialogOut {\n from {\n opacity: 1;\n transform: scale(1) translateY(0);\n }\n to {\n opacity: 0;\n transform: scale(0.97) translateY(4px);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .modal-dialog[open],\n .modal-dialog--closing {\n animation: none;\n transform: none;\n }\n\n .modal-dialog::backdrop {\n animation: none;\n opacity: 1;\n }\n\n .modal-dialog--closing,\n .modal-dialog--closing::backdrop {\n opacity: 0;\n }\n}\n\n.modal-header {\n align-items: flex-start;\n flex: 0 0 auto;\n color: var(--color-foreground-primary);\n}\n\n.modal-copy,\nds-text.modal-heading,\nds-text.modal-description {\n width: auto;\n text-align: left;\n}\n\nds-text.modal-heading {\n -webkit-user-select: none;\n user-select: none;\n}\n\n.modal-close-tooltip {\n flex: 0 0 auto;\n}\n\n.modal-content {\n min-height: 0;\n padding: var(--dimension-space-200);\n overflow-y: auto;\n flex: 1 1 auto;\n box-sizing: border-box;\n}\n\n.modal-footer {\n justify-content: flex-start;\n height: var(--dimension-size-800);\n flex: 0 0 auto;\n border-top: var(--dimension-stroke-width-012) solid var(--color-border-tertiary);\n}\n\n.modal-footer__actions {\n display: flex;\n align-items: center;\n gap: var(--dimension-space-100);\n min-width: 0;\n}\n\n.modal-footer--empty {\n display: none;\n}\n\n.modal-footer__actions slot {\n display: contents;\n}\n\n@media (forced-colors: active) {\n .modal-dialog {\n box-shadow: none;\n outline: var(--dimension-stroke-width-012) solid var(--ds-forced-color-content);\n outline-offset: calc(-1 * var(--dimension-stroke-width-012));\n }\n}\n",
|
|
298
298
|
"type": "registry:ui"
|
|
299
299
|
},
|
|
300
300
|
{
|
|
301
301
|
"path": "src/wc/components/Modal/Modal.tsx",
|
|
302
|
-
"content": "import {
|
|
302
|
+
"content": "import {\n Component,\n Prop,\n State,\n Event,\n EventEmitter,\n Element,\n Watch,\n h,\n Host,\n} from '@stencil/core';\nimport { resolveMotionTimeMs, TOKEN_DEFAULTS } from '../../utils';\n\nexport type ModalWidth = 'sm' | 'md' | 'lg';\nexport type ModalCloseReason = 'close-button' | 'escape' | 'backdrop';\n\nexport interface ModalCloseDetail {\n reason: ModalCloseReason;\n originalEvent: Event;\n}\n\nconst WIDTH_MAP: Record<ModalWidth, string> = {\n sm: 'var(--dimension-modal-width-sm)',\n md: 'var(--dimension-modal-width-md)',\n lg: 'var(--dimension-modal-width-lg)',\n};\n\nconst FOCUSABLE_SEL = [\n 'a[href]',\n 'area[href]',\n 'button:not([disabled])',\n 'input:not([disabled]):not([type=\"hidden\"])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n 'iframe',\n '[contenteditable=\"true\"]',\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(',');\n\nlet modalIdCounter = 0;\n\n@Component({\n tag: 'ds-modal',\n styleUrl: 'Modal.css',\n scoped: true,\n})\nexport class Modal {\n @Element() el!: HTMLElement;\n\n @Prop({ mutable: true }) open: boolean = false;\n @Prop() heading!: string;\n /** Optional supporting copy shown below the heading. */\n @Prop() description: string = '';\n @Prop() closeAriaLabel: string = 'Close';\n @Prop() modalWidth: ModalWidth | string = 'md';\n /** Optional id reference for explanatory content in the default slot. */\n @Prop({ attribute: 'aria-describedby' }) ariaDescribedby: string | undefined;\n\n @State() private closing: boolean = false;\n @State() private hasFooter: boolean = false;\n\n /** Emitted when an internal dismissal control requests that the modal close. */\n @Event() dsClose!: EventEmitter<ModalCloseDetail>;\n /** Emitted after exit motion completes, the top layer closes, and focus is restored. */\n @Event() dsAfterClose!: EventEmitter<void>;\n\n private titleId = `ds-modal-title-${++modalIdCounter}`;\n private descriptionId = `${this.titleId}-description`;\n private dialogEl: HTMLDialogElement | null = null;\n private previousFocus: HTMLElement | null = null;\n private closeTimer: ReturnType<typeof setTimeout> | null = null;\n\n componentDidLoad() {\n this.updateFooterPresence();\n if (this.open) this.onOpenChange(true);\n }\n\n componentDidRender() {\n if (!this.closing || !this.dialogEl?.open || this.closeTimer) return;\n const closeAnimationMs = this.closeAnimationMs;\n if (closeAnimationMs <= 0) {\n this.finishClose();\n return;\n }\n this.closeTimer = setTimeout(() => {\n this.closeTimer = null;\n this.finishClose();\n }, closeAnimationMs);\n }\n\n disconnectedCallback() {\n this.clearCloseTimer();\n if (this.dialogEl?.open) this.dialogEl.close();\n }\n\n @Watch('open')\n onOpenChange(isOpen: boolean) {\n if (isOpen) {\n this.clearCloseTimer();\n const alreadyOpen = !!this.dialogEl?.open;\n this.closing = false;\n if (!this.dialogEl || alreadyOpen) return;\n this.previousFocus = document.activeElement as HTMLElement | null;\n this.dialogEl.showModal();\n requestAnimationFrame(() => {\n if (this.open && this.dialogEl?.open) this.focusClose();\n });\n } else if (this.dialogEl?.open) {\n this.closing = true;\n }\n }\n\n private focusClose() {\n const close = this.el.querySelector('ds-button-unfilled.modal-close') as\n | (HTMLElement & { setFocus?: () => Promise<void> })\n | null;\n void close?.setFocus?.();\n }\n\n private clearCloseTimer() {\n if (this.closeTimer) {\n clearTimeout(this.closeTimer);\n this.closeTimer = null;\n }\n }\n\n private requestClose(reason: ModalCloseReason, originalEvent: Event) {\n if (!this.open || this.closing || !this.dialogEl?.open) return;\n this.dsClose.emit({ reason, originalEvent });\n this.open = false;\n }\n\n private updateFooterPresence(slot?: HTMLSlotElement) {\n const assignedNodes = slot?.assignedNodes() ?? [];\n this.hasFooter =\n assignedNodes.some(node => node.nodeType !== Node.TEXT_NODE || !!node.textContent?.trim()) ||\n this.el.querySelector('[slot=\"footer\"]') !== null;\n }\n\n private handleCancel(event: Event) {\n event.preventDefault();\n this.requestClose('escape', event);\n }\n\n private handleBackdropPointerDown(event: PointerEvent) {\n if (event.target === this.dialogEl) this.requestClose('backdrop', event);\n }\n\n private handleKeyDown(event: KeyboardEvent) {\n if (event.key !== 'Tab' || !this.dialogEl) return;\n const focusables = Array.from(\n this.dialogEl.querySelectorAll<HTMLElement>(FOCUSABLE_SEL)\n ).filter(\n element => !element.hasAttribute('disabled') && element.getAttribute('aria-hidden') !== 'true'\n );\n if (!focusables.length) {\n event.preventDefault();\n this.dialogEl.focus();\n return;\n }\n const first = focusables[0];\n const last = focusables[focusables.length - 1];\n const active = document.activeElement;\n if (event.shiftKey && (active === first || active === this.dialogEl)) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && active === last) {\n event.preventDefault();\n first.focus();\n }\n }\n\n private get resolvedWidth(): string {\n if (this.modalWidth === 'sm' || this.modalWidth === 'md' || this.modalWidth === 'lg') {\n return WIDTH_MAP[this.modalWidth];\n }\n return this.modalWidth as string;\n }\n\n private get describedBy(): string | undefined {\n const ids = [this.description ? this.descriptionId : '', this.ariaDescribedby ?? ''].filter(\n Boolean\n );\n return ids.length > 0 ? ids.join(' ') : undefined;\n }\n\n private get closeAnimationMs(): number {\n return resolveMotionTimeMs(TOKEN_DEFAULTS.motionShort2, TOKEN_DEFAULTS.animationDurationShort3);\n }\n\n private finishClose() {\n if (!this.dialogEl?.open) return;\n this.dialogEl.close();\n this.closing = false;\n this.clearCloseTimer();\n this.previousFocus?.focus?.();\n this.previousFocus = null;\n this.dsAfterClose.emit();\n }\n\n render() {\n return (\n <Host>\n <dialog\n ref={(element?: HTMLDialogElement) => {\n this.dialogEl = element ?? null;\n }}\n class={{ 'modal-dialog': true, 'modal-dialog--closing': this.closing }}\n aria-labelledby={this.titleId}\n aria-describedby={this.describedBy}\n onCancel={(event: Event) => this.handleCancel(event)}\n onPointerDown={(event: PointerEvent) => this.handleBackdropPointerDown(event)}\n onKeyDown={(event: KeyboardEvent) => this.handleKeyDown(event)}\n style={{\n width: `min(${this.resolvedWidth}, calc(100vw - 2 * var(--dimension-space-200)))`,\n }}\n >\n <div class=\"modal-header ds-chrome-header ds-chrome-header--bounded\">\n <div class=\"modal-copy ds-chrome-header__copy ds-chrome-header__copy--stacked ds-control--md\">\n <ds-text\n class=\"modal-heading ds-chrome-header__heading\"\n as=\"h2\"\n variant=\"text-title-small\"\n emphasis\n color=\"primary\"\n lineTruncation={1}\n textId={this.titleId}\n >\n {this.heading}\n </ds-text>\n {this.description ? (\n <ds-text\n class=\"modal-description ds-chrome-header__description\"\n as=\"p\"\n variant=\"text-body-medium\"\n color=\"secondary\"\n wrap=\"wrap\"\n textId={this.descriptionId}\n >\n {this.description}\n </ds-text>\n ) : null}\n </div>\n <ds-tooltip\n class=\"modal-close-tooltip ds-chrome-header__trailing\"\n label={this.closeAriaLabel}\n side=\"bottom\"\n size=\"sm\"\n >\n <ds-button-unfilled\n class=\"modal-close\"\n variant=\"icon\"\n icon=\"Cross\"\n size=\"md\"\n aria-label={this.closeAriaLabel}\n activeFill={false}\n hasBorder={false}\n onDsClick={(event: CustomEvent<MouseEvent>) =>\n this.requestClose('close-button', event.detail)\n }\n />\n </ds-tooltip>\n </div>\n <div class=\"modal-content\">\n <slot />\n </div>\n <div\n class={{\n 'modal-footer': true,\n 'ds-chrome-row': true,\n 'ds-chrome-space--lg': true,\n 'modal-footer--empty': !this.hasFooter,\n }}\n >\n <div class=\"modal-footer__actions\">\n <slot\n name=\"footer\"\n onSlotchange={(event: Event) =>\n this.updateFooterPresence(event.currentTarget as HTMLSlotElement)\n }\n />\n </div>\n </div>\n </dialog>\n </Host>\n );\n }\n}\n",
|
|
303
303
|
"type": "registry:ui"
|
|
304
304
|
}
|
|
305
305
|
]
|