diff --git a/docs/components/slider.md b/docs/components/slider.md index 4f34d2e1ab..db63018d2a 100644 --- a/docs/components/slider.md +++ b/docs/components/slider.md @@ -59,6 +59,69 @@ Sliders can use icons or labels to represent a numeric or relative scale. +## Types + + + + + + + +1. [Continuous](#continuous) +1. [Discrete](#discrete) +1. [Range](#range) + ## Usage Sliders may be continuous or discrete, and may also represent a range. diff --git a/slider/internal/_slider.scss b/slider/internal/_slider.scss index e1f192b7d6..5fd0e648d3 100644 --- a/slider/internal/_slider.scss +++ b/slider/internal/_slider.scss @@ -37,26 +37,26 @@ $_md-sys-shape: tokens.md-sys-shape-values(); // size to account for always showing the active track on the outside // edge of the last tick. $_active-track-max-clip: calc( - 100% - var(--_with-tick-marks-container-size) * 2 + 100% - var(--_with-tick-marks-container-size) * 2 ); // When the start fraction is !0, add clipping by the tick container size $_start-fraction-not-zero: min(var(--_start-fraction) * 1e9, 1); $_active-track-start-offset: calc( - var(--_with-tick-marks-container-size) * $_start-fraction-not-zero + var(--_with-tick-marks-container-size) * $_start-fraction-not-zero ); $_active-track-start-clip: calc( - $_active-track-start-offset + $_active-track-max-clip * - var(--_start-fraction) + $_active-track-start-offset + $_active-track-max-clip * + var(--_start-fraction) + 4px ); // When the end fraction is !1, add clipping by the tick container size $_end-fraction-not-one: min((1 - var(--_end-fraction)) * 1e9, 1); $_active-track-end-offset: calc( - var(--_with-tick-marks-container-size) * $_end-fraction-not-one + var(--_with-tick-marks-container-size) * $_end-fraction-not-one ); $_active-track-end-clip: calc( - $_active-track-end-offset + $_active-track-max-clip * - (1 - var(--_end-fraction)) + $_active-track-end-offset + $_active-track-max-clip * + (1 - var(--_end-fraction)) + 4px ); :host { @@ -84,7 +84,7 @@ $_md-sys-shape: tokens.md-sys-shape-values(); md-focus-ring { height: 48px; inset: unset; - width: 48px; + width: 8px; } md-elevation { @@ -123,68 +123,147 @@ $_md-sys-shape: tokens.md-sys-shape-values(); touch-action: none; } - .track, + .trackPadded, .tickmarks { position: absolute; inset: 0; display: flex; align-items: center; + padding-inline: calc(var(--_state-layer-size) / 2); + } + + .track-start { + position: absolute; + background: var(--_active-track-color); + block-size: var(--_active-track-height); + border-top-left-radius: var(--_active-track-shape); + border-bottom-left-radius: var(--_active-track-shape); + border-top-right-radius: 4000px; + border-bottom-right-radius: 4000px; + inline-size: calc(100% * var(--_end-fraction) - var(--_state-layer-size) / 2); + left: 0px; + } + + .ranged .track-start { + background: var(--_inactive-track-color); + inline-size: calc(100% * var(--_start-fraction) - var(--_state-layer-size) / 2); + background-image: _get-tick-image(var(--_with-tick-marks-inactive-container-color), calc(var(--_with-tick-marks-container-size) + 2px)); + } + + input.end:focus ~ .track .track-start { + inline-size: calc(100% * var(--_start-fraction) - var(--_state-layer-size) / 2 + 2px); + } + + .track-middle { + position: absolute; + background: var(--_active-track-color); + block-size: var(--_active-track-height); + border-radius: 4px; + inline-size: calc(100% * (var(--_end-fraction) - var(--_start-fraction)) - var(--_state-layer-size)); + left: calc(100% * var(--_start-fraction) + var(--_state-layer-size) / 2); + } + + input.start:focus ~ .track .track-middle { + margin-inline-start: calc(var(--_state-layer-size) / 2 - 2px); + } + + input.end:focus ~ .track .track-middle { + inline-size: calc(100% * (var(--_end-fraction) - var(--_start-fraction)) - var(--_state-layer-size) / 2); + margin-inline-end: calc(var(--_state-layer-size) / 2 - 2px); + } + + .track-end { + position: absolute; + background: var(--_inactive-track-color); + block-size: var(--_inactive-track-height); + border-top-right-radius: var(--_inactive-track-shape); + border-bottom-right-radius: var(--_inactive-track-shape); + border-top-left-radius: 4000px; + border-bottom-left-radius: 4000px; + inline-size: calc(100% * (1 - var(--_end-fraction)) - var(--_state-layer-size) / 2); + right: 0px; + background-image: _get-tick-image(var(--_with-tick-marks-inactive-container-color), calc(100% - var(--_with-tick-marks-container-size) - 2px)); + } + + input.end:focus ~ .track .track-end { + inline-size: calc(100% * (1 - var(--_end-fraction)) - var(--_state-layer-size) / 2 + 2px); } - // inactive-track - .track::before, .tickmarks::before, - // active-track - .track::after, .tickmarks::after { position: absolute; content: ''; // pad the track inward by half the ripple size offset by the tick container size. $_track-padding: calc( - (var(--_state-layer-size) / 2) - var(--_with-tick-marks-container-size) + (var(--_state-layer-size) / 2) - var(--_with-tick-marks-container-size) ); inset-inline-start: $_track-padding; inset-inline-end: $_track-padding; // ticks size: set here since it does not change. background-size: calc( - (100% - var(--_with-tick-marks-container-size) * 2) / var(--_tick-count) - ) - 100%; + (100% - var(--_with-tick-marks-container-size) * 2) / var(--_tick-count) + ) 100%; } // inactive-track - .track::before, .tickmarks::before { block-size: var(--_inactive-track-height); border-radius: var(--_inactive-track-shape); } - .track::before { - background: var(--_inactive-track-color); - } - .tickmarks::before { background-image: _get-tick-image( - var(--_with-tick-marks-inactive-container-color) + var(--_with-tick-marks-inactive-container-color) ); + margin-left: 6px; + margin-right: 6px; } :host([disabled]) .track::before { // Note, the active track opacity is applied to the entire host, // so the inactive track is calc'd to compensate. opacity: calc( - (1 / var(--_disabled-active-track-opacity)) * - var(--_disabled-inactive-track-opacity) + (1 / var(--_disabled-active-track-opacity)) * + var(--_disabled-inactive-track-opacity) ); background: var(--_disabled-inactive-track-color); } + .tickmarks::before { + clip-path: polygon( + calc((var(--_end-fraction) * 100%) - 8px) 0%, + calc((var(--_end-fraction) * 100%) - 8px) 100%, + calc((var(--_end-fraction) * 100%) + 8px) 100%, + calc((var(--_end-fraction) * 100%) + 8px) 0%, + 100% 0%, + 100% 100%, + 0% 100%, + 0% 0% + ); + } + + .ranged .tickmarks::before { + clip-path: polygon( + 0% 0%, + 0% 100%, + calc((var(--_start-fraction) * 100%) - 8px) 100%, + calc((var(--_start-fraction) * 100%) - 8px) 0%, + calc((var(--_end-fraction) * 100%) + 8px) 0%, + calc((var(--_end-fraction) * 100%) + 8px) 100%, + 100% 100%, + 100% 0% + ); + } + // active-track - .track::after, .tickmarks::after { block-size: var(--_active-track-height); border-radius: var(--_active-track-shape); + clip-path: inset(0 $_active-track-end-clip 0 0); + } + + .ranged .tickmarks::after { clip-path: inset(0 $_active-track-end-clip 0 $_active-track-start-clip); } @@ -194,8 +273,10 @@ $_md-sys-shape: tokens.md-sys-shape-values(); .tickmarks::after { background-image: _get-tick-image( - var(--_with-tick-marks-active-container-color) + var(--_with-tick-marks-active-container-color) ); + margin-left: 6px; + margin-right: 6px; } // rtl for active track clipping @@ -213,7 +294,7 @@ $_md-sys-shape: tokens.md-sys-shape-values(); :host([disabled]) .tickmarks::before { background-image: _get-tick-image( - var(--_with-tick-marks-disabled-container-color) + var(--_with-tick-marks-disabled-container-color) ); } @@ -230,6 +311,7 @@ $_md-sys-shape: tokens.md-sys-shape-values(); padding-inline: calc(var(--_state-layer-size) / 2); } + .trackBlock, .handleContainerBlock { position: relative; block-size: 100%; @@ -270,6 +352,7 @@ $_md-sys-shape: tokens.md-sys-shape-values(); input.end:focus ~ .handleContainerPadded .handle.end > .handleNub, input.start:focus ~ .handleContainerPadded .handle.start > .handleNub { background: var(--_focus-handle-color); + width: 2px; } // prefix classes exist to overcome specificity of focus styling. @@ -287,13 +370,11 @@ $_md-sys-shape: tokens.md-sys-shape-values(); .onTop.isOverlapping { .label, .label::before { - outline: var(--_with-overlap-handle-outline-color) solid - var(--_with-overlap-handle-outline-width); + outline: var(--_with-overlap-handle-outline-color) solid var(--_with-overlap-handle-outline-width); } .handleNub { - border: var(--_with-overlap-handle-outline-color) solid - var(--_with-overlap-handle-outline-width); + border: var(--_with-overlap-handle-outline-color) solid var(--_with-overlap-handle-outline-width); } } @@ -309,7 +390,10 @@ $_md-sys-shape: tokens.md-sys-shape-values(); position: absolute; box-sizing: border-box; display: flex; - padding: 4px; + padding-top: 12px; + padding-bottom: 12px; + padding-left: 16px; + padding-right: 16px; place-content: center; place-items: center; border-radius: map.get($_md-sys-shape, 'corner-full'); @@ -320,12 +404,11 @@ $_md-sys-shape: tokens.md-sys-shape-values(); line-height: var(--_label-text-line-height); font-weight: var(--_label-text-weight); - inset-block-end: 100%; + inset-block-end: calc(100% + 18px); min-inline-size: var(--_label-container-height); min-block-size: var(--_label-container-height); background: var(--_label-container-color); - transition: transform map.get($_md-sys-motion, 'duration-short2') - map.get($_md-sys-motion, 'easing-emphasized'); + transition: transform map.get($_md-sys-motion, 'duration-short2') map.get($_md-sys-motion, 'easing-emphasized'); transform-origin: center bottom; transform: scale(0); } @@ -346,16 +429,6 @@ $_md-sys-shape: tokens.md-sys-shape-values(); background: inherit; } - // triangle below label - .label::before { - // Note, sizing carefully estimated to create an ice cream cone shape. - $_triangleSize: calc(var(--_label-container-height) / 2); - inline-size: $_triangleSize; - block-size: $_triangleSize; - bottom: calc(var(--_label-container-height) / -10); - transform: rotate(45deg); - } - // fits inside label and occludes top half of triangle. .label::after { inset: 0px; @@ -423,11 +496,9 @@ $_md-sys-shape: tokens.md-sys-shape-values(); // Clip the inputs to the space left/right of the center point between the // values so the right input gets pointer events. $_clip-to-start: calc( - var(--_state-layer-size) / 2 + (100% - var(--_state-layer-size)) * - ( - var(--_start-fraction) + - ((var(--_end-fraction) - var(--_start-fraction)) / 2) - ) + var(--_state-layer-size) / 2 + (100% - var(--_state-layer-size)) * + (var(--_start-fraction) + + ((var(--_end-fraction) - var(--_start-fraction)) / 2)) ); $_clip-to-end: calc(100% - $_clip-to-start); @@ -476,12 +547,12 @@ $_md-sys-shape: tokens.md-sys-shape-values(); } // Returns a background-image with sized circular ticks of the given color. -@function _get-tick-image($color) { +@function _get-tick-image($color, $location: var(--_with-tick-marks-container-size)) { @return radial-gradient( - circle at var(--_with-tick-marks-container-size) center, - #{$color} 0, - #{$color} calc(var(--_with-tick-marks-container-size) / 2), - transparent calc(var(--_with-tick-marks-container-size) / 2) + circle at $location center, + #{$color} 0, + #{$color} calc(var(--_with-tick-marks-container-size) / 2), + transparent calc(var(--_with-tick-marks-container-size) / 2) ); } @@ -504,11 +575,11 @@ $_md-sys-shape: tokens.md-sys-shape-values(); input.#{$start-or-end}::-webkit-slider-thumb { // AKA `layout-shift` in the equations above --_track-and-knob-padding: calc( - (var(--_state-layer-size) - var(--_handle-width)) / 2 + (var(--_state-layer-size) - var(--_handle-width)) / 2 ); --_x-translate: calc( - var(--_track-and-knob-padding) - 2 * var(--_#{$start-or-end}-fraction) * - var(--_track-and-knob-padding) + var(--_track-and-knob-padding) - 2 * var(--_#{$start-or-end}-fraction) * + var(--_track-and-knob-padding) ); transform: translateX(var(--_x-translate)); } diff --git a/slider/internal/slider.ts b/slider/internal/slider.ts index 997859c1a9..9e525b2726 100644 --- a/slider/internal/slider.ts +++ b/slider/internal/slider.ts @@ -4,7 +4,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -import '../../elevation/elevation.js'; import '../../focus/md-focus-ring.js'; import '../../ripple/ripple.js'; @@ -403,11 +402,21 @@ export class Slider extends sliderBaseClass { private renderTrack() { return html` -
+