@ds-mo/ui 15.6.0 → 15.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.package-ready.json +1 -1
- package/dist/agent-patterns.json +1 -1
- package/dist/agent.json +327 -5
- package/dist/angular/ds-navigation-tab-group.d.ts +15 -0
- package/dist/angular/ds-navigation-tab-group.js +42 -0
- package/dist/angular/ds-select-toggle.d.ts +15 -0
- package/dist/angular/ds-select-toggle.js +42 -0
- package/dist/angular/ds-table.d.ts +1 -1
- package/dist/angular/ds-table.js +3 -3
- package/dist/angular/index.d.ts +1 -1
- package/dist/angular/index.js +2 -0
- package/dist/angular/proxies.d.ts +2 -0
- package/dist/angular/proxies.js +2 -0
- package/dist/components/ds-agent-activity.js +1 -1
- package/dist/components/ds-agent-questionnaire.js +1 -1
- package/dist/components/ds-agent-response.js +1 -1
- package/dist/components/ds-agent-source-list.js +1 -1
- package/dist/components/ds-agent-tool-call.js +1 -1
- package/dist/components/ds-attachment-list.js +1 -1
- package/dist/components/ds-badge.js +1 -1
- package/dist/components/ds-banner.js +1 -1
- package/dist/components/ds-bar-action.js +1 -1
- package/dist/components/ds-bar-nav.js +1 -1
- package/dist/components/ds-bar-page-title.js +1 -1
- package/dist/components/ds-bar-title.js +1 -1
- package/dist/components/ds-bar-workflow.js +1 -1
- package/dist/components/ds-breadcrumb.js +1 -1
- package/dist/components/ds-button-filled.js +1 -1
- package/dist/components/ds-button-inverted.js +1 -1
- package/dist/components/ds-button-unfilled.js +1 -1
- package/dist/components/ds-calendar.js +1 -1
- package/dist/components/ds-card-action-center.js +1 -1
- package/dist/components/ds-card-chart.js +1 -1
- package/dist/components/ds-card-navigation.js +1 -1
- package/dist/components/ds-card-overview.js +1 -1
- package/dist/components/ds-card-setting.js +1 -1
- package/dist/components/ds-card-settings-scope.js +1 -1
- package/dist/components/ds-chart-legend.js +1 -1
- package/dist/components/ds-chart.js +1 -1
- package/dist/components/ds-checkbox-group.js +1 -1
- package/dist/components/ds-checkbox.js +1 -1
- package/dist/components/ds-chip.js +1 -1
- package/dist/components/ds-code-block.js +1 -1
- package/dist/components/ds-conversation-list-item.js +1 -1
- package/dist/components/ds-conversation-list-section.js +1 -1
- package/dist/components/ds-conversation-list.js +1 -1
- package/dist/components/ds-data-customize.js +1 -1
- package/dist/components/ds-data-filter.js +1 -1
- package/dist/components/ds-data-group.js +1 -1
- package/dist/components/ds-data-preferences.js +1 -1
- package/dist/components/ds-data-saved-views.js +1 -1
- package/dist/components/ds-data-search.js +1 -1
- package/dist/components/ds-data-sort.js +1 -1
- package/dist/components/ds-data-toolbar.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-inline-banner-settings.js +1 -1
- package/dist/components/ds-input-date.js +1 -1
- package/dist/components/ds-input-time.js +1 -1
- package/dist/components/ds-input.js +1 -1
- package/dist/components/ds-map-cluster-marker.js +1 -1
- package/dist/components/ds-map-entity-marker.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-composer.js +1 -1
- package/dist/components/ds-message-scroller.js +1 -1
- package/dist/components/ds-message.js +1 -1
- package/dist/components/ds-mobile-bar-nav.js +1 -1
- package/dist/components/ds-mobile-header.js +1 -1
- package/dist/components/ds-mobile-section-switcher.js +1 -1
- package/dist/components/ds-mobile-sheet-nav.js +1 -1
- package/dist/components/ds-modal.js +1 -1
- package/dist/components/ds-navigation-tab-group.d.ts +11 -0
- package/dist/components/ds-navigation-tab-group.js +2 -0
- package/dist/components/ds-navigation-tab-group.js.map +1 -0
- package/dist/components/ds-pagination.js +1 -1
- package/dist/components/ds-panel-nav.js +1 -1
- package/dist/components/ds-panel-sub-nav.js +1 -1
- package/dist/components/ds-panel-tool-header.js +1 -1
- package/dist/components/ds-panel-tool-search.js +1 -1
- package/dist/components/ds-panel-tools.js +1 -1
- package/dist/components/ds-paper-texture.js +1 -1
- package/dist/components/ds-radio-tile.js +1 -1
- package/dist/components/ds-radio.js +1 -1
- package/dist/components/ds-score.js +1 -1
- package/dist/components/ds-select-toggle.d.ts +11 -0
- package/dist/components/ds-select-toggle.js +2 -0
- package/dist/components/ds-select-toggle.js.map +1 -0
- package/dist/components/ds-select.js +1 -1
- package/dist/components/ds-setting-row-checkbox.js +1 -1
- package/dist/components/ds-setting-row-radio.js +1 -1
- package/dist/components/ds-setting-row-radio.js.map +1 -1
- package/dist/components/ds-setting-row-toggle.js +1 -1
- package/dist/components/ds-shell-app.js +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.js +1 -1
- package/dist/components/ds-table.js.map +1 -1
- package/dist/components/ds-tag.js +1 -1
- package/dist/components/ds-text.js +1 -1
- package/dist/components/ds-textarea.js +1 -1
- package/dist/components/ds-time-picker.js +1 -1
- package/dist/components/ds-toast.js +1 -1
- package/dist/components/ds-tooltip-chart.js +1 -1
- package/dist/components/ds-tooltip.js +1 -1
- package/dist/components/ds-typing-indicator.js +1 -1
- package/dist/components/{p--pJyciTI.js → p--_bPiNCd.js} +2 -2
- package/dist/components/{p-UJMEz8_O.js → p-3SCy1PHa.js} +2 -2
- package/dist/components/{p-7MwoWtTJ.js → p-4sBUSsqs.js} +2 -2
- package/dist/components/p-4sBUSsqs.js.map +1 -0
- package/dist/components/{p-kwS-vkau.js → p-5aZVNYQz.js} +2 -2
- package/dist/components/{p-DKGW3gRa.js → p-AGqUGzNZ.js} +2 -2
- package/dist/components/{p-DbW8S5qe.js → p-BJf49edB.js} +2 -2
- package/dist/components/{p-BNarCFJZ.js → p-BKyE025p.js} +2 -2
- package/dist/components/{p-S1Ct3FJV.js → p-BNN9KkXe.js} +2 -2
- package/dist/components/p-BPDo1eQV.js +2 -0
- package/dist/components/{p-xMBaOeKa.js.map → p-BPDo1eQV.js.map} +1 -1
- package/dist/components/{p-CwdRaJER.js → p-BWJb2rih.js} +2 -2
- package/dist/components/{p-D8OWhnID.js → p-BbvAWFJA.js} +2 -2
- package/dist/components/{p-HfZ1gI2q.js → p-Bdp2V6-c.js} +2 -2
- package/dist/components/{p-CIMyD_6H.js → p-BjBFJ2dd.js} +2 -2
- package/dist/components/{p-BmIllmq2.js → p-BwNW5nqj.js} +2 -2
- package/dist/components/p-C0L-VnSr.js +2 -0
- package/dist/components/{p-CElsKlxj.js.map → p-C0L-VnSr.js.map} +1 -1
- package/dist/components/p-C2e7BkNn.js +2 -0
- package/dist/components/p-C2e7BkNn.js.map +1 -0
- package/dist/components/{p-BnuNaXR2.js → p-C4t7HnfZ.js} +2 -2
- package/dist/components/{p-C1ZfBbJ_.js → p-C7_awFtr.js} +2 -2
- package/dist/components/{p-leZFu-cd.js → p-CCsxoztj.js} +2 -2
- package/dist/components/{p-DYtVsnDS.js → p-CHyxUWqL.js} +2 -2
- package/dist/components/{p-d76Ry5nh.js → p-CKSJwSWd.js} +2 -2
- package/dist/components/p-CLqqb0Jp.js +2 -0
- package/dist/components/{p-BZY40-pE.js.map → p-CLqqb0Jp.js.map} +1 -1
- package/dist/components/{p-DdFeWC-e.js → p-CRhoeCC8.js} +2 -2
- package/dist/components/p-CWc6Blcp.js +2 -0
- package/dist/components/{p-B95UP66y.js.map → p-CWc6Blcp.js.map} +1 -1
- package/dist/components/{p-DYGllrVT.js → p-CXB_T13u.js} +2 -2
- package/dist/components/{p-DObhRLBa.js → p-CgC4SM5b.js} +2 -2
- package/dist/components/p-CqAC2IQO.js +2 -0
- package/dist/components/{p-5-gQbIIz.js.map → p-CqAC2IQO.js.map} +1 -1
- package/dist/components/{p-KA8srEed.js → p-Cs8Gq-PZ.js} +2 -2
- package/dist/components/{p-DcxY_pdJ.js → p-Ct2KQHF4.js} +2 -2
- package/dist/components/{p-B3o9zn3Y.js → p-CvIHBa8S.js} +2 -2
- package/dist/components/{p-YC7UM4jH.js → p-D1tmszmN.js} +2 -2
- package/dist/components/{p-CaFXrn_8.js → p-D62yUMhr.js} +2 -2
- package/dist/components/{p-CgQB8Ct5.js → p-D9l0QBgN.js} +2 -2
- package/dist/components/{p-Dr5rkAhe.js → p-DGrSro-o.js} +2 -2
- package/dist/components/{p-BVxK8ce0.js → p-DKvHBXwn.js} +2 -2
- package/dist/components/p-D_xZ5SFL.js +2 -0
- package/dist/components/{p-DP07JCed.js.map → p-D_xZ5SFL.js.map} +1 -1
- package/dist/components/{p-Ouk6FCxc.js → p-DdNJPBCS.js} +2 -2
- package/dist/components/p-DiPqdcD4.js +2 -0
- package/dist/components/{p-wXKIVUTn.js.map → p-DiPqdcD4.js.map} +1 -1
- package/dist/components/p-Dkh_40bl.js +2 -0
- package/dist/components/p-Dkh_40bl.js.map +1 -0
- package/dist/components/p-DmgDVoDb.js +2 -0
- package/dist/components/{p-BR9Pjp15.js.map → p-DmgDVoDb.js.map} +1 -1
- package/dist/components/{p-DXL5D30P.js → p-Du8LT8JS.js} +2 -2
- package/dist/components/{p-C6kUn-vb.js → p-Dyjs8_Cl.js} +2 -2
- package/dist/components/{p-CUYLGe5d.js → p-LJXiEPw1.js} +2 -2
- package/dist/components/{p-CbCN9ew9.js → p-QGtBlTMt.js} +2 -2
- package/dist/components/{p-CSxwWcwW.js → p-UBQ5O7B-.js} +2 -2
- package/dist/components/{p-DVsGfz7J.js → p-dSNHIJz6.js} +2 -2
- package/dist/components/{p-B9y6azEw.js → p-eKnJNQtL.js} +2 -2
- package/dist/components/{p-B-pkoIX9.js → p-g2OUK8Z0.js} +2 -2
- package/dist/components/{p-DSmDyI2o.js → p-n_OBbh94.js} +2 -2
- package/dist/components/{p-OJ5WeR05.js → p-oBpjD9OA.js} +2 -2
- package/dist/components/p-pJ6gbwSa.js +2 -0
- package/dist/components/p-pJ6gbwSa.js.map +1 -0
- package/dist/components/{p-DfMTDFcE.js → p-v9Nhw5yM.js} +2 -2
- package/dist/docs/components.json +683 -7
- package/dist/lint/index.js +1 -1
- package/dist/mcp-data/registry/navigation-tab-group.json +279 -0
- package/dist/mcp-data/registry/registry.json +332 -4
- package/dist/mcp-data/registry/select-toggle.json +351 -0
- package/dist/mcp-data/registry/slider.json +6 -5
- package/dist/mcp-data/registry/switch.json +2 -2
- package/dist/mcp-data/registry/tab-group.json +14 -8
- package/dist/mcp-data/registry/table.json +22 -3
- package/dist/react/components.d.ts +2 -0
- package/dist/react/components.js +2 -0
- package/dist/react/ds-navigation-tab-group.d.ts +12 -0
- package/dist/react/ds-navigation-tab-group.js +12 -0
- package/dist/react/ds-select-toggle.d.ts +12 -0
- package/dist/react/ds-select-toggle.js +12 -0
- package/dist/types/components/NavigationTabGroup/NavigationTabGroup.d.ts +16 -0
- package/dist/types/components/SelectToggle/SelectToggle.d.ts +46 -0
- package/dist/types/components/Slider/Slider.d.ts +1 -1
- package/dist/types/components/TabGroup/TabGroup.d.ts +2 -2
- package/dist/types/components/Table/Table.d.ts +3 -0
- package/dist/types/components.d.ts +237 -15
- package/dist/vue/components.d.ts +2 -0
- package/dist/vue/components.js +2 -0
- package/dist/vue/ds-navigation-tab-group.d.ts +3 -0
- package/dist/vue/ds-navigation-tab-group.js +16 -0
- package/dist/vue/ds-select-toggle.d.ts +3 -0
- package/dist/vue/ds-select-toggle.js +19 -0
- package/dist/vue/ds-table.js +1 -0
- package/package.json +1 -1
- package/dist/components/p-5-gQbIIz.js +0 -2
- package/dist/components/p-7MwoWtTJ.js.map +0 -1
- package/dist/components/p-B95UP66y.js +0 -2
- package/dist/components/p-BR9Pjp15.js +0 -2
- package/dist/components/p-BZY40-pE.js +0 -2
- package/dist/components/p-Bf-BphXP.js +0 -2
- package/dist/components/p-Bf-BphXP.js.map +0 -1
- package/dist/components/p-BqipGGua.js +0 -2
- package/dist/components/p-BqipGGua.js.map +0 -1
- package/dist/components/p-CElsKlxj.js +0 -2
- package/dist/components/p-DP07JCed.js +0 -2
- package/dist/components/p-wXKIVUTn.js +0 -2
- package/dist/components/p-xMBaOeKa.js +0 -2
- /package/dist/components/{p--pJyciTI.js.map → p--_bPiNCd.js.map} +0 -0
- /package/dist/components/{p-UJMEz8_O.js.map → p-3SCy1PHa.js.map} +0 -0
- /package/dist/components/{p-kwS-vkau.js.map → p-5aZVNYQz.js.map} +0 -0
- /package/dist/components/{p-DKGW3gRa.js.map → p-AGqUGzNZ.js.map} +0 -0
- /package/dist/components/{p-DbW8S5qe.js.map → p-BJf49edB.js.map} +0 -0
- /package/dist/components/{p-BNarCFJZ.js.map → p-BKyE025p.js.map} +0 -0
- /package/dist/components/{p-S1Ct3FJV.js.map → p-BNN9KkXe.js.map} +0 -0
- /package/dist/components/{p-CwdRaJER.js.map → p-BWJb2rih.js.map} +0 -0
- /package/dist/components/{p-D8OWhnID.js.map → p-BbvAWFJA.js.map} +0 -0
- /package/dist/components/{p-HfZ1gI2q.js.map → p-Bdp2V6-c.js.map} +0 -0
- /package/dist/components/{p-CIMyD_6H.js.map → p-BjBFJ2dd.js.map} +0 -0
- /package/dist/components/{p-BmIllmq2.js.map → p-BwNW5nqj.js.map} +0 -0
- /package/dist/components/{p-BnuNaXR2.js.map → p-C4t7HnfZ.js.map} +0 -0
- /package/dist/components/{p-C1ZfBbJ_.js.map → p-C7_awFtr.js.map} +0 -0
- /package/dist/components/{p-leZFu-cd.js.map → p-CCsxoztj.js.map} +0 -0
- /package/dist/components/{p-DYtVsnDS.js.map → p-CHyxUWqL.js.map} +0 -0
- /package/dist/components/{p-d76Ry5nh.js.map → p-CKSJwSWd.js.map} +0 -0
- /package/dist/components/{p-DdFeWC-e.js.map → p-CRhoeCC8.js.map} +0 -0
- /package/dist/components/{p-DYGllrVT.js.map → p-CXB_T13u.js.map} +0 -0
- /package/dist/components/{p-DObhRLBa.js.map → p-CgC4SM5b.js.map} +0 -0
- /package/dist/components/{p-KA8srEed.js.map → p-Cs8Gq-PZ.js.map} +0 -0
- /package/dist/components/{p-DcxY_pdJ.js.map → p-Ct2KQHF4.js.map} +0 -0
- /package/dist/components/{p-B3o9zn3Y.js.map → p-CvIHBa8S.js.map} +0 -0
- /package/dist/components/{p-YC7UM4jH.js.map → p-D1tmszmN.js.map} +0 -0
- /package/dist/components/{p-CaFXrn_8.js.map → p-D62yUMhr.js.map} +0 -0
- /package/dist/components/{p-CgQB8Ct5.js.map → p-D9l0QBgN.js.map} +0 -0
- /package/dist/components/{p-Dr5rkAhe.js.map → p-DGrSro-o.js.map} +0 -0
- /package/dist/components/{p-BVxK8ce0.js.map → p-DKvHBXwn.js.map} +0 -0
- /package/dist/components/{p-Ouk6FCxc.js.map → p-DdNJPBCS.js.map} +0 -0
- /package/dist/components/{p-DXL5D30P.js.map → p-Du8LT8JS.js.map} +0 -0
- /package/dist/components/{p-C6kUn-vb.js.map → p-Dyjs8_Cl.js.map} +0 -0
- /package/dist/components/{p-CUYLGe5d.js.map → p-LJXiEPw1.js.map} +0 -0
- /package/dist/components/{p-CbCN9ew9.js.map → p-QGtBlTMt.js.map} +0 -0
- /package/dist/components/{p-CSxwWcwW.js.map → p-UBQ5O7B-.js.map} +0 -0
- /package/dist/components/{p-DVsGfz7J.js.map → p-dSNHIJz6.js.map} +0 -0
- /package/dist/components/{p-B9y6azEw.js.map → p-eKnJNQtL.js.map} +0 -0
- /package/dist/components/{p-B-pkoIX9.js.map → p-g2OUK8Z0.js.map} +0 -0
- /package/dist/components/{p-DSmDyI2o.js.map → p-n_OBbh94.js.map} +0 -0
- /package/dist/components/{p-OJ5WeR05.js.map → p-oBpjD9OA.js.map} +0 -0
- /package/dist/components/{p-DfMTDFcE.js.map → p-v9Nhw5yM.js.map} +0 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "select-toggle",
|
|
4
|
+
"title": "SelectToggle",
|
|
5
|
+
"description": "Compact cycling choice control with a vertical position indicator.",
|
|
6
|
+
"type": "registry:ui",
|
|
7
|
+
"meta": {
|
|
8
|
+
"library": "@ds-mo/ui",
|
|
9
|
+
"distribution": "npm",
|
|
10
|
+
"storybook": "https://zainadeel.github.io/compomo/?path=/story/select-toggle",
|
|
11
|
+
"source": "src/wc/components/SelectToggle/SelectToggle.tsx",
|
|
12
|
+
"intentStatus": "complete",
|
|
13
|
+
"intent": {
|
|
14
|
+
"audience": "general",
|
|
15
|
+
"status": "experimental",
|
|
16
|
+
"summary": "Compact cycling choice control with a vertical position indicator.",
|
|
17
|
+
"useWhen": [
|
|
18
|
+
"Two or three local view modes should cycle through one compact button without opening a popup.",
|
|
19
|
+
"The current choice should update its label and/or icon while a small vertical dot indicator shows its position."
|
|
20
|
+
],
|
|
21
|
+
"avoidWhen": [
|
|
22
|
+
"The full choice set must remain visible at once; use TabGroup instead.",
|
|
23
|
+
"The user must inspect or search more than three options; use Select instead.",
|
|
24
|
+
"The value must participate in native form submission; this initial component is a local cycling control, not a form-associated select."
|
|
25
|
+
],
|
|
26
|
+
"alternatives": [
|
|
27
|
+
{
|
|
28
|
+
"when": "Every choice should be visible and individually selectable.",
|
|
29
|
+
"component": "component:ds-tab-group",
|
|
30
|
+
"reason": "TabGroup exposes all choices and owns segmented keyboard selection."
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"when": "The option list exceeds three choices or needs a dropdown.",
|
|
34
|
+
"component": "component:ds-select",
|
|
35
|
+
"reason": "Select provides a popup and the complete form-select contract."
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"commonlyComposedWith": [
|
|
39
|
+
"component:ds-text",
|
|
40
|
+
"component:ds-icon"
|
|
41
|
+
],
|
|
42
|
+
"accessibility": [
|
|
43
|
+
"A single native button owns focus and activation by pointer, Enter, or Space; the dots are decorative and hidden from assistive technology.",
|
|
44
|
+
"Each option supplies a nonempty label, including icon-only choices. The button name announces the current label, position, and next choice; aria-label adds stable context.",
|
|
45
|
+
"An inactive button is natively disabled. Do not use aria-pressed because this is a multi-choice cycle, not a binary toggle."
|
|
46
|
+
],
|
|
47
|
+
"states": [
|
|
48
|
+
"The application supplies exactly two or three uniquely valued options and may set value; invalid values resolve to the first choice without emitting a user-change event.",
|
|
49
|
+
"Activation advances to the next choice, wraps after the last, updates the mutable value, and emits dsChange once with the next value.",
|
|
50
|
+
"Variant selects label, icon, or icon-label content; the trailing slot scales with control density and displays the same number of vertical dots as choices.",
|
|
51
|
+
"Both treatments use transparent unfilled chrome; hasBorder removes only the inset stroke while preserving control geometry."
|
|
52
|
+
],
|
|
53
|
+
"responsiveBehavior": [
|
|
54
|
+
"Hug follows the current content; fill uses the parent width and truncates a long label without moving the trailing indicator.",
|
|
55
|
+
"When collapseLabel is enabled in a table caption or data toolbar narrower than 900px, icon-label choices display only the icon while preserving the changing accessible name. Explicit icon and label variants are unchanged.",
|
|
56
|
+
"Small, medium, and large use shared control-density heights and matching dot-slot geometry."
|
|
57
|
+
],
|
|
58
|
+
"frameworkCaveats": {
|
|
59
|
+
"customElements": [
|
|
60
|
+
"Assign options as a JavaScript property and listen for dsChange to observe user cycling."
|
|
61
|
+
],
|
|
62
|
+
"react": [
|
|
63
|
+
"Pass options as an array and handle onDsChange to observe user cycling."
|
|
64
|
+
],
|
|
65
|
+
"vue": [
|
|
66
|
+
"Bind options as an array and handle dsChange to update application state."
|
|
67
|
+
],
|
|
68
|
+
"angular": [
|
|
69
|
+
"Bind options as an array and handle dsChange to update application state."
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
"references": [
|
|
73
|
+
{
|
|
74
|
+
"label": "Storybook examples",
|
|
75
|
+
"path": "src/wc/components/SelectToggle/SelectToggle.stories.ts"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"label": "Rendered behavior and geometry",
|
|
79
|
+
"path": "tests/e2e/select-toggle.spec.ts"
|
|
80
|
+
}
|
|
81
|
+
],
|
|
82
|
+
"styling": {
|
|
83
|
+
"rationale": "The component owns one density-sized unfilled button, changing choice content, and a fixed trailing position indicator; applications own surrounding layout and the choice data.",
|
|
84
|
+
"protected": [
|
|
85
|
+
{
|
|
86
|
+
"properties": [
|
|
87
|
+
"height",
|
|
88
|
+
"padding",
|
|
89
|
+
"gap",
|
|
90
|
+
"border-radius",
|
|
91
|
+
"background",
|
|
92
|
+
"border",
|
|
93
|
+
"color"
|
|
94
|
+
],
|
|
95
|
+
"reason": "Control density and the indicator slot stay aligned across the supported variants.",
|
|
96
|
+
"props": [
|
|
97
|
+
"size",
|
|
98
|
+
"variant",
|
|
99
|
+
"width",
|
|
100
|
+
"hasBorder"
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
],
|
|
104
|
+
"customProperties": []
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
"api": {
|
|
108
|
+
"props": {
|
|
109
|
+
"ariaLabel": {
|
|
110
|
+
"type": "string | null",
|
|
111
|
+
"resolvedType": "null | string",
|
|
112
|
+
"attribute": "aria-label",
|
|
113
|
+
"default": "null",
|
|
114
|
+
"required": false,
|
|
115
|
+
"mutable": false,
|
|
116
|
+
"description": "Optional stable context prepended to the changing accessible choice name."
|
|
117
|
+
},
|
|
118
|
+
"collapseLabel": {
|
|
119
|
+
"type": "boolean",
|
|
120
|
+
"resolvedType": "boolean",
|
|
121
|
+
"attribute": "collapse-label",
|
|
122
|
+
"default": "false",
|
|
123
|
+
"required": false,
|
|
124
|
+
"mutable": false,
|
|
125
|
+
"description": "Collapse an icon-label choice to its icon in a narrow table caption or data toolbar."
|
|
126
|
+
},
|
|
127
|
+
"hasBorder": {
|
|
128
|
+
"type": "boolean",
|
|
129
|
+
"resolvedType": "boolean",
|
|
130
|
+
"attribute": "has-border",
|
|
131
|
+
"default": "true",
|
|
132
|
+
"required": false,
|
|
133
|
+
"mutable": false,
|
|
134
|
+
"description": "Show an inset border; both treatments keep the transparent unfilled surface."
|
|
135
|
+
},
|
|
136
|
+
"isInactive": {
|
|
137
|
+
"type": "boolean",
|
|
138
|
+
"resolvedType": "boolean",
|
|
139
|
+
"attribute": "is-inactive",
|
|
140
|
+
"default": "false",
|
|
141
|
+
"required": false,
|
|
142
|
+
"mutable": false,
|
|
143
|
+
"description": "Prevent interaction."
|
|
144
|
+
},
|
|
145
|
+
"options": {
|
|
146
|
+
"type": "SelectToggleOption[]",
|
|
147
|
+
"resolvedType": "SelectToggleOption[]",
|
|
148
|
+
"default": "[]",
|
|
149
|
+
"required": false,
|
|
150
|
+
"mutable": false,
|
|
151
|
+
"description": "Exactly two or three choices, each with a unique value and accessible label."
|
|
152
|
+
},
|
|
153
|
+
"size": {
|
|
154
|
+
"type": "SelectToggleSize",
|
|
155
|
+
"resolvedType": "\"lg\" | \"md\" | \"sm\"",
|
|
156
|
+
"attribute": "size",
|
|
157
|
+
"default": "'md'",
|
|
158
|
+
"required": false,
|
|
159
|
+
"mutable": false,
|
|
160
|
+
"description": "Control density: small, medium, or large; the trailing indicator scales with it."
|
|
161
|
+
},
|
|
162
|
+
"value": {
|
|
163
|
+
"type": "string",
|
|
164
|
+
"resolvedType": "string",
|
|
165
|
+
"attribute": "value",
|
|
166
|
+
"default": "''",
|
|
167
|
+
"required": false,
|
|
168
|
+
"mutable": true,
|
|
169
|
+
"description": "Current choice. User activation updates this value and emits dsChange."
|
|
170
|
+
},
|
|
171
|
+
"variant": {
|
|
172
|
+
"type": "SelectToggleVariant",
|
|
173
|
+
"resolvedType": "\"icon\" | \"icon-label\" | \"label\"",
|
|
174
|
+
"attribute": "variant",
|
|
175
|
+
"default": "'label'",
|
|
176
|
+
"required": false,
|
|
177
|
+
"mutable": false,
|
|
178
|
+
"description": "Show the current label, icon, or both. Icon variants require an icon on each choice."
|
|
179
|
+
},
|
|
180
|
+
"width": {
|
|
181
|
+
"type": "ControlWidth",
|
|
182
|
+
"resolvedType": "\"fill\" | \"hug\"",
|
|
183
|
+
"attribute": "width",
|
|
184
|
+
"default": "'hug'",
|
|
185
|
+
"required": false,
|
|
186
|
+
"mutable": false,
|
|
187
|
+
"description": "Hug the content or fill the parent."
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
"events": [
|
|
191
|
+
{
|
|
192
|
+
"name": "dsChange",
|
|
193
|
+
"detail": "string",
|
|
194
|
+
"bubbles": true,
|
|
195
|
+
"cancelable": true,
|
|
196
|
+
"composed": true
|
|
197
|
+
}
|
|
198
|
+
],
|
|
199
|
+
"methods": [],
|
|
200
|
+
"slots": []
|
|
201
|
+
},
|
|
202
|
+
"props": {
|
|
203
|
+
"ariaLabel": {
|
|
204
|
+
"type": "string | null",
|
|
205
|
+
"resolvedType": "null | string",
|
|
206
|
+
"attribute": "aria-label",
|
|
207
|
+
"default": "null",
|
|
208
|
+
"required": false,
|
|
209
|
+
"mutable": false,
|
|
210
|
+
"description": "Optional stable context prepended to the changing accessible choice name."
|
|
211
|
+
},
|
|
212
|
+
"collapseLabel": {
|
|
213
|
+
"type": "boolean",
|
|
214
|
+
"resolvedType": "boolean",
|
|
215
|
+
"attribute": "collapse-label",
|
|
216
|
+
"default": "false",
|
|
217
|
+
"required": false,
|
|
218
|
+
"mutable": false,
|
|
219
|
+
"description": "Collapse an icon-label choice to its icon in a narrow table caption or data toolbar."
|
|
220
|
+
},
|
|
221
|
+
"hasBorder": {
|
|
222
|
+
"type": "boolean",
|
|
223
|
+
"resolvedType": "boolean",
|
|
224
|
+
"attribute": "has-border",
|
|
225
|
+
"default": "true",
|
|
226
|
+
"required": false,
|
|
227
|
+
"mutable": false,
|
|
228
|
+
"description": "Show an inset border; both treatments keep the transparent unfilled surface."
|
|
229
|
+
},
|
|
230
|
+
"isInactive": {
|
|
231
|
+
"type": "boolean",
|
|
232
|
+
"resolvedType": "boolean",
|
|
233
|
+
"attribute": "is-inactive",
|
|
234
|
+
"default": "false",
|
|
235
|
+
"required": false,
|
|
236
|
+
"mutable": false,
|
|
237
|
+
"description": "Prevent interaction."
|
|
238
|
+
},
|
|
239
|
+
"options": {
|
|
240
|
+
"type": "SelectToggleOption[]",
|
|
241
|
+
"resolvedType": "SelectToggleOption[]",
|
|
242
|
+
"default": "[]",
|
|
243
|
+
"required": false,
|
|
244
|
+
"mutable": false,
|
|
245
|
+
"description": "Exactly two or three choices, each with a unique value and accessible label."
|
|
246
|
+
},
|
|
247
|
+
"size": {
|
|
248
|
+
"type": "SelectToggleSize",
|
|
249
|
+
"resolvedType": "\"lg\" | \"md\" | \"sm\"",
|
|
250
|
+
"attribute": "size",
|
|
251
|
+
"default": "'md'",
|
|
252
|
+
"required": false,
|
|
253
|
+
"mutable": false,
|
|
254
|
+
"description": "Control density: small, medium, or large; the trailing indicator scales with it."
|
|
255
|
+
},
|
|
256
|
+
"value": {
|
|
257
|
+
"type": "string",
|
|
258
|
+
"resolvedType": "string",
|
|
259
|
+
"attribute": "value",
|
|
260
|
+
"default": "''",
|
|
261
|
+
"required": false,
|
|
262
|
+
"mutable": true,
|
|
263
|
+
"description": "Current choice. User activation updates this value and emits dsChange."
|
|
264
|
+
},
|
|
265
|
+
"variant": {
|
|
266
|
+
"type": "SelectToggleVariant",
|
|
267
|
+
"resolvedType": "\"icon\" | \"icon-label\" | \"label\"",
|
|
268
|
+
"attribute": "variant",
|
|
269
|
+
"default": "'label'",
|
|
270
|
+
"required": false,
|
|
271
|
+
"mutable": false,
|
|
272
|
+
"description": "Show the current label, icon, or both. Icon variants require an icon on each choice."
|
|
273
|
+
},
|
|
274
|
+
"width": {
|
|
275
|
+
"type": "ControlWidth",
|
|
276
|
+
"resolvedType": "\"fill\" | \"hug\"",
|
|
277
|
+
"attribute": "width",
|
|
278
|
+
"default": "'hug'",
|
|
279
|
+
"required": false,
|
|
280
|
+
"mutable": false,
|
|
281
|
+
"description": "Hug the content or fill the parent."
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
"events": [
|
|
285
|
+
{
|
|
286
|
+
"name": "dsChange",
|
|
287
|
+
"detail": "string",
|
|
288
|
+
"bubbles": true,
|
|
289
|
+
"cancelable": true,
|
|
290
|
+
"composed": true
|
|
291
|
+
}
|
|
292
|
+
],
|
|
293
|
+
"methods": [],
|
|
294
|
+
"slots": [],
|
|
295
|
+
"exports": {
|
|
296
|
+
"customElement": "ds-select-toggle",
|
|
297
|
+
"react": "DsSelectToggle",
|
|
298
|
+
"vue": "DsSelectToggle",
|
|
299
|
+
"angular": "DsSelectToggle"
|
|
300
|
+
},
|
|
301
|
+
"consumption": {
|
|
302
|
+
"install": "npm install @ds-mo/ui @ds-mo/tokens @ds-mo/icons",
|
|
303
|
+
"cssSetup": "import '@ds-mo/tokens';\nimport '@ds-mo/tokens/reset';\nimport '@ds-mo/tokens/globals';",
|
|
304
|
+
"customElements": {
|
|
305
|
+
"import": "import '@ds-mo/ui/dist/components/ds-select-toggle.js';",
|
|
306
|
+
"example": "<ds-select-toggle></ds-select-toggle>"
|
|
307
|
+
},
|
|
308
|
+
"react": {
|
|
309
|
+
"import": "import { DsSelectToggle } from '@ds-mo/ui/react';",
|
|
310
|
+
"example": "<DsSelectToggle />"
|
|
311
|
+
},
|
|
312
|
+
"vue": {
|
|
313
|
+
"import": "import { DsSelectToggle } from '@ds-mo/ui/vue';",
|
|
314
|
+
"example": "<DsSelectToggle />"
|
|
315
|
+
},
|
|
316
|
+
"angular": {
|
|
317
|
+
"import": "import { DsSelectToggle } from '@ds-mo/ui/angular/ds-select-toggle';",
|
|
318
|
+
"example": "<ds-select-toggle></ds-select-toggle>"
|
|
319
|
+
},
|
|
320
|
+
"complexPropertyNote": "Assign these non-primitive values as JavaScript properties: options.",
|
|
321
|
+
"peerDependencies": {
|
|
322
|
+
"required": [
|
|
323
|
+
"@ds-mo/tokens ^6.7.0",
|
|
324
|
+
"@ds-mo/icons ^8.1.1"
|
|
325
|
+
],
|
|
326
|
+
"frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
"dependencies": [
|
|
331
|
+
"@ds-mo/ui",
|
|
332
|
+
"@ds-mo/tokens",
|
|
333
|
+
"@ds-mo/icons"
|
|
334
|
+
],
|
|
335
|
+
"registryDependencies": [
|
|
336
|
+
"icon",
|
|
337
|
+
"text"
|
|
338
|
+
],
|
|
339
|
+
"files": [
|
|
340
|
+
{
|
|
341
|
+
"path": "src/wc/components/SelectToggle/SelectToggle.css",
|
|
342
|
+
"content": "@import '../../utils/radius.css';\n@import '../../utils/control-density.css';\n@import '../../utils/control-parts.css';\n@import '../../utils/control-width.css';\n@import '../../utils/focus-ring.css';\n@import '../../utils/interaction-fill.css';\n@import '../../utils/control-inactive.css';\n\n:host {\n display: inline-flex;\n min-inline-size: 0;\n --_select-toggle-dot-position: var(--dimension-size-050);\n --_select-toggle-dot-idle: var(--dimension-space-025);\n --_select-toggle-dot-active: var(--_select-toggle-dot-idle);\n --_select-toggle-dot-gap: var(--dimension-space-012);\n}\n\n:host(.ds-control--sm) {\n --_select-toggle-dot-position: calc(var(--dimension-size-050) * 3 / 4);\n --_select-toggle-dot-idle: var(--dimension-space-012);\n}\n\n:host(.ds-control--lg) {\n --_select-toggle-dot-position: calc(var(--dimension-size-050) * 5 / 4);\n --_select-toggle-dot-idle: calc(var(--dimension-size-050) * 3 / 4);\n --_select-toggle-dot-gap: var(--dimension-space-025);\n}\n\n:host(.ds-control-width--fill) {\n display: flex;\n width: 100%;\n}\n\n.trigger {\n appearance: none;\n min-inline-size: 0;\n max-inline-size: 100%;\n justify-content: space-between;\n background: var(--color-background-transparent);\n border: 0;\n color: var(--color-foreground-secondary);\n font-family: inherit;\n -webkit-user-select: none;\n user-select: none;\n}\n\n.trigger--bordered {\n --ds-interaction-border-width: var(--dimension-stroke-width-012);\n --ds-interaction-border-color: var(--color-border-tertiary);\n}\n\n:host(.ds-control-width--fill) .trigger {\n width: 100%;\n}\n\n.trigger__content {\n display: inline-flex;\n align-items: center;\n flex: 1 1 auto;\n gap: var(--ds-control-gap);\n min-width: 0;\n overflow: hidden;\n}\n\n.trigger__label {\n min-width: 0;\n overflow: hidden;\n text-align: left;\n}\n\n.trigger__indicator {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n flex: 0 0 calc(var(--ds-control-icon) - var(--dimension-size-100));\n width: calc(var(--ds-control-icon) - var(--dimension-size-100));\n height: var(--ds-control-icon);\n gap: var(--_select-toggle-dot-gap);\n}\n\n.trigger__dot-position {\n display: grid;\n place-items: center;\n flex: 0 0 var(--_select-toggle-dot-position);\n width: var(--_select-toggle-dot-position);\n height: var(--_select-toggle-dot-position);\n}\n\n.trigger__dot {\n width: var(--_select-toggle-dot-idle);\n height: var(--_select-toggle-dot-idle);\n border-radius: var(--dimension-radius-half);\n background: var(--color-foreground-quaternary);\n}\n\n.trigger__dot--active {\n width: var(--_select-toggle-dot-active);\n height: var(--_select-toggle-dot-active);\n background: var(--color-foreground-secondary);\n}\n\n@media (forced-colors: active) {\n .trigger {\n outline: var(--dimension-stroke-width-012) solid var(--ds-forced-color-control-boundary);\n outline-offset: calc(-1 * var(--dimension-stroke-width-012));\n }\n\n .trigger__dot {\n background: var(--ds-forced-color-content);\n }\n\n .trigger__dot--active {\n background: var(--ds-forced-color-selected);\n }\n}\n",
|
|
343
|
+
"type": "registry:ui"
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
"path": "src/wc/components/SelectToggle/SelectToggle.tsx",
|
|
347
|
+
"content": "import {\n Component,\n Element,\n Event,\n EventEmitter,\n h,\n Host,\n Prop,\n State,\n Watch,\n} from '@stencil/core';\nimport { controlWidthClass, CONTROL_TEXT_VARIANT, type ControlWidth } from '../../utils';\nimport { observeTableCaptionCompact } from '../../utils/table-caption-compact';\n\nexport interface SelectToggleOption {\n value: string;\n label: string;\n icon?: string;\n}\n\nexport type SelectToggleVariant = 'label' | 'icon' | 'icon-label';\nexport type SelectToggleSize = 'sm' | 'md' | 'lg';\n\n@Component({ tag: 'ds-select-toggle', styleUrl: 'SelectToggle.css', scoped: true })\nexport class SelectToggle {\n @Element() el!: HTMLElement;\n\n /** Exactly two or three choices, each with a unique value and accessible label. */\n @Prop() options: SelectToggleOption[] = [];\n\n /** Current choice. User activation updates this value and emits dsChange. */\n @Prop({ mutable: true }) value: string = '';\n\n /** Show the current label, icon, or both. Icon variants require an icon on each choice. */\n @Prop() variant: SelectToggleVariant = 'label';\n\n /** Collapse an icon-label choice to its icon in a narrow table caption or data toolbar. */\n @Prop({ reflect: true }) collapseLabel: boolean = false;\n\n /** Control density: small, medium, or large; the trailing indicator scales with it. */\n @Prop() size: SelectToggleSize = 'md';\n\n /** Hug the content or fill the parent. */\n @Prop() width: ControlWidth = 'hug';\n\n /** Show an inset border; both treatments keep the transparent unfilled surface. */\n @Prop() hasBorder: boolean = true;\n\n /** Prevent interaction. */\n @Prop() isInactive: boolean = false;\n\n /** Optional stable context prepended to the changing accessible choice name. */\n @Prop({ attribute: 'aria-label' }) ariaLabel: string | null = null;\n\n @Event() dsChange!: EventEmitter<string>;\n\n @State() private captionCompact = false;\n\n private captionCompactDisconnect: (() => void) | undefined;\n private hasLoaded = false;\n\n componentWillLoad() {\n this.reconcileValue();\n }\n\n componentDidLoad(): void {\n this.hasLoaded = true;\n this.syncCaptionCompactObserver();\n }\n\n connectedCallback(): void {\n if (this.hasLoaded) this.syncCaptionCompactObserver();\n }\n\n disconnectedCallback(): void {\n this.disconnectCaptionCompactObserver();\n }\n\n @Watch('collapseLabel')\n onCollapseLabelChange(): void {\n this.syncCaptionCompactObserver();\n }\n\n @Watch('options')\n @Watch('value')\n reconcileValue() {\n const choices = this.validOptions;\n if (choices.length && !choices.some(option => option.value === this.value)) {\n this.value = choices[0].value;\n }\n }\n\n private get validOptions(): SelectToggleOption[] {\n if (this.options.length < 2 || this.options.length > 3) return [];\n if (\n this.options.some(option => !option.value || !option.label) ||\n new Set(this.options.map(option => option.value)).size !== this.options.length\n ) {\n return [];\n }\n return this.options;\n }\n\n private advance = () => {\n const choices = this.validOptions;\n if (this.isInactive || !choices.length) return;\n const current = Math.max(\n 0,\n choices.findIndex(option => option.value === this.value)\n );\n const next = choices[(current + 1) % choices.length];\n this.value = next.value;\n this.dsChange.emit(next.value);\n };\n\n private syncCaptionCompactObserver(): void {\n this.disconnectCaptionCompactObserver();\n if (!this.collapseLabel) {\n if (this.captionCompact) this.captionCompact = false;\n return;\n }\n this.captionCompactDisconnect = observeTableCaptionCompact(this.el, compact => {\n if (this.captionCompact !== compact) this.captionCompact = compact;\n });\n }\n\n private disconnectCaptionCompactObserver(): void {\n this.captionCompactDisconnect?.();\n this.captionCompactDisconnect = undefined;\n }\n\n render() {\n const choices = this.validOptions;\n const activeIndex = Math.max(\n 0,\n choices.findIndex(option => option.value === this.value)\n );\n const active = choices[activeIndex];\n const next = choices[(activeIndex + 1) % choices.length];\n const showIcon = this.variant !== 'label';\n const showLabel =\n this.variant !== 'icon' &&\n !(this.collapseLabel && this.captionCompact && this.variant === 'icon-label' && active?.icon);\n const accessibleName = active\n ? `${this.ariaLabel ? `${this.ariaLabel}: ` : ''}${active.label}, ${activeIndex + 1} of ${choices.length}. Next: ${next.label}`\n : undefined;\n\n return (\n <Host\n class={{\n [`ds-control--${this.size}`]: true,\n ...controlWidthClass(this.width),\n }}\n >\n {active && (\n <button\n type=\"button\"\n class={{\n trigger: true,\n 'ds-control-frame': true,\n 'ds-focus-ring-inset': true,\n 'ds-interaction-fill': true,\n 'ds-control-inactive': this.isInactive,\n 'trigger--bordered': this.hasBorder,\n }}\n aria-label={accessibleName}\n disabled={this.isInactive}\n onClick={this.advance}\n >\n <span class=\"trigger__content ds-interaction-fill__content\">\n {showIcon && active.icon && (\n <span class=\"trigger__icon ds-control-icon-box\" aria-hidden=\"true\">\n <ds-icon name={active.icon} size={this.size} color=\"inherit\" />\n </span>\n )}\n {showLabel && (\n <ds-text\n class=\"trigger__label ds-control-label-box\"\n as=\"span\"\n variant={CONTROL_TEXT_VARIANT[this.size]}\n color=\"inherit\"\n lineTruncation={1}\n >\n {active.label}\n </ds-text>\n )}\n </span>\n <span class=\"trigger__indicator ds-interaction-fill__content\" aria-hidden=\"true\">\n {choices.map((option, index) => (\n <span key={option.value} class=\"trigger__dot-position\">\n <span\n class={{\n trigger__dot: true,\n 'trigger__dot--active': index === activeIndex,\n }}\n />\n </span>\n ))}\n </span>\n </button>\n )}\n </Host>\n );\n }\n}\n",
|
|
348
|
+
"type": "registry:ui"
|
|
349
|
+
}
|
|
350
|
+
]
|
|
351
|
+
}
|
|
@@ -67,7 +67,8 @@
|
|
|
67
67
|
"Horizontal Slider fills the width supplied by its parent; the parent chooses an appropriate readable track length.",
|
|
68
68
|
"Vertical Slider owns a token-based default length that may be overridden with --ds-slider-vertical-length in layout-specific compositions.",
|
|
69
69
|
"Use md in ordinary forms and sm or xs only when the surrounding control density uses the same recipe.",
|
|
70
|
-
"Track thickness scales with density. The unfilled track
|
|
70
|
+
"Track thickness scales with density. The half-rounded unfilled track uses a faint-neutral background and tertiary border, and the filled range uses brand color. The round thumb layers a track-sized bold-brand orb with a tertiary inset border over a larger transparent, blurred, medium-elevation circle.",
|
|
71
|
+
"Edge alignment places the inner orb edge at each full-width rail endpoint while the transparent outer circle overhangs; center alignment places the orb center at inset rail endpoints.",
|
|
71
72
|
"Horizontal value direction is left-to-right; the design system does not author RTL-specific component behavior."
|
|
72
73
|
],
|
|
73
74
|
"frameworkCaveats": {
|
|
@@ -325,7 +326,7 @@
|
|
|
325
326
|
"default": "'edge'",
|
|
326
327
|
"required": false,
|
|
327
328
|
"mutable": false,
|
|
328
|
-
"description": "Align the
|
|
329
|
+
"description": "Align the inner orb edge with full-width rail endpoints, or its center with inset rail endpoints."
|
|
329
330
|
},
|
|
330
331
|
"ticks": {
|
|
331
332
|
"type": "SliderTick[]",
|
|
@@ -590,7 +591,7 @@
|
|
|
590
591
|
"default": "'edge'",
|
|
591
592
|
"required": false,
|
|
592
593
|
"mutable": false,
|
|
593
|
-
"description": "Align the
|
|
594
|
+
"description": "Align the inner orb edge with full-width rail endpoints, or its center with inset rail endpoints."
|
|
594
595
|
},
|
|
595
596
|
"ticks": {
|
|
596
597
|
"type": "SliderTick[]",
|
|
@@ -712,12 +713,12 @@
|
|
|
712
713
|
"files": [
|
|
713
714
|
{
|
|
714
715
|
"path": "src/wc/components/Slider/Slider.css",
|
|
715
|
-
"content": "@import '../../utils/control-inactive.css';\n@import '../../utils/field-stack.css';\n@import '../../utils/forced-colors.css';\n\n:host {\n --ds-slider-control-size: var(--dimension-size-400);\n --ds-slider-thumb-size: var(--dimension-size-200);\n --ds-slider-track-size: var(--dimension-size-100);\n --ds-slider-track-stroke: var(--dimension-stroke-width-012);\n --ds-slider-vertical-length: calc(var(--dimension-size-800) * 3);\n box-sizing: border-box;\n width: 100%;\n color: var(--color-foreground-primary);\n}\n\n:host(.slider--sm) {\n --ds-slider-control-size: var(--dimension-size-300);\n --ds-slider-thumb-size: var(--dimension-size-150);\n --ds-slider-track-size: var(--dimension-size-075);\n}\n\n:host(.slider--xs) {\n --ds-slider-control-size: var(--dimension-size-200);\n --ds-slider-thumb-size: var(--dimension-size-100);\n --ds-slider-track-size: var(--dimension-size-050);\n}\n\n:host(.slider--vertical) {\n width: fit-content;\n min-width: var(--dimension-form-width-xs);\n}\n\n.slider__label-row {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: var(--dimension-space-100);\n min-width: 0;\n}\n\n.slider__label {\n min-width: 0;\n}\n\n.slider__value {\n flex-shrink: 0;\n}\n\n.slider__control {\n position: relative;\n width: 100%;\n height: var(--ds-slider-control-size);\n border-radius: var(--dimension-radius-half);\n cursor: default;\n touch-action: none;\n user-select: none;\n}\n\n:host(.slider--vertical) .slider__control {\n align-self: center;\n width: var(--ds-slider-control-size);\n height: var(--ds-slider-vertical-length);\n}\n\n:host(.slider--readonly) .slider__control {\n cursor: default;\n}\n\n/* Inactive: `.ds-control-inactive` (50% opacity) on the host. */\n:host(.ds-control-inactive) .slider__control {\n cursor: not-allowed;\n}\n\n.slider__rail {\n position: absolute;\n top: 50%;\n right: 0;\n left: 0;\n height: var(--ds-slider-track-size);\n overflow: hidden;\n border-radius: var(--dimension-radius-025);\n background-color: transparent;\n box-shadow: inset 0 0 0 var(--ds-slider-track-stroke) var(--color-border-secondary);\n transform: translateY(-50%);\n pointer-events: none;\n}\n\n:host(.slider--thumb-center) .slider__rail {\n right: calc(var(--ds-slider-thumb-size) / 2);\n left: calc(var(--ds-slider-thumb-size) / 2);\n}\n\n.slider__indicator {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: calc(\n var(--ds-slider-thumb-size) / 2 + (var(--ds-slider-end-pct, 0) / 100) *\n (100% - var(--ds-slider-thumb-size))\n );\n border-radius: inherit;\n background-color: var(--color-background-bold-brand);\n}\n\n:host(.slider--range) .slider__indicator {\n left: calc(\n var(--ds-slider-thumb-size) / 2 + (var(--ds-slider-start-pct, 0) / 100) *\n (100% - var(--ds-slider-thumb-size))\n );\n width: calc(\n ((var(--ds-slider-end-pct, 0) - var(--ds-slider-start-pct, 0)) / 100) *\n (100% - var(--ds-slider-thumb-size))\n );\n}\n\n:host(.slider--thumb-center) .slider__indicator {\n left: calc(var(--ds-slider-start-pct, 0) * 1%);\n width: calc((var(--ds-slider-end-pct, 0) - var(--ds-slider-start-pct, 0)) * 1%);\n}\n\n.slider__thumb {\n position: absolute;\n top: 50%;\n left: calc((var(--ds-slider-thumb-pct, 0) / 100) * (100% - var(--ds-slider-thumb-size)));\n width: var(--ds-slider-thumb-size);\n height: var(--ds-slider-thumb-size);\n transform: translateY(-50%);\n z-index: 1;\n}\n\n.slider__thumb--active {\n z-index: 2;\n}\n\n.slider__input {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n margin: 0;\n padding: 0;\n border: 0;\n opacity: 0;\n appearance: none;\n pointer-events: none;\n z-index: 2;\n}\n\n.slider__thumb-visual {\n position: absolute;\n inset: 0;\n box-sizing: border-box;\n overflow: visible;\n border-radius: var(--dimension-radius-025);\n background-color: var(--color-background-primary);\n box-shadow: var(--effect-elevation-elevated-sm);\n pointer-events: none;\n transition:\n background-color var(--effect-motion-short-2),\n box-shadow var(--effect-motion-short-2);\n}\n\n.slider__thumb-wash {\n position: absolute;\n inset: 0;\n border-radius: inherit;\n background-color: transparent;\n transition: background-color var(--effect-motion-short-2);\n}\n\n@media (hover: hover) and (pointer: fine) {\n :host(:not(.slider--readonly):not(.ds-control-inactive))\n .slider__thumb:hover\n .slider__thumb-wash {\n background-color: var(--color-interaction-hover);\n }\n}\n\n:host([data-dragging]:not(.slider--readonly):not(.ds-control-inactive))\n .slider__thumb--active\n .slider__thumb-wash {\n background-color: var(--color-interaction-pressed);\n}\n\n.slider__input:focus-visible + .slider__thumb-visual,\n:host([data-review-focus]) .slider__thumb[data-index='0'] .slider__thumb-visual {\n outline: var(--dimension-stroke-width-025) solid var(--color-interaction-focus);\n outline-offset: var(--dimension-space-025);\n}\n\n:host(.slider--vertical) .slider__rail {\n top: 0;\n right: auto;\n bottom: 0;\n left: 50%;\n width: var(--ds-slider-track-size);\n height: auto;\n transform: translateX(-50%);\n}\n\n:host(.slider--vertical.slider--thumb-center) .slider__rail {\n top: calc(var(--ds-slider-thumb-size) / 2);\n bottom: calc(var(--ds-slider-thumb-size) / 2);\n}\n\n:host(.slider--vertical) .slider__indicator {\n top: auto;\n right: 0;\n bottom: 0;\n left: 0;\n width: auto;\n height: calc(\n var(--ds-slider-thumb-size) / 2 + (var(--ds-slider-end-pct, 0) / 100) *\n (100% - var(--ds-slider-thumb-size))\n );\n}\n\n:host(.slider--vertical.slider--range) .slider__indicator {\n bottom: calc(\n var(--ds-slider-thumb-size) / 2 + (var(--ds-slider-start-pct, 0) / 100) *\n (100% - var(--ds-slider-thumb-size))\n );\n height: calc(\n ((var(--ds-slider-end-pct, 0) - var(--ds-slider-start-pct, 0)) / 100) *\n (100% - var(--ds-slider-thumb-size))\n );\n}\n\n:host(.slider--vertical.slider--thumb-center) .slider__indicator {\n bottom: calc(var(--ds-slider-start-pct, 0) * 1%);\n height: calc((var(--ds-slider-end-pct, 0) - var(--ds-slider-start-pct, 0)) * 1%);\n}\n\n:host(.slider--vertical) .slider__thumb {\n top: auto;\n bottom: calc((var(--ds-slider-thumb-pct, 0) / 100) * (100% - var(--ds-slider-thumb-size)));\n left: 50%;\n transform: translateX(-50%);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .slider__thumb-visual,\n .slider__thumb-wash {\n transition: none;\n }\n}\n\n@media (forced-colors: active) {\n .slider__rail {\n border: var(--ds-slider-track-stroke) solid var(--ds-forced-color-content);\n box-shadow: none;\n }\n\n .slider__indicator {\n background-color: var(--ds-forced-color-selected);\n }\n\n .slider__thumb-visual {\n border: var(--dimension-stroke-width-012) solid var(--ds-forced-color-control-boundary);\n background-color: var(--ds-forced-color-surface);\n box-shadow: none;\n }\n\n .slider__input:focus-visible + .slider__thumb-visual,\n :host([data-review-focus]) .slider__thumb[data-index='0'] .slider__thumb-visual {\n outline-color: var(--ds-forced-color-selected);\n }\n}\n\n.slider__tick {\n position: absolute;\n top: calc(\n 50% + var(--ds-slider-track-size) / 2 + var(--dimension-space-050) + var(--dimension-space-012)\n );\n left: calc(\n var(--ds-slider-thumb-size) / 2 + (var(--ds-slider-tick-pct) / 100) *\n (100% - var(--ds-slider-thumb-size))\n );\n transform: translateX(-50%);\n pointer-events: none;\n}\n.slider__tick-mark {\n display: block;\n width: var(--dimension-stroke-width-025);\n height: var(--dimension-stroke-width-025);\n border-radius: var(--dimension-radius-half);\n background: var(--color-foreground-tertiary);\n}\n.slider__tick-label {\n width: max-content;\n max-width: none;\n position: absolute;\n top: calc(100% + var(--dimension-space-050));\n left: 50%;\n transform: translateX(-50%);\n white-space: nowrap;\n}\n:host(:not(.slider--vertical)) .slider__control {\n container-type: inline-size;\n}\n:host(:not(.slider--vertical)) .slider__tick-label {\n /* Center on the tick, clamping only when the text reaches the control edges. */\n --ds-slider-label-anchor: calc(\n var(--ds-slider-thumb-size) / 2 + (var(--ds-slider-tick-pct) / 100) *\n (100cqw - var(--ds-slider-thumb-size))\n );\n transform: translateX(\n clamp(\n calc(-1 * var(--ds-slider-label-anchor)),\n -50%,\n calc(100cqw - var(--ds-slider-label-anchor) - 100%)\n )\n );\n}\n:host(.slider--tick-labels:not(.slider--vertical)) .slider__control {\n margin-bottom: var(--dimension-space-200);\n}\n:host(.slider--vertical) .slider__tick {\n top: auto;\n bottom: calc(\n var(--ds-slider-thumb-size) / 2 + (var(--ds-slider-tick-pct) / 100) *\n (100% - var(--ds-slider-thumb-size))\n );\n left: calc(50% + var(--ds-slider-track-size) / 2 + var(--dimension-space-050));\n transform: translateY(50%);\n}\n:host(.slider--vertical) .slider__tick-label {\n top: 50%;\n left: calc(100% + var(--dimension-space-050));\n transform: translateY(-50%);\n}\n",
|
|
716
|
+
"content": "@import '../../utils/control-inactive.css';\n@import '../../utils/field-stack.css';\n@import '../../utils/forced-colors.css';\n\n:host {\n --ds-slider-control-size: var(--dimension-size-400);\n --ds-slider-track-size: var(--dimension-size-100);\n --ds-slider-thumb-size: calc(\n var(--ds-slider-track-size) + var(--dimension-space-050) + var(--dimension-space-050)\n );\n --ds-slider-endpoint-size: var(--ds-slider-track-size);\n --ds-slider-thumb-overhang: calc(\n (var(--ds-slider-thumb-size) - var(--ds-slider-endpoint-size)) / 2\n );\n --ds-slider-track-stroke: var(--dimension-stroke-width-012);\n --ds-slider-vertical-length: calc(var(--dimension-size-800) * 3);\n box-sizing: border-box;\n width: 100%;\n color: var(--color-foreground-primary);\n}\n\n:host(.slider--sm) {\n --ds-slider-control-size: var(--dimension-size-300);\n --ds-slider-track-size: var(--dimension-size-075);\n}\n\n:host(.slider--xs) {\n --ds-slider-control-size: var(--dimension-size-200);\n --ds-slider-track-size: var(--dimension-size-050);\n}\n\n:host(.slider--thumb-center) {\n --ds-slider-endpoint-size: var(--ds-slider-thumb-size);\n}\n\n:host(.slider--vertical) {\n width: fit-content;\n min-width: var(--dimension-form-width-xs);\n}\n\n.slider__label-row {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: var(--dimension-space-100);\n min-width: 0;\n}\n\n.slider__label {\n min-width: 0;\n}\n\n.slider__value {\n flex-shrink: 0;\n}\n\n.slider__control {\n position: relative;\n width: 100%;\n height: var(--ds-slider-control-size);\n border-radius: var(--dimension-radius-half);\n cursor: default;\n touch-action: none;\n user-select: none;\n}\n\n:host(.slider--vertical) .slider__control {\n align-self: center;\n width: var(--ds-slider-control-size);\n height: var(--ds-slider-vertical-length);\n}\n\n:host(.slider--readonly) .slider__control {\n cursor: default;\n}\n\n/* Inactive: `.ds-control-inactive` (50% opacity) on the host. */\n:host(.ds-control-inactive) .slider__control {\n cursor: not-allowed;\n}\n\n.slider__rail {\n position: absolute;\n top: 50%;\n right: 0;\n left: 0;\n height: var(--ds-slider-track-size);\n overflow: hidden;\n border-radius: var(--dimension-radius-half);\n background-color: var(--color-background-faint-neutral);\n box-shadow: inset 0 0 0 var(--ds-slider-track-stroke) var(--color-border-tertiary);\n transform: translateY(-50%);\n pointer-events: none;\n}\n\n:host(.slider--thumb-center) .slider__rail {\n right: calc(var(--ds-slider-thumb-size) / 2);\n left: calc(var(--ds-slider-thumb-size) / 2);\n}\n\n.slider__indicator {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: calc(\n var(--ds-slider-endpoint-size) / 2 + (var(--ds-slider-end-pct, 0) / 100) *\n (100% - var(--ds-slider-endpoint-size))\n );\n border-radius: inherit;\n background-color: var(--color-background-bold-brand);\n}\n\n:host(.slider--range) .slider__indicator {\n left: calc(\n var(--ds-slider-endpoint-size) / 2 + (var(--ds-slider-start-pct, 0) / 100) *\n (100% - var(--ds-slider-endpoint-size))\n );\n width: calc(\n ((var(--ds-slider-end-pct, 0) - var(--ds-slider-start-pct, 0)) / 100) *\n (100% - var(--ds-slider-endpoint-size))\n );\n}\n\n:host(.slider--thumb-center) .slider__indicator {\n left: calc(var(--ds-slider-start-pct, 0) * 1%);\n width: calc((var(--ds-slider-end-pct, 0) - var(--ds-slider-start-pct, 0)) * 1%);\n}\n\n.slider__thumb {\n position: absolute;\n top: 50%;\n left: calc(\n (var(--ds-slider-thumb-pct, 0) / 100) * (100% - var(--ds-slider-endpoint-size)) -\n var(--ds-slider-thumb-overhang)\n );\n width: var(--ds-slider-thumb-size);\n height: var(--ds-slider-thumb-size);\n transform: translateY(-50%);\n z-index: 1;\n}\n\n.slider__thumb--active {\n z-index: 2;\n}\n\n.slider__input {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n margin: 0;\n padding: 0;\n border: 0;\n opacity: 0;\n appearance: none;\n pointer-events: none;\n z-index: 2;\n}\n\n.slider__thumb-visual {\n position: absolute;\n inset: 0;\n box-sizing: border-box;\n overflow: visible;\n border-radius: var(--dimension-radius-half);\n background-color: var(--color-background-transparent);\n -webkit-backdrop-filter: blur(var(--effect-blur-sm));\n backdrop-filter: blur(var(--effect-blur-sm));\n box-shadow: var(--effect-elevation-elevated-md);\n pointer-events: none;\n transition:\n background-color var(--effect-motion-short-2),\n box-shadow var(--effect-motion-short-2);\n}\n\n.slider__thumb-wash {\n position: absolute;\n inset: 0;\n border-radius: inherit;\n background-color: transparent;\n transition: background-color var(--effect-motion-short-2);\n}\n\n.slider__thumb-orb {\n position: absolute;\n top: 50%;\n left: 50%;\n box-sizing: border-box;\n width: var(--ds-slider-track-size);\n height: var(--ds-slider-track-size);\n border-radius: var(--dimension-radius-half);\n background-color: var(--color-background-bold-brand);\n box-shadow: inset 0 0 0 var(--ds-slider-track-stroke) var(--color-border-tertiary);\n transform: translate(-50%, -50%);\n}\n\n@media (hover: hover) and (pointer: fine) {\n :host(:not(.slider--readonly):not(.ds-control-inactive))\n .slider__thumb:hover\n .slider__thumb-wash {\n background-color: var(--color-interaction-hover);\n }\n}\n\n:host([data-dragging]:not(.slider--readonly):not(.ds-control-inactive))\n .slider__thumb--active\n .slider__thumb-wash {\n background-color: var(--color-interaction-pressed);\n}\n\n.slider__input:focus-visible + .slider__thumb-visual,\n:host([data-review-focus]) .slider__thumb[data-index='0'] .slider__thumb-visual {\n outline: var(--dimension-stroke-width-025) solid var(--color-interaction-focus);\n outline-offset: var(--dimension-space-025);\n}\n\n:host(.slider--vertical) .slider__rail {\n top: 0;\n right: auto;\n bottom: 0;\n left: 50%;\n width: var(--ds-slider-track-size);\n height: auto;\n transform: translateX(-50%);\n}\n\n:host(.slider--vertical.slider--thumb-center) .slider__rail {\n top: calc(var(--ds-slider-thumb-size) / 2);\n bottom: calc(var(--ds-slider-thumb-size) / 2);\n}\n\n:host(.slider--vertical) .slider__indicator {\n top: auto;\n right: 0;\n bottom: 0;\n left: 0;\n width: auto;\n height: calc(\n var(--ds-slider-endpoint-size) / 2 + (var(--ds-slider-end-pct, 0) / 100) *\n (100% - var(--ds-slider-endpoint-size))\n );\n}\n\n:host(.slider--vertical.slider--range) .slider__indicator {\n bottom: calc(\n var(--ds-slider-endpoint-size) / 2 + (var(--ds-slider-start-pct, 0) / 100) *\n (100% - var(--ds-slider-endpoint-size))\n );\n height: calc(\n ((var(--ds-slider-end-pct, 0) - var(--ds-slider-start-pct, 0)) / 100) *\n (100% - var(--ds-slider-endpoint-size))\n );\n}\n\n:host(.slider--vertical.slider--thumb-center) .slider__indicator {\n bottom: calc(var(--ds-slider-start-pct, 0) * 1%);\n height: calc((var(--ds-slider-end-pct, 0) - var(--ds-slider-start-pct, 0)) * 1%);\n}\n\n:host(.slider--vertical) .slider__thumb {\n top: auto;\n bottom: calc(\n (var(--ds-slider-thumb-pct, 0) / 100) * (100% - var(--ds-slider-endpoint-size)) -\n var(--ds-slider-thumb-overhang)\n );\n left: 50%;\n transform: translateX(-50%);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .slider__thumb-visual,\n .slider__thumb-wash {\n transition: none;\n }\n}\n\n@media (forced-colors: active) {\n .slider__rail {\n border: var(--ds-slider-track-stroke) solid var(--ds-forced-color-content);\n box-shadow: none;\n }\n\n .slider__indicator {\n background-color: var(--ds-forced-color-selected);\n }\n\n .slider__thumb-visual {\n border: var(--dimension-stroke-width-012) solid var(--ds-forced-color-control-boundary);\n background-color: var(--ds-forced-color-surface);\n -webkit-backdrop-filter: none;\n backdrop-filter: none;\n box-shadow: none;\n }\n\n .slider__thumb-orb {\n border: var(--ds-slider-track-stroke) solid var(--ds-forced-color-on-selected);\n background-color: var(--ds-forced-color-selected);\n box-shadow: none;\n }\n\n .slider__input:focus-visible + .slider__thumb-visual,\n :host([data-review-focus]) .slider__thumb[data-index='0'] .slider__thumb-visual {\n outline-color: var(--ds-forced-color-selected);\n }\n}\n\n.slider__tick {\n position: absolute;\n top: calc(\n 50% + var(--ds-slider-track-size) / 2 + var(--dimension-space-050) + var(--dimension-space-012)\n );\n left: calc(\n var(--ds-slider-endpoint-size) / 2 + (var(--ds-slider-tick-pct) / 100) *\n (100% - var(--ds-slider-endpoint-size))\n );\n transform: translateX(-50%);\n pointer-events: none;\n}\n.slider__tick-mark {\n display: block;\n width: var(--dimension-stroke-width-025);\n height: var(--dimension-stroke-width-025);\n border-radius: var(--dimension-radius-half);\n background: var(--color-foreground-tertiary);\n}\n.slider__tick-label {\n width: max-content;\n max-width: none;\n position: absolute;\n top: calc(100% + var(--dimension-space-050));\n left: 50%;\n transform: translateX(-50%);\n white-space: nowrap;\n}\n:host(:not(.slider--vertical)) .slider__control {\n container-type: inline-size;\n}\n:host(:not(.slider--vertical)) .slider__tick-label {\n /* Center on the tick, clamping only when the text reaches the control edges. */\n --ds-slider-label-anchor: calc(\n var(--ds-slider-endpoint-size) / 2 + (var(--ds-slider-tick-pct) / 100) *\n (100cqw - var(--ds-slider-endpoint-size))\n );\n transform: translateX(\n clamp(\n calc(-1 * var(--ds-slider-label-anchor)),\n -50%,\n calc(100cqw - var(--ds-slider-label-anchor) - 100%)\n )\n );\n}\n:host(.slider--tick-labels:not(.slider--vertical)) .slider__control {\n margin-bottom: var(--dimension-space-200);\n}\n:host(.slider--vertical) .slider__tick {\n top: auto;\n bottom: calc(\n var(--ds-slider-endpoint-size) / 2 + (var(--ds-slider-tick-pct) / 100) *\n (100% - var(--ds-slider-endpoint-size))\n );\n left: calc(50% + var(--ds-slider-track-size) / 2 + var(--dimension-space-050));\n transform: translateY(50%);\n}\n:host(.slider--vertical) .slider__tick-label {\n top: 50%;\n left: calc(100% + var(--dimension-space-050));\n transform: translateY(-50%);\n}\n",
|
|
716
717
|
"type": "registry:ui"
|
|
717
718
|
},
|
|
718
719
|
{
|
|
719
720
|
"path": "src/wc/components/Slider/Slider.tsx",
|
|
720
|
-
"content": "import { numberFormatter } from '../../utils/intl-formatters';\nimport {\n AttachInternals,\n Component,\n Element,\n Event,\n EventEmitter,\n h,\n Host,\n Method,\n Prop,\n State,\n Watch,\n} from '@stencil/core';\nimport {\n restoreNumberArrayFormState,\n restoreStringFormState,\n setFormControlValue,\n setRepeatedFormControlValue,\n} from '../../utils';\n\nexport interface SliderTick {\n value: number;\n label?: string;\n}\n\nexport type SliderValue = number | number[];\nexport type SliderSize = 'md' | 'sm' | 'xs';\nexport type SliderOrientation = 'horizontal' | 'vertical';\nexport type SliderThumbAlignment = 'edge' | 'center';\n\nlet sliderId = 0;\nlet sliderExternalLabelId = 0;\n\n@Component({\n tag: 'ds-slider',\n styleUrl: 'Slider.css',\n scoped: true,\n formAssociated: true,\n})\nexport class Slider {\n @Element() el!: HTMLElement;\n @AttachInternals() internals!: ElementInternals;\n\n /** Current value. Assign a two-number array through the JavaScript property for a range slider. */\n @Prop({ mutable: true }) value: SliderValue = 0;\n @Prop() min: number = 0;\n @Prop() max: number = 100;\n @Prop() step: number | 'any' = 1;\n /** Explicit ticks are the selectable snap points. Empty ticks with step='any' allow smooth adjustment. */\n @Prop() ticks: SliderTick[] = [];\n /** Minimum number of steps kept between two range thumbs. */\n @Prop() minStepsBetweenValues: number = 0;\n /** Visible field label. Supply aria-label when the label is intentionally hidden. */\n @Prop() label: string | undefined;\n /** Show the formatted current value beside the visible label. */\n @Prop() showValue: boolean = true;\n @Prop() size: SliderSize = 'md';\n @Prop() orientation: SliderOrientation = 'horizontal';\n /** Align the thumb edge with full-width rail endpoints, or its center with inset rail endpoints. */\n @Prop() thumbAlignment: SliderThumbAlignment = 'edge';\n @Prop({ reflect: true }) name: string | undefined;\n @Prop({ reflect: true }) form: string | undefined;\n @Prop({ reflect: true }) disabled: boolean = false;\n /** Keep the value focusable and submittable while preventing changes. */\n @Prop({ reflect: true }) readOnly: boolean = false;\n @Prop() isInactive: boolean = false;\n /** Direct accessible name for a single-thumb slider without a visible label. */\n @Prop({ attribute: 'aria-label' }) ariaLabel: string | null = null;\n /** Id references for a visible single-thumb slider label. */\n @Prop({ attribute: 'aria-labelledby' }) ariaLabelledby: string | undefined;\n /** Id references for supporting guidance or error text. */\n @Prop({ attribute: 'aria-describedby' }) ariaDescribedby: string | undefined;\n /** Accessible label for the lower thumb in a range slider. */\n @Prop() startLabel: string = 'Minimum value';\n /** Accessible label for the upper thumb in a range slider. */\n @Prop() endLabel: string = 'Maximum value';\n /** Human-readable value text for a single slider or both range thumbs. */\n @Prop() valueText: string | undefined;\n /** Per-thumb human-readable value text for a range slider. Assign through the JavaScript property. */\n @Prop() valueTexts: string[] = [];\n /** Locale used for the visible value. Defaults to English (en-US). */\n @Prop() locale: string | undefined;\n /** Intl.NumberFormat options. Assign objects through the JavaScript property. */\n @Prop() formatOptions: Intl.NumberFormatOptions | undefined;\n @Prop() valuePrefix: string = '';\n @Prop() valueSuffix: string = '';\n @Prop() rangeSeparator: string = '–';\n /** Id applied to the first native range input. */\n @Prop() inputId: string | undefined;\n\n /** Emitted continuously while the value changes. */\n @Event() dsChange!: EventEmitter<SliderValue>;\n /** Emitted when a pointer or keyboard value change is committed. */\n @Event() dsCommit!: EventEmitter<SliderValue>;\n\n @State() private formDisabled = false;\n @State() private dragging = false;\n @State() private focused = false;\n @State() private touched = false;\n @State() private activeThumbIndex = 0;\n @State() private externalLabelledby: string | undefined;\n\n private readonly generatedId = `ds-slider-${++sliderId}`;\n private readonly labelId = `${this.generatedId}-label`;\n private initialValue: SliderValue = 0;\n private lastCommittedValue: SliderValue = 0;\n private pointerId: number | null = null;\n private pointerStartValue: SliderValue = 0;\n private controlEl: HTMLDivElement | null = null;\n\n componentWillLoad() {\n this.initialValue = this.cloneValue(this.publicValue);\n this.lastCommittedValue = this.cloneValue(this.publicValue);\n this.syncFormValue();\n }\n\n componentDidLoad() {\n this.syncNativeLabels();\n }\n\n @Watch('value')\n @Watch('min')\n @Watch('max')\n @Watch('step')\n @Watch('ticks')\n @Watch('minStepsBetweenValues')\n @Watch('name')\n @Watch('disabled')\n @Watch('isInactive')\n syncFormValue() {\n const values = this.resolvedValues;\n if (this.isRange) {\n setRepeatedFormControlValue(this.internals, this.name, values, {\n inactive: this.isDisabled,\n });\n } else {\n const value = String(values[0]);\n setFormControlValue(this.internals, this.name ? value : null, {\n inactive: this.isDisabled,\n state: value,\n });\n }\n }\n\n formDisabledCallback(disabled: boolean) {\n this.formDisabled = disabled;\n this.syncFormValue();\n }\n\n formResetCallback() {\n this.value = this.cloneValue(this.initialValue);\n this.lastCommittedValue = this.cloneValue(this.initialValue);\n this.dragging = false;\n this.focused = false;\n this.touched = false;\n }\n\n formStateRestoreCallback(state: string | File | FormData | null) {\n if (this.isRange) {\n const restored = restoreNumberArrayFormState(state, 2);\n this.value = restored.length ? restored : this.cloneValue(this.initialValue);\n } else {\n const restored = Number(restoreStringFormState(state, 'NaN'));\n if (Number.isFinite(restored)) this.value = restored;\n }\n this.lastCommittedValue = this.cloneValue(this.publicValue);\n }\n\n @Method()\n async setFocus(index: number = 0) {\n const inputs = this.el.querySelectorAll<HTMLInputElement>('.slider__input');\n inputs[Math.max(0, Math.min(index, inputs.length - 1))]?.focus();\n }\n\n private get isDisabled(): boolean {\n return this.disabled || this.isInactive || this.formDisabled;\n }\n\n private get isRange(): boolean {\n return Array.isArray(this.value);\n }\n\n private get safeStep(): number {\n const step = Number(this.step);\n return Number.isFinite(step) && step > 0 ? step : 1;\n }\n\n private get resolvedTicks(): SliderTick[] {\n const unique = new Map<number, SliderTick>();\n for (const tick of this.ticks) {\n if (Number.isFinite(tick.value) && tick.value >= this.safeMin && tick.value <= this.safeMax)\n unique.set(tick.value, tick);\n }\n return [...unique.values()].sort((a, b) => a.value - b.value);\n }\n\n private get safeMin(): number {\n return Number.isFinite(this.min) ? this.min : 0;\n }\n\n private get safeMax(): number {\n const minimum = this.safeMin;\n return Number.isFinite(this.max) && this.max > minimum ? this.max : minimum + this.safeStep;\n }\n\n private get minimumGap(): number {\n const steps = Number.isFinite(this.minStepsBetweenValues)\n ? Math.max(0, Math.floor(this.minStepsBetweenValues))\n : 0;\n const ticks = this.resolvedTicks;\n const span = ticks.length\n ? ticks[ticks.length - 1].value - ticks[0].value\n : this.safeMax - this.safeMin;\n return Math.min(span, steps * this.safeStep);\n }\n\n private get resolvedValues(): number[] {\n const minimum = this.safeMin;\n const maximum = this.safeMax;\n if (!this.isRange) return [this.snap(Number(this.value))];\n\n const raw = this.value as number[];\n let lower = this.snap(Number(raw[0] ?? minimum));\n let upper = this.snap(Number(raw[1] ?? maximum));\n if (lower > upper) [lower, upper] = [upper, lower];\n\n const gap = Math.min(this.minimumGap, maximum - minimum);\n if (upper - lower < gap) {\n upper = Math.min(maximum, this.snap(lower + gap));\n lower = Math.max(minimum, this.snap(upper - gap));\n const ticks = this.resolvedTicks;\n if (ticks.length && upper - lower < gap) {\n upper =\n ticks.find(tick => tick.value >= lower + gap)?.value ?? ticks[ticks.length - 1].value;\n lower =\n [...ticks].reverse().find(tick => tick.value <= upper - gap)?.value ?? ticks[0].value;\n }\n }\n return [lower, upper];\n }\n\n private get publicValue(): SliderValue {\n const values = this.resolvedValues;\n return this.isRange ? [...values] : values[0];\n }\n\n private cloneValue(value: SliderValue): SliderValue {\n return Array.isArray(value) ? [...value] : value;\n }\n\n private valuesEqual(a: SliderValue, b: SliderValue): boolean {\n if (Array.isArray(a) && Array.isArray(b)) {\n return a.length === b.length && a.every((value, index) => value === b[index]);\n }\n return !Array.isArray(a) && !Array.isArray(b) && a === b;\n }\n\n private decimalPlaces(value: number): number {\n const text = String(value).toLowerCase();\n if (text.includes('e-')) return Number(text.split('e-')[1]) || 0;\n return text.includes('.') ? text.split('.')[1].length : 0;\n }\n\n private snap(value: number, minimum = this.safeMin, maximum = this.safeMax): number {\n const ticks = this.resolvedTicks.filter(tick => tick.value >= minimum && tick.value <= maximum);\n if (ticks.length)\n return ticks.reduce((nearest, tick) =>\n Math.abs(tick.value - value) < Math.abs(nearest.value - value) ? tick : nearest\n ).value;\n if (this.step === 'any')\n return Math.min(maximum, Math.max(minimum, Number.isFinite(value) ? value : minimum));\n const candidate = Number.isFinite(value) ? value : minimum;\n const stepped = minimum + Math.round((candidate - minimum) / this.safeStep) * this.safeStep;\n const precision = Math.min(\n 10,\n Math.max(\n this.decimalPlaces(minimum),\n this.decimalPlaces(maximum),\n this.decimalPlaces(this.safeStep)\n )\n );\n return Number(Math.min(maximum, Math.max(minimum, stepped)).toFixed(precision));\n }\n\n private percent(value: number): number {\n return ((value - this.safeMin) / (this.safeMax - this.safeMin)) * 100;\n }\n\n private allowedMin(index: number, values = this.resolvedValues): number {\n return this.isRange && index === 1\n ? Math.min(this.safeMax, values[0] + this.minimumGap)\n : this.safeMin;\n }\n\n private allowedMax(index: number, values = this.resolvedValues): number {\n return this.isRange && index === 0\n ? Math.max(this.safeMin, values[1] - this.minimumGap)\n : this.safeMax;\n }\n\n private setValueAt(index: number, next: number): boolean {\n const previous = this.publicValue;\n const values = this.resolvedValues;\n const constrained = Math.min(\n this.allowedMax(index, values),\n Math.max(\n this.allowedMin(index, values),\n this.snap(next, this.allowedMin(index, values), this.allowedMax(index, values))\n )\n );\n values[index] = constrained;\n const nextValue: SliderValue = this.isRange ? [...values] : values[0];\n if (this.valuesEqual(previous, nextValue)) return false;\n\n this.value = nextValue;\n this.dsChange.emit(this.cloneValue(nextValue));\n return true;\n }\n\n private commitIfChanged() {\n const current = this.publicValue;\n if (this.valuesEqual(current, this.lastCommittedValue)) return;\n this.lastCommittedValue = this.cloneValue(current);\n this.dsCommit.emit(this.cloneValue(current));\n }\n\n private syncNativeLabels() {\n if (this.label || this.ariaLabel || this.ariaLabelledby || this.isRange) return;\n const ids = (Array.from(this.internals.labels) as HTMLLabelElement[]).map(label => {\n if (!label.id) label.id = `ds-slider-label-${++sliderExternalLabelId}`;\n return label.id;\n });\n if (ids.length) this.externalLabelledby = ids.join(' ');\n }\n\n private formattedValue(value: number, index: number): string {\n const authored =\n this.valueTexts[index] ??\n this.valueText ??\n this.resolvedTicks.find(tick => tick.value === value)?.label;\n if (authored) return authored;\n let number: string;\n try {\n number = numberFormatter(this.locale, this.formatOptions).format(value);\n } catch {\n number = String(value);\n }\n return `${this.valuePrefix}${number}${this.valueSuffix}`;\n }\n\n private ariaValueText(value: number, index: number): string | undefined {\n if (\n this.resolvedTicks.some(tick => tick.value === value && tick.label) ||\n this.valueTexts[index] ||\n this.valueText ||\n this.valuePrefix ||\n this.valueSuffix ||\n this.formatOptions\n ) {\n return this.formattedValue(value, index);\n }\n return undefined;\n }\n\n private handleNativeInput = (event: Event, index: number) => {\n const input = event.target as HTMLInputElement;\n this.activeThumbIndex = index;\n if (this.readOnly || this.isDisabled) {\n input.value = String(this.resolvedValues[index]);\n return;\n }\n this.setValueAt(index, Number(input.value));\n };\n\n private handleNativeChange = () => {\n if (!this.readOnly && !this.isDisabled) this.commitIfChanged();\n };\n\n private handleKeyDown = (event: KeyboardEvent, index: number) => {\n const keys = [\n 'ArrowLeft',\n 'ArrowRight',\n 'ArrowUp',\n 'ArrowDown',\n 'Home',\n 'End',\n 'PageUp',\n 'PageDown',\n ];\n if (!this.readOnly && this.resolvedTicks.length && keys.includes(event.key)) {\n event.preventDefault();\n if (this.isDisabled) return;\n const ticks = this.resolvedTicks.filter(\n tick => tick.value >= this.allowedMin(index) && tick.value <= this.allowedMax(index)\n );\n const current = this.resolvedValues[index];\n const forward = ['ArrowRight', 'ArrowUp', 'PageUp'].includes(event.key);\n const next =\n event.key === 'Home'\n ? ticks[0]\n : event.key === 'End'\n ? ticks[ticks.length - 1]\n : forward\n ? ticks.find(tick => tick.value > current)\n : [...ticks].reverse().find(tick => tick.value < current);\n if (next && this.setValueAt(index, next.value)) this.commitIfChanged();\n return;\n }\n if (!this.readOnly) return;\n if (\n [\n 'ArrowLeft',\n 'ArrowRight',\n 'ArrowUp',\n 'ArrowDown',\n 'Home',\n 'End',\n 'PageUp',\n 'PageDown',\n ].includes(event.key)\n ) {\n event.preventDefault();\n }\n };\n\n private handleFocus = (index: number) => {\n this.activeThumbIndex = index;\n this.focused = true;\n };\n\n private handleBlur = (event: FocusEvent) => {\n const next = event.relatedTarget;\n if (next instanceof Node && this.el.contains(next)) return;\n this.focused = false;\n this.touched = true;\n };\n\n private handleHostClick = (event: MouseEvent) => {\n if (event.target !== this.el || this.isDisabled) return;\n const inputs = this.el.querySelectorAll<HTMLInputElement>('.slider__input');\n inputs[Math.min(this.activeThumbIndex, inputs.length - 1)]?.focus();\n };\n\n private pointerPercent(event: PointerEvent): number {\n const control = this.controlEl;\n if (!control) return 0;\n const rect = control.getBoundingClientRect();\n const thumb = this.el.querySelector<HTMLElement>('.slider__thumb');\n const thumbSize = thumb\n ? this.orientation === 'horizontal'\n ? thumb.offsetWidth\n : thumb.offsetHeight\n : 0;\n // Both alignment modes keep the complete thumb inside the control. Edge\n // alignment lets the rail span the control while center alignment insets\n // the rail to the same thumb-center travel.\n const inset = thumbSize / 2;\n\n if (this.orientation === 'vertical') {\n const travel = Math.max(1, rect.height - inset * 2);\n return Math.min(1, Math.max(0, (rect.bottom - inset - event.clientY) / travel));\n }\n\n const travel = Math.max(1, rect.width - inset * 2);\n const percent = (event.clientX - rect.left - inset) / travel;\n return Math.min(1, Math.max(0, percent));\n }\n\n private valueFromPointer(event: PointerEvent): number {\n return this.snap(this.safeMin + this.pointerPercent(event) * (this.safeMax - this.safeMin));\n }\n\n private closestThumbIndex(value: number, target: EventTarget | null): number {\n const targetElement =\n target instanceof HTMLElement ? target.closest<HTMLElement>('.slider__thumb') : null;\n if (targetElement?.dataset['index']) return Number(targetElement.dataset['index']);\n if (!this.isRange) return 0;\n\n const values = this.resolvedValues;\n const lowerDistance = Math.abs(value - values[0]);\n const upperDistance = Math.abs(value - values[1]);\n if (lowerDistance === upperDistance) return value >= values[0] ? 1 : 0;\n return lowerDistance < upperDistance ? 0 : 1;\n }\n\n private handlePointerDown = (event: PointerEvent) => {\n if (event.button !== 0 || this.isDisabled || this.readOnly) return;\n event.preventDefault();\n const value = this.valueFromPointer(event);\n const index = this.closestThumbIndex(value, event.target);\n this.activeThumbIndex = index;\n this.pointerId = event.pointerId;\n this.pointerStartValue = this.cloneValue(this.publicValue);\n this.dragging = true;\n this.setValueAt(index, value);\n this.controlEl?.setPointerCapture(event.pointerId);\n requestAnimationFrame(() => {\n const input = this.el.querySelectorAll<HTMLInputElement>('.slider__input')[index];\n input?.focus({ preventScroll: true });\n });\n };\n\n private handlePointerMove = (event: PointerEvent) => {\n if (this.pointerId !== event.pointerId || !this.dragging) return;\n event.preventDefault();\n this.setValueAt(this.activeThumbIndex, this.valueFromPointer(event));\n };\n\n private endPointerInteraction(event: PointerEvent, canceled: boolean) {\n if (this.pointerId !== event.pointerId) return;\n if (this.controlEl?.hasPointerCapture(event.pointerId))\n this.controlEl.releasePointerCapture(event.pointerId);\n this.pointerId = null;\n this.dragging = false;\n this.touched = true;\n if (!canceled && !this.valuesEqual(this.pointerStartValue, this.publicValue))\n this.commitIfChanged();\n }\n\n private renderThumb(value: number, index: number) {\n const inputId =\n index === 0\n ? (this.inputId ?? `${this.generatedId}-input`)\n : `${this.inputId ?? `${this.generatedId}-input`}-end`;\n const rangeLabel = index === 0 ? this.startLabel : this.endLabel;\n const labelledby = this.isRange\n ? undefined\n : (this.ariaLabelledby ?? (this.label ? this.labelId : this.externalLabelledby));\n const directLabel = this.isRange ? rangeLabel : labelledby ? undefined : this.ariaLabel;\n\n return (\n <div\n class={{\n slider__thumb: true,\n 'slider__thumb--active': index === this.activeThumbIndex,\n }}\n data-index={String(index)}\n style={{ '--ds-slider-thumb-pct': String(this.percent(value)) } as Record<string, string>}\n >\n <input\n class=\"slider__input\"\n type=\"range\"\n id={inputId}\n min={this.allowedMin(index)}\n max={this.allowedMax(index)}\n step={this.resolvedTicks.length || this.step === 'any' ? 'any' : this.safeStep}\n value={value}\n disabled={this.isDisabled}\n aria-label={directLabel}\n aria-labelledby={labelledby}\n aria-describedby={this.ariaDescribedby}\n aria-valuetext={this.ariaValueText(value, index)}\n aria-orientation={this.orientation === 'vertical' ? 'vertical' : undefined}\n aria-readonly={this.readOnly ? 'true' : undefined}\n onInput={(event: Event) => this.handleNativeInput(event, index)}\n onChange={this.handleNativeChange}\n onKeyDown={event => this.handleKeyDown(event, index)}\n onFocus={() => this.handleFocus(index)}\n onBlur={this.handleBlur}\n />\n <span class=\"slider__thumb-visual\" aria-hidden=\"true\">\n <span class=\"slider__thumb-wash\" />\n </span>\n </div>\n );\n }\n\n render() {\n const values = this.resolvedValues;\n const first = values[0];\n const last = values[values.length - 1];\n const firstInputId = this.inputId ?? `${this.generatedId}-input`;\n const dirty = !this.valuesEqual(this.publicValue, this.initialValue);\n const valueDisplay = values\n .map((value, index) => this.formattedValue(value, index))\n .join(` ${this.rangeSeparator} `);\n\n return (\n <Host\n aria-disabled={this.isDisabled ? 'true' : undefined}\n class={{\n slider: true,\n 'ds-field-stack': true,\n [`slider--${this.size}`]: true,\n [`slider--${this.orientation}`]: true,\n [`slider--thumb-${this.thumbAlignment}`]: true,\n 'slider--range': this.isRange,\n 'slider--ticks': this.resolvedTicks.length > 0,\n 'slider--tick-labels': this.resolvedTicks.some(tick => Boolean(tick.label)),\n 'slider--readonly': this.readOnly,\n 'ds-control-inactive': this.isDisabled,\n }}\n data-orientation={this.orientation}\n data-disabled={this.isDisabled ? '' : undefined}\n data-readonly={this.readOnly ? '' : undefined}\n data-dragging={this.dragging ? '' : undefined}\n data-focused={this.focused ? '' : undefined}\n data-filled=\"\"\n data-dirty={dirty ? '' : undefined}\n data-touched={this.touched ? '' : undefined}\n data-valid={!this.isDisabled ? '' : undefined}\n onClick={this.handleHostClick}\n >\n {(this.label || this.showValue) && (\n <div class=\"slider__label-row\">\n {this.label && (\n <ds-text\n class=\"slider__label\"\n as=\"label\"\n variant=\"text-body-small\"\n color=\"primary\"\n emphasis\n for={firstInputId}\n textId={this.labelId}\n >\n {this.label}\n </ds-text>\n )}\n {this.showValue && (\n <ds-text\n class=\"slider__value\"\n as=\"span\"\n variant=\"text-body-small\"\n color=\"primary\"\n fontFeature=\"tabular-nums\"\n >\n {valueDisplay}\n </ds-text>\n )}\n </div>\n )}\n <div\n class=\"slider__control\"\n ref={element => {\n this.controlEl = element ?? null;\n }}\n style={\n {\n '--ds-slider-start-pct': String(this.isRange ? this.percent(first) : 0),\n '--ds-slider-end-pct': String(this.percent(last)),\n } as Record<string, string>\n }\n onPointerDown={this.handlePointerDown}\n onPointerMove={this.handlePointerMove}\n onPointerUp={(event: PointerEvent) => this.endPointerInteraction(event, false)}\n onPointerCancel={(event: PointerEvent) => this.endPointerInteraction(event, true)}\n >\n <div class=\"slider__rail\" aria-hidden=\"true\">\n <div class=\"slider__indicator\" />\n </div>\n {this.resolvedTicks.map(tick => (\n <span\n class=\"slider__tick\"\n data-edge={\n tick.value === this.safeMin\n ? 'start'\n : tick.value === this.safeMax\n ? 'end'\n : undefined\n }\n aria-hidden=\"true\"\n style={{ '--ds-slider-tick-pct': String(this.percent(tick.value)) }}\n >\n <span class=\"slider__tick-mark\" />\n {tick.label && (\n <ds-text\n class=\"slider__tick-label\"\n as=\"span\"\n variant=\"text-caption\"\n color=\"secondary\"\n >\n {tick.label}\n </ds-text>\n )}\n </span>\n ))}\n {values.map((value, index) => this.renderThumb(value, index))}\n </div>\n </Host>\n );\n }\n}\n",
|
|
721
|
+
"content": "import { numberFormatter } from '../../utils/intl-formatters';\nimport {\n AttachInternals,\n Component,\n Element,\n Event,\n EventEmitter,\n h,\n Host,\n Method,\n Prop,\n State,\n Watch,\n} from '@stencil/core';\nimport {\n restoreNumberArrayFormState,\n restoreStringFormState,\n setFormControlValue,\n setRepeatedFormControlValue,\n} from '../../utils';\n\nexport interface SliderTick {\n value: number;\n label?: string;\n}\n\nexport type SliderValue = number | number[];\nexport type SliderSize = 'md' | 'sm' | 'xs';\nexport type SliderOrientation = 'horizontal' | 'vertical';\nexport type SliderThumbAlignment = 'edge' | 'center';\n\nlet sliderId = 0;\nlet sliderExternalLabelId = 0;\n\n@Component({\n tag: 'ds-slider',\n styleUrl: 'Slider.css',\n scoped: true,\n formAssociated: true,\n})\nexport class Slider {\n @Element() el!: HTMLElement;\n @AttachInternals() internals!: ElementInternals;\n\n /** Current value. Assign a two-number array through the JavaScript property for a range slider. */\n @Prop({ mutable: true }) value: SliderValue = 0;\n @Prop() min: number = 0;\n @Prop() max: number = 100;\n @Prop() step: number | 'any' = 1;\n /** Explicit ticks are the selectable snap points. Empty ticks with step='any' allow smooth adjustment. */\n @Prop() ticks: SliderTick[] = [];\n /** Minimum number of steps kept between two range thumbs. */\n @Prop() minStepsBetweenValues: number = 0;\n /** Visible field label. Supply aria-label when the label is intentionally hidden. */\n @Prop() label: string | undefined;\n /** Show the formatted current value beside the visible label. */\n @Prop() showValue: boolean = true;\n @Prop() size: SliderSize = 'md';\n @Prop() orientation: SliderOrientation = 'horizontal';\n /** Align the inner orb edge with full-width rail endpoints, or its center with inset rail endpoints. */\n @Prop() thumbAlignment: SliderThumbAlignment = 'edge';\n @Prop({ reflect: true }) name: string | undefined;\n @Prop({ reflect: true }) form: string | undefined;\n @Prop({ reflect: true }) disabled: boolean = false;\n /** Keep the value focusable and submittable while preventing changes. */\n @Prop({ reflect: true }) readOnly: boolean = false;\n @Prop() isInactive: boolean = false;\n /** Direct accessible name for a single-thumb slider without a visible label. */\n @Prop({ attribute: 'aria-label' }) ariaLabel: string | null = null;\n /** Id references for a visible single-thumb slider label. */\n @Prop({ attribute: 'aria-labelledby' }) ariaLabelledby: string | undefined;\n /** Id references for supporting guidance or error text. */\n @Prop({ attribute: 'aria-describedby' }) ariaDescribedby: string | undefined;\n /** Accessible label for the lower thumb in a range slider. */\n @Prop() startLabel: string = 'Minimum value';\n /** Accessible label for the upper thumb in a range slider. */\n @Prop() endLabel: string = 'Maximum value';\n /** Human-readable value text for a single slider or both range thumbs. */\n @Prop() valueText: string | undefined;\n /** Per-thumb human-readable value text for a range slider. Assign through the JavaScript property. */\n @Prop() valueTexts: string[] = [];\n /** Locale used for the visible value. Defaults to English (en-US). */\n @Prop() locale: string | undefined;\n /** Intl.NumberFormat options. Assign objects through the JavaScript property. */\n @Prop() formatOptions: Intl.NumberFormatOptions | undefined;\n @Prop() valuePrefix: string = '';\n @Prop() valueSuffix: string = '';\n @Prop() rangeSeparator: string = '–';\n /** Id applied to the first native range input. */\n @Prop() inputId: string | undefined;\n\n /** Emitted continuously while the value changes. */\n @Event() dsChange!: EventEmitter<SliderValue>;\n /** Emitted when a pointer or keyboard value change is committed. */\n @Event() dsCommit!: EventEmitter<SliderValue>;\n\n @State() private formDisabled = false;\n @State() private dragging = false;\n @State() private focused = false;\n @State() private touched = false;\n @State() private activeThumbIndex = 0;\n @State() private externalLabelledby: string | undefined;\n\n private readonly generatedId = `ds-slider-${++sliderId}`;\n private readonly labelId = `${this.generatedId}-label`;\n private initialValue: SliderValue = 0;\n private lastCommittedValue: SliderValue = 0;\n private pointerId: number | null = null;\n private pointerStartValue: SliderValue = 0;\n private controlEl: HTMLDivElement | null = null;\n\n componentWillLoad() {\n this.initialValue = this.cloneValue(this.publicValue);\n this.lastCommittedValue = this.cloneValue(this.publicValue);\n this.syncFormValue();\n }\n\n componentDidLoad() {\n this.syncNativeLabels();\n }\n\n @Watch('value')\n @Watch('min')\n @Watch('max')\n @Watch('step')\n @Watch('ticks')\n @Watch('minStepsBetweenValues')\n @Watch('name')\n @Watch('disabled')\n @Watch('isInactive')\n syncFormValue() {\n const values = this.resolvedValues;\n if (this.isRange) {\n setRepeatedFormControlValue(this.internals, this.name, values, {\n inactive: this.isDisabled,\n });\n } else {\n const value = String(values[0]);\n setFormControlValue(this.internals, this.name ? value : null, {\n inactive: this.isDisabled,\n state: value,\n });\n }\n }\n\n formDisabledCallback(disabled: boolean) {\n this.formDisabled = disabled;\n this.syncFormValue();\n }\n\n formResetCallback() {\n this.value = this.cloneValue(this.initialValue);\n this.lastCommittedValue = this.cloneValue(this.initialValue);\n this.dragging = false;\n this.focused = false;\n this.touched = false;\n }\n\n formStateRestoreCallback(state: string | File | FormData | null) {\n if (this.isRange) {\n const restored = restoreNumberArrayFormState(state, 2);\n this.value = restored.length ? restored : this.cloneValue(this.initialValue);\n } else {\n const restored = Number(restoreStringFormState(state, 'NaN'));\n if (Number.isFinite(restored)) this.value = restored;\n }\n this.lastCommittedValue = this.cloneValue(this.publicValue);\n }\n\n @Method()\n async setFocus(index: number = 0) {\n const inputs = this.el.querySelectorAll<HTMLInputElement>('.slider__input');\n inputs[Math.max(0, Math.min(index, inputs.length - 1))]?.focus();\n }\n\n private get isDisabled(): boolean {\n return this.disabled || this.isInactive || this.formDisabled;\n }\n\n private get isRange(): boolean {\n return Array.isArray(this.value);\n }\n\n private get safeStep(): number {\n const step = Number(this.step);\n return Number.isFinite(step) && step > 0 ? step : 1;\n }\n\n private get resolvedTicks(): SliderTick[] {\n const unique = new Map<number, SliderTick>();\n for (const tick of this.ticks) {\n if (Number.isFinite(tick.value) && tick.value >= this.safeMin && tick.value <= this.safeMax)\n unique.set(tick.value, tick);\n }\n return [...unique.values()].sort((a, b) => a.value - b.value);\n }\n\n private get safeMin(): number {\n return Number.isFinite(this.min) ? this.min : 0;\n }\n\n private get safeMax(): number {\n const minimum = this.safeMin;\n return Number.isFinite(this.max) && this.max > minimum ? this.max : minimum + this.safeStep;\n }\n\n private get minimumGap(): number {\n const steps = Number.isFinite(this.minStepsBetweenValues)\n ? Math.max(0, Math.floor(this.minStepsBetweenValues))\n : 0;\n const ticks = this.resolvedTicks;\n const span = ticks.length\n ? ticks[ticks.length - 1].value - ticks[0].value\n : this.safeMax - this.safeMin;\n return Math.min(span, steps * this.safeStep);\n }\n\n private get resolvedValues(): number[] {\n const minimum = this.safeMin;\n const maximum = this.safeMax;\n if (!this.isRange) return [this.snap(Number(this.value))];\n\n const raw = this.value as number[];\n let lower = this.snap(Number(raw[0] ?? minimum));\n let upper = this.snap(Number(raw[1] ?? maximum));\n if (lower > upper) [lower, upper] = [upper, lower];\n\n const gap = Math.min(this.minimumGap, maximum - minimum);\n if (upper - lower < gap) {\n upper = Math.min(maximum, this.snap(lower + gap));\n lower = Math.max(minimum, this.snap(upper - gap));\n const ticks = this.resolvedTicks;\n if (ticks.length && upper - lower < gap) {\n upper =\n ticks.find(tick => tick.value >= lower + gap)?.value ?? ticks[ticks.length - 1].value;\n lower =\n [...ticks].reverse().find(tick => tick.value <= upper - gap)?.value ?? ticks[0].value;\n }\n }\n return [lower, upper];\n }\n\n private get publicValue(): SliderValue {\n const values = this.resolvedValues;\n return this.isRange ? [...values] : values[0];\n }\n\n private cloneValue(value: SliderValue): SliderValue {\n return Array.isArray(value) ? [...value] : value;\n }\n\n private valuesEqual(a: SliderValue, b: SliderValue): boolean {\n if (Array.isArray(a) && Array.isArray(b)) {\n return a.length === b.length && a.every((value, index) => value === b[index]);\n }\n return !Array.isArray(a) && !Array.isArray(b) && a === b;\n }\n\n private decimalPlaces(value: number): number {\n const text = String(value).toLowerCase();\n if (text.includes('e-')) return Number(text.split('e-')[1]) || 0;\n return text.includes('.') ? text.split('.')[1].length : 0;\n }\n\n private snap(value: number, minimum = this.safeMin, maximum = this.safeMax): number {\n const ticks = this.resolvedTicks.filter(tick => tick.value >= minimum && tick.value <= maximum);\n if (ticks.length)\n return ticks.reduce((nearest, tick) =>\n Math.abs(tick.value - value) < Math.abs(nearest.value - value) ? tick : nearest\n ).value;\n if (this.step === 'any')\n return Math.min(maximum, Math.max(minimum, Number.isFinite(value) ? value : minimum));\n const candidate = Number.isFinite(value) ? value : minimum;\n const stepped = minimum + Math.round((candidate - minimum) / this.safeStep) * this.safeStep;\n const precision = Math.min(\n 10,\n Math.max(\n this.decimalPlaces(minimum),\n this.decimalPlaces(maximum),\n this.decimalPlaces(this.safeStep)\n )\n );\n return Number(Math.min(maximum, Math.max(minimum, stepped)).toFixed(precision));\n }\n\n private percent(value: number): number {\n return ((value - this.safeMin) / (this.safeMax - this.safeMin)) * 100;\n }\n\n private allowedMin(index: number, values = this.resolvedValues): number {\n return this.isRange && index === 1\n ? Math.min(this.safeMax, values[0] + this.minimumGap)\n : this.safeMin;\n }\n\n private allowedMax(index: number, values = this.resolvedValues): number {\n return this.isRange && index === 0\n ? Math.max(this.safeMin, values[1] - this.minimumGap)\n : this.safeMax;\n }\n\n private setValueAt(index: number, next: number): boolean {\n const previous = this.publicValue;\n const values = this.resolvedValues;\n const constrained = Math.min(\n this.allowedMax(index, values),\n Math.max(\n this.allowedMin(index, values),\n this.snap(next, this.allowedMin(index, values), this.allowedMax(index, values))\n )\n );\n values[index] = constrained;\n const nextValue: SliderValue = this.isRange ? [...values] : values[0];\n if (this.valuesEqual(previous, nextValue)) return false;\n\n this.value = nextValue;\n this.dsChange.emit(this.cloneValue(nextValue));\n return true;\n }\n\n private commitIfChanged() {\n const current = this.publicValue;\n if (this.valuesEqual(current, this.lastCommittedValue)) return;\n this.lastCommittedValue = this.cloneValue(current);\n this.dsCommit.emit(this.cloneValue(current));\n }\n\n private syncNativeLabels() {\n if (this.label || this.ariaLabel || this.ariaLabelledby || this.isRange) return;\n const ids = (Array.from(this.internals.labels) as HTMLLabelElement[]).map(label => {\n if (!label.id) label.id = `ds-slider-label-${++sliderExternalLabelId}`;\n return label.id;\n });\n if (ids.length) this.externalLabelledby = ids.join(' ');\n }\n\n private formattedValue(value: number, index: number): string {\n const authored =\n this.valueTexts[index] ??\n this.valueText ??\n this.resolvedTicks.find(tick => tick.value === value)?.label;\n if (authored) return authored;\n let number: string;\n try {\n number = numberFormatter(this.locale, this.formatOptions).format(value);\n } catch {\n number = String(value);\n }\n return `${this.valuePrefix}${number}${this.valueSuffix}`;\n }\n\n private ariaValueText(value: number, index: number): string | undefined {\n if (\n this.resolvedTicks.some(tick => tick.value === value && tick.label) ||\n this.valueTexts[index] ||\n this.valueText ||\n this.valuePrefix ||\n this.valueSuffix ||\n this.formatOptions\n ) {\n return this.formattedValue(value, index);\n }\n return undefined;\n }\n\n private handleNativeInput = (event: Event, index: number) => {\n const input = event.target as HTMLInputElement;\n this.activeThumbIndex = index;\n if (this.readOnly || this.isDisabled) {\n input.value = String(this.resolvedValues[index]);\n return;\n }\n this.setValueAt(index, Number(input.value));\n };\n\n private handleNativeChange = () => {\n if (!this.readOnly && !this.isDisabled) this.commitIfChanged();\n };\n\n private handleKeyDown = (event: KeyboardEvent, index: number) => {\n const keys = [\n 'ArrowLeft',\n 'ArrowRight',\n 'ArrowUp',\n 'ArrowDown',\n 'Home',\n 'End',\n 'PageUp',\n 'PageDown',\n ];\n if (!this.readOnly && this.resolvedTicks.length && keys.includes(event.key)) {\n event.preventDefault();\n if (this.isDisabled) return;\n const ticks = this.resolvedTicks.filter(\n tick => tick.value >= this.allowedMin(index) && tick.value <= this.allowedMax(index)\n );\n const current = this.resolvedValues[index];\n const forward = ['ArrowRight', 'ArrowUp', 'PageUp'].includes(event.key);\n const next =\n event.key === 'Home'\n ? ticks[0]\n : event.key === 'End'\n ? ticks[ticks.length - 1]\n : forward\n ? ticks.find(tick => tick.value > current)\n : [...ticks].reverse().find(tick => tick.value < current);\n if (next && this.setValueAt(index, next.value)) this.commitIfChanged();\n return;\n }\n if (!this.readOnly) return;\n if (\n [\n 'ArrowLeft',\n 'ArrowRight',\n 'ArrowUp',\n 'ArrowDown',\n 'Home',\n 'End',\n 'PageUp',\n 'PageDown',\n ].includes(event.key)\n ) {\n event.preventDefault();\n }\n };\n\n private handleFocus = (index: number) => {\n this.activeThumbIndex = index;\n this.focused = true;\n };\n\n private handleBlur = (event: FocusEvent) => {\n const next = event.relatedTarget;\n if (next instanceof Node && this.el.contains(next)) return;\n this.focused = false;\n this.touched = true;\n };\n\n private handleHostClick = (event: MouseEvent) => {\n if (event.target !== this.el || this.isDisabled) return;\n const inputs = this.el.querySelectorAll<HTMLInputElement>('.slider__input');\n inputs[Math.min(this.activeThumbIndex, inputs.length - 1)]?.focus();\n };\n\n private pointerPercent(event: PointerEvent): number {\n const control = this.controlEl;\n if (!control) return 0;\n const rect = control.getBoundingClientRect();\n const endpoint = this.el.querySelector<HTMLElement>(\n this.thumbAlignment === 'edge' ? '.slider__thumb-orb' : '.slider__thumb'\n );\n const endpointSize = endpoint\n ? this.orientation === 'horizontal'\n ? endpoint.offsetWidth\n : endpoint.offsetHeight\n : 0;\n // Edge alignment positions the inner orb at the rail ends; the outer halo\n // may overhang. Center alignment uses the complete thumb at inset rail ends.\n const inset = endpointSize / 2;\n\n if (this.orientation === 'vertical') {\n const travel = Math.max(1, rect.height - inset * 2);\n return Math.min(1, Math.max(0, (rect.bottom - inset - event.clientY) / travel));\n }\n\n const travel = Math.max(1, rect.width - inset * 2);\n const percent = (event.clientX - rect.left - inset) / travel;\n return Math.min(1, Math.max(0, percent));\n }\n\n private valueFromPointer(event: PointerEvent): number {\n return this.snap(this.safeMin + this.pointerPercent(event) * (this.safeMax - this.safeMin));\n }\n\n private closestThumbIndex(value: number, target: EventTarget | null): number {\n const targetElement =\n target instanceof HTMLElement ? target.closest<HTMLElement>('.slider__thumb') : null;\n if (targetElement?.dataset['index']) return Number(targetElement.dataset['index']);\n if (!this.isRange) return 0;\n\n const values = this.resolvedValues;\n const lowerDistance = Math.abs(value - values[0]);\n const upperDistance = Math.abs(value - values[1]);\n if (lowerDistance === upperDistance) return value >= values[0] ? 1 : 0;\n return lowerDistance < upperDistance ? 0 : 1;\n }\n\n private handlePointerDown = (event: PointerEvent) => {\n if (event.button !== 0 || this.isDisabled || this.readOnly) return;\n event.preventDefault();\n const value = this.valueFromPointer(event);\n const index = this.closestThumbIndex(value, event.target);\n this.activeThumbIndex = index;\n this.pointerId = event.pointerId;\n this.pointerStartValue = this.cloneValue(this.publicValue);\n this.dragging = true;\n this.setValueAt(index, value);\n this.controlEl?.setPointerCapture(event.pointerId);\n requestAnimationFrame(() => {\n const input = this.el.querySelectorAll<HTMLInputElement>('.slider__input')[index];\n input?.focus({ preventScroll: true });\n });\n };\n\n private handlePointerMove = (event: PointerEvent) => {\n if (this.pointerId !== event.pointerId || !this.dragging) return;\n event.preventDefault();\n this.setValueAt(this.activeThumbIndex, this.valueFromPointer(event));\n };\n\n private endPointerInteraction(event: PointerEvent, canceled: boolean) {\n if (this.pointerId !== event.pointerId) return;\n if (this.controlEl?.hasPointerCapture(event.pointerId))\n this.controlEl.releasePointerCapture(event.pointerId);\n this.pointerId = null;\n this.dragging = false;\n this.touched = true;\n if (!canceled && !this.valuesEqual(this.pointerStartValue, this.publicValue))\n this.commitIfChanged();\n }\n\n private renderThumb(value: number, index: number) {\n const inputId =\n index === 0\n ? (this.inputId ?? `${this.generatedId}-input`)\n : `${this.inputId ?? `${this.generatedId}-input`}-end`;\n const rangeLabel = index === 0 ? this.startLabel : this.endLabel;\n const labelledby = this.isRange\n ? undefined\n : (this.ariaLabelledby ?? (this.label ? this.labelId : this.externalLabelledby));\n const directLabel = this.isRange ? rangeLabel : labelledby ? undefined : this.ariaLabel;\n\n return (\n <div\n class={{\n slider__thumb: true,\n 'slider__thumb--active': index === this.activeThumbIndex,\n }}\n data-index={String(index)}\n style={{ '--ds-slider-thumb-pct': String(this.percent(value)) } as Record<string, string>}\n >\n <input\n class=\"slider__input\"\n type=\"range\"\n id={inputId}\n min={this.allowedMin(index)}\n max={this.allowedMax(index)}\n step={this.resolvedTicks.length || this.step === 'any' ? 'any' : this.safeStep}\n value={value}\n disabled={this.isDisabled}\n aria-label={directLabel}\n aria-labelledby={labelledby}\n aria-describedby={this.ariaDescribedby}\n aria-valuetext={this.ariaValueText(value, index)}\n aria-orientation={this.orientation === 'vertical' ? 'vertical' : undefined}\n aria-readonly={this.readOnly ? 'true' : undefined}\n onInput={(event: Event) => this.handleNativeInput(event, index)}\n onChange={this.handleNativeChange}\n onKeyDown={event => this.handleKeyDown(event, index)}\n onFocus={() => this.handleFocus(index)}\n onBlur={this.handleBlur}\n />\n <span class=\"slider__thumb-visual\" aria-hidden=\"true\">\n <span class=\"slider__thumb-wash\" />\n <span class=\"slider__thumb-orb\" />\n </span>\n </div>\n );\n }\n\n render() {\n const values = this.resolvedValues;\n const first = values[0];\n const last = values[values.length - 1];\n const firstInputId = this.inputId ?? `${this.generatedId}-input`;\n const dirty = !this.valuesEqual(this.publicValue, this.initialValue);\n const valueDisplay = values\n .map((value, index) => this.formattedValue(value, index))\n .join(` ${this.rangeSeparator} `);\n\n return (\n <Host\n aria-disabled={this.isDisabled ? 'true' : undefined}\n class={{\n slider: true,\n 'ds-field-stack': true,\n [`slider--${this.size}`]: true,\n [`slider--${this.orientation}`]: true,\n [`slider--thumb-${this.thumbAlignment}`]: true,\n 'slider--range': this.isRange,\n 'slider--ticks': this.resolvedTicks.length > 0,\n 'slider--tick-labels': this.resolvedTicks.some(tick => Boolean(tick.label)),\n 'slider--readonly': this.readOnly,\n 'ds-control-inactive': this.isDisabled,\n }}\n data-orientation={this.orientation}\n data-disabled={this.isDisabled ? '' : undefined}\n data-readonly={this.readOnly ? '' : undefined}\n data-dragging={this.dragging ? '' : undefined}\n data-focused={this.focused ? '' : undefined}\n data-filled=\"\"\n data-dirty={dirty ? '' : undefined}\n data-touched={this.touched ? '' : undefined}\n data-valid={!this.isDisabled ? '' : undefined}\n onClick={this.handleHostClick}\n >\n {(this.label || this.showValue) && (\n <div class=\"slider__label-row\">\n {this.label && (\n <ds-text\n class=\"slider__label\"\n as=\"label\"\n variant=\"text-body-small\"\n color=\"primary\"\n emphasis\n for={firstInputId}\n textId={this.labelId}\n >\n {this.label}\n </ds-text>\n )}\n {this.showValue && (\n <ds-text\n class=\"slider__value\"\n as=\"span\"\n variant=\"text-body-small\"\n color=\"primary\"\n fontFeature=\"tabular-nums\"\n >\n {valueDisplay}\n </ds-text>\n )}\n </div>\n )}\n <div\n class=\"slider__control\"\n ref={element => {\n this.controlEl = element ?? null;\n }}\n style={\n {\n '--ds-slider-start-pct': String(this.isRange ? this.percent(first) : 0),\n '--ds-slider-end-pct': String(this.percent(last)),\n } as Record<string, string>\n }\n onPointerDown={this.handlePointerDown}\n onPointerMove={this.handlePointerMove}\n onPointerUp={(event: PointerEvent) => this.endPointerInteraction(event, false)}\n onPointerCancel={(event: PointerEvent) => this.endPointerInteraction(event, true)}\n >\n <div class=\"slider__rail\" aria-hidden=\"true\">\n <div class=\"slider__indicator\" />\n </div>\n {this.resolvedTicks.map(tick => (\n <span\n class=\"slider__tick\"\n data-edge={\n tick.value === this.safeMin\n ? 'start'\n : tick.value === this.safeMax\n ? 'end'\n : undefined\n }\n aria-hidden=\"true\"\n style={{ '--ds-slider-tick-pct': String(this.percent(tick.value)) }}\n >\n <span class=\"slider__tick-mark\" />\n {tick.label && (\n <ds-text\n class=\"slider__tick-label\"\n as=\"span\"\n variant=\"text-caption\"\n color=\"secondary\"\n >\n {tick.label}\n </ds-text>\n )}\n </span>\n ))}\n {values.map((value, index) => this.renderThumb(value, index))}\n </div>\n </Host>\n );\n }\n}\n",
|
|
721
722
|
"type": "registry:ui"
|
|
722
723
|
}
|
|
723
724
|
]
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"Activation updates checked immediately and emits dsChange with the new boolean value.",
|
|
58
58
|
"The initial checked value is the form-reset baseline; unchecked switches submit nothing unless uncheckedValue is provided.",
|
|
59
59
|
"Sizes are lg 40×24 with a 16px thumb and 4px body inset, md 32×20 with a 12px thumb and 4px inset, sm 24×16 with a 10px thumb and 3px inset, and xs 20×12 with an 8px thumb and 2px inset.",
|
|
60
|
-
"Unchecked uses a
|
|
60
|
+
"Unchecked uses a faint-neutral track with a density-specific tertiary-border inset stroke and a primary-background thumb with medium elevation. Checked uses a brand track with the same default tertiary-border inset stroke and elevated primary-background thumb.",
|
|
61
61
|
"Presentation mode removes switch semantics, focus, interaction, and form submission while retaining the checked visual."
|
|
62
62
|
],
|
|
63
63
|
"responsiveBehavior": [
|
|
@@ -393,7 +393,7 @@
|
|
|
393
393
|
"files": [
|
|
394
394
|
{
|
|
395
395
|
"path": "src/wc/components/Switch/Switch.css",
|
|
396
|
-
"content": "@import '../../utils/focus-ring.css';\n@import '../../utils/control-inactive.css';\n@import '../../utils/forced-colors.css';\n\n:host {\n --ds-switch-width: var(--dimension-size-400);\n --ds-switch-height: var(--dimension-size-250);\n --ds-switch-thumb-size: var(--dimension-size-150);\n --ds-switch-gap: var(--dimension-space-050);\n --ds-switch-stroke: var(--dimension-stroke-width-012);\n --ds-switch-inset: var(--ds-switch-gap);\n --ds-switch-thumb-hover: var(--color-interaction-hover);\n --ds-switch-thumb-pressed: var(--color-interaction-pressed);\n --ds-switch-travel: calc(\n var(--ds-switch-width) - var(--ds-switch-thumb-size) - var(--ds-switch-inset) -\n var(--ds-switch-inset)\n );\n position: relative;\n display: inline-flex;\n width: var(--ds-switch-width);\n height: var(--ds-switch-height);\n padding: 0;\n border: none;\n border-radius: var(--dimension-radius-half);\n box-sizing: border-box;\n flex-shrink: 0;\n overflow: visible;\n cursor: default;\n user-select: none;\n outline: none;\n background-color:
|
|
396
|
+
"content": "@import '../../utils/focus-ring.css';\n@import '../../utils/control-inactive.css';\n@import '../../utils/forced-colors.css';\n\n:host {\n --ds-switch-width: var(--dimension-size-400);\n --ds-switch-height: var(--dimension-size-250);\n --ds-switch-thumb-size: var(--dimension-size-150);\n --ds-switch-gap: var(--dimension-space-050);\n --ds-switch-stroke: var(--dimension-stroke-width-012);\n --ds-switch-inset: var(--ds-switch-gap);\n --ds-switch-thumb-hover: var(--color-interaction-hover);\n --ds-switch-thumb-pressed: var(--color-interaction-pressed);\n --ds-switch-travel: calc(\n var(--ds-switch-width) - var(--ds-switch-thumb-size) - var(--ds-switch-inset) -\n var(--ds-switch-inset)\n );\n position: relative;\n display: inline-flex;\n width: var(--ds-switch-width);\n height: var(--ds-switch-height);\n padding: 0;\n border: none;\n border-radius: var(--dimension-radius-half);\n box-sizing: border-box;\n flex-shrink: 0;\n overflow: visible;\n cursor: default;\n user-select: none;\n outline: none;\n background-color: var(--color-background-faint-neutral);\n box-shadow: inset 0 0 0 var(--ds-switch-stroke) var(--color-border-tertiary);\n transition: none;\n}\n\n:host(.switch--transitions-enabled) {\n transition:\n background-color var(--effect-motion-short-3),\n box-shadow var(--effect-motion-short-3);\n}\n\n:host(.switch--lg) {\n --ds-switch-width: var(--dimension-size-500);\n --ds-switch-height: var(--dimension-size-300);\n --ds-switch-thumb-size: var(--dimension-iconography-sm);\n --ds-switch-gap: var(--dimension-space-050);\n}\n\n:host(.switch--sm) {\n --ds-switch-width: var(--dimension-size-300);\n --ds-switch-height: var(--dimension-size-200);\n --ds-switch-thumb-size: calc(var(--dimension-size-base) * 5 / 4);\n --ds-switch-gap: calc(var(--dimension-space-025) + var(--dimension-space-012));\n}\n\n:host(.switch--xs) {\n --ds-switch-width: var(--dimension-size-250);\n --ds-switch-height: var(--dimension-size-150);\n --ds-switch-thumb-size: var(--dimension-size-100);\n --ds-switch-gap: var(--dimension-space-025);\n}\n\n:host(.checked) {\n background-color: var(--color-background-bold-brand);\n box-shadow: inset 0 0 0 var(--ds-switch-stroke) var(--color-border-tertiary);\n}\n\n:host(.switch--readonly),\n:host(.switch--presentation) {\n cursor: default;\n}\n\n:host(.switch--presentation) {\n pointer-events: none;\n}\n\n/* Inactive: `.ds-control-inactive` (50% opacity). */\n\n.thumb {\n position: absolute;\n top: 50%;\n left: var(--ds-switch-inset);\n width: var(--ds-switch-thumb-size);\n height: var(--ds-switch-thumb-size);\n border-radius: var(--dimension-radius-half);\n background-color: var(--color-background-primary);\n box-shadow: var(--effect-elevation-elevated-md);\n transform: translateY(-50%);\n transition: none;\n pointer-events: none;\n}\n\n:host(.switch--transitions-enabled) .thumb {\n transition:\n background-color var(--effect-motion-short-3),\n transform var(--effect-motion-short-3);\n}\n\n.thumb::after {\n content: '';\n position: absolute;\n inset: 0;\n border-radius: inherit;\n background: transparent;\n pointer-events: none;\n}\n\n@media (hover: hover) and (pointer: fine) {\n :host(\n :hover:not(:disabled):not(.ds-control-inactive):not(.switch--readonly):not(\n .switch--presentation\n )\n )\n .thumb::after {\n background: var(--ds-switch-thumb-hover);\n }\n}\n\n:host(\n :active:not(:disabled):not(.ds-control-inactive):not(.switch--readonly):not(\n .switch--presentation\n )\n )\n .thumb::after {\n background: var(--ds-switch-thumb-pressed);\n}\n\n:host(.checked) .thumb {\n background-color: var(--color-background-primary);\n transform: translate3d(var(--ds-switch-travel), -50%, 0);\n}\n\n@media (prefers-reduced-motion: reduce) {\n :host,\n :host(.switch--transitions-enabled) {\n transition: none;\n }\n\n .thumb,\n :host(.switch--transitions-enabled) .thumb {\n transition: none;\n }\n}\n\n@media (forced-colors: active) {\n :host {\n border: var(--ds-switch-stroke) solid var(--ds-forced-color-content);\n box-shadow: none;\n }\n\n :host(.checked) {\n background-color: var(--ds-forced-color-selected);\n border-color: var(--ds-forced-color-selected);\n box-shadow: none;\n }\n\n .thumb {\n background-color: var(--ds-forced-color-content);\n box-shadow: none;\n }\n\n :host(.checked) .thumb {\n background-color: var(--ds-forced-color-on-selected);\n }\n}\n",
|
|
397
397
|
"type": "registry:ui"
|
|
398
398
|
},
|
|
399
399
|
{
|